Compare commits

..

No commits in common. "master" and "Crypto-feature" have entirely different histories.

61 changed files with 756 additions and 1408 deletions

2
.gitignore vendored
View file

@ -16,6 +16,4 @@
/Calendar/CalendarEvent.pyc
/Calendar/design_exported_old.png
/Calendar/settings_dev.py
/Calendar/settings_pers.py
/.vscode
/Calendar/images/

View file

@ -8,17 +8,14 @@ from settings import line_thickness
separator_width = line_thickness
class AgendaListDesign (DesignEntity):
'''Lists upcoming events in chronological order and groups them by days'''
def __init__(self, size, calendar, line_spacing=0, col_spacing=8, text_size=defaultfontsize, start_date=date.today(), always_add_start_row=True, day_limit_foresight=91):
def __init__ (self, size, calendar, line_spacing = 0, col_spacing = 8, text_size = defaultfontsize, start_date = date.today(), always_add_start_row = True):
super(AgendaListDesign, self).__init__(size)
self.calendar = calendar
self.line_spacing = line_spacing
self.col_spacing = col_spacing
self.text_size = text_size
self.day_limit_foresight = day_limit_foresight
self.start_dt = date(start_date.year, start_date.month, start_date.day)
self.always_add_start_row = always_add_start_row
@ -38,8 +35,7 @@ class AgendaListDesign (DesignEntity):
self.infos = []
self.cell_props = []
fetch_day = self.start_dt
days_foresight = 0
while len(self.infos) < self.__event_number__ and days_foresight < self.day_limit_foresight:
while len(self.infos) < self.__event_number__:
day_events = self.calendar.get_day_events(fetch_day)
fetch_day_added_once = False
for event in day_events:
@ -56,7 +52,6 @@ class AgendaListDesign (DesignEntity):
self.infos.append(row)
fetch_day = fetch_day + timedelta(1)
days_foresight = days_foresight + 1
if self.infos[0][1] != date_summary_str(self.start_dt) and self.always_add_start_row:
row = ["", date_summary_str(self.start_dt), "", ""]
@ -65,8 +60,7 @@ class AgendaListDesign (DesignEntity):
self.cell_props.insert(0, props)
def __draw_infos__ (self):
table = TableDesign(self.size, self.infos, fontsize=self.__date_fontsize__,
line_spacing=self.__date_linespace__, col_spacing=self.col_spacing, cell_properties=self.cell_props)
table = TableDesign(self.size, self.infos, fontsize = self.__date_fontsize__, line_spacing=self.__date_linespace__, col_spacing = self.col_spacing, cell_properties=self.cell_props)
self.draw_design(table)
def __draw_lines__ (self):
@ -79,8 +73,7 @@ class AgendaListDesign (DesignEntity):
pos = (0, ypos)
positions = [ pos, (self.size[0], ypos) ]
ImageDraw.Draw(self.__image__).line(
positions, fill=colors["fg"], width=separator_width)
ImageDraw.Draw(self.__image__).line(positions, fill=colors["fg"], width=separator_width)
def __get_row_props__ (self, event = None):
color = colors["fg"]

View file

@ -13,17 +13,14 @@ seperator_width = line_thickness
infolist_size = (1, 0.24)
infolist_padding = 0
class AgendaListPanel (PanelDesign):
'''Lists upcoming events in chronological order and groups them by days'''
def __init__(self, size):
super(AgendaListPanel, self).__init__(size)
self.weather_size = (0, 0)
self.info_size = (0, 0)
if general_settings["weather-info"]:
self.weather_size = (
self.size[0], self.size[1] * weatherheader_height)
self.weather_size = (self.size[0], self.size[1] * weatherheader_height)
def add_weather (self, weather):
self.weather = weather
@ -68,8 +65,7 @@ class AgendaListPanel (PanelDesign):
self.__draw_weather__()
def __draw_seperator__ (self, height, color):
ImageDraw.Draw(self.__image__).line([self.__abs_pos__(
(0, height)), self.__abs_pos__((1, height))], fill=color, width=seperator_width)
ImageDraw.Draw(self.__image__).line([ self.__abs_pos__((0, height)), self.__abs_pos__((1, height)) ], fill=color, width=seperator_width)
def __abs_pos__ (self, pos, size = None):
if size is None:
@ -77,8 +73,7 @@ class AgendaListPanel (PanelDesign):
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
def __draw_calendar__(self):
size = (self.size[0], self.size[1] - self.weather_size[1] -
self.info_size[1] - agenda_ypadding)
size = (self.size[0], self.size[1] - self.weather_size[1] - self.info_size[1] - agenda_ypadding)
agenda = AgendaListDesign(size, self.calendar)
agenda.pos = (0, agenda_ypadding + self.weather_size[1])

View file

@ -47,49 +47,3 @@ colors = {
"fg" : "black",
"bg" : "white"
}
supported_img_formats = [
"BMP",
"DIB",
"EPS",
"GIF",
"ICNS",
"ICO",
"IM",
"JPG",
"JPEG",
"J2K",
"J2P",
"JPX",
"MSP",
"PCX",
"PNG",
"PPM",
"SGI",
"SPI",
"TGA",
"TIFF",
"WEBP",
"XBM",
"BLP",
"CUR",
"DCX",
"DDS",
"FLI",
"FLC",
"FPX",
"FTEX",
"GBR",
"GD",
"IMT",
"IPTC",
"NAA",
"MCIDAS",
"MIC",
"MPO",
"PCD",
"PIXAR",
"PSD",
"WAL",
"XPM",
]

View file

@ -1,10 +1,8 @@
from DesignEntity import DesignEntity
from PIL import ImageDraw, ImageOps
class BoxDesign (DesignEntity):
"""Redefinition of ImageDraw.Draw.Rectangle"""
def __init__(self, size, fill=None, outline=None, width=0):
super(BoxDesign, self).__init__((size[0]+1, size[1]+1), mask=True)
self.size = size
@ -19,5 +17,4 @@ class BoxDesign (DesignEntity):
self.corners = [topleft, bottomright]
def __finish_image__ (self):
ImageDraw.Draw(self.__image__).rectangle(
self.corners, fill=self.fill, outline=self.outline, width=self.width)
ImageDraw.Draw(self.__image__).rectangle(self.corners, fill=self.fill, outline=self.outline, width=self.width)

View file

@ -1,6 +1,5 @@
class CalendarEvent (object):
"""Defines a calendar event, independent of any implementation"""
def __init__ (self):
self.begin_datetime = None
self.end_datetime = None
@ -15,7 +14,6 @@ class CalendarEvent (object):
self.highlight = None
self.calendar_name = None
self.calendar_url = None
self.location = None
self.fetch_datetime = None

View file

@ -3,15 +3,11 @@ from datetime import datetime, timezone, timedelta, date
from dateutil.rrule import rrulestr
from dateutil.parser import parse
import calendar
from CalendarEvent import CalendarEvent
class CalendarInterface (DataSourceInterface):
"""Interface for fetching and processing calendar event information."""
def __init__ (self):
self.events = []
self.excluded_urls = []
def reload (self):
if self.is_available() == False:
@ -19,9 +15,6 @@ class CalendarInterface (DataSourceInterface):
self.events = self.__get_events__()
self.events = self.__sort_events__(self.events)
def exclude_calendars(self, urls=[]):
self.excluded_urls = urls
def __sort_events__ (self, events):
events.sort(key=lambda x : x.begin_datetime)
return events
@ -29,8 +22,7 @@ class CalendarInterface (DataSourceInterface):
def __sort_event_types__ (self, events):
multiday = [ev for ev in events if ev.multiday]
allday = [ev for ev in events if ev.allday and ev.multiday == False]
timed = [ev for ev in events if ev.allday ==
False and ev.multiday == False]
timed = [ev for ev in events if ev.allday == False and ev.multiday == False]
return multiday + allday + timed
def __get_events__ (self):
@ -49,11 +41,9 @@ class CalendarInterface (DataSourceInterface):
def get_day_events (self, day):
if type(day) is not type(date.today()):
raise TypeError(
"get_day_events only takes date-objects as parameters, not \"%s\"" % str(type(day)))
raise TypeError("get_day_events only takes date-objects as parameters, not \"%s\"" % str(type(day)))
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
day_start = datetime(day.year, day.month, day.day,
0, 0, 0, 0, local_tzinfo)
day_start = datetime(day.year, day.month, day.day, 0, 0, 0, 0, local_tzinfo)
return self.__get_events_in_range__(day_start, timedelta(1))
def get_month_events (self, month = -1, year = -1):
@ -64,8 +54,7 @@ class CalendarInterface (DataSourceInterface):
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
month_start = datetime(year, month, 1, 0, 0, 0, 0, local_tzinfo)
month_days = calendar.monthrange(
month_start.year, month_start.month)[1]
month_days = calendar.monthrange(month_start.year, month_start.month)[1]
return self.__get_events_in_range__(month_start, timedelta(month_days))
def __get_events_in_range__ (self, start, duration):
@ -77,12 +66,7 @@ class CalendarInterface (DataSourceInterface):
events_in_range = []
for event in self.events:
# Is excluded?
if event.calendar_url in self.excluded_urls:
continue
event_occurrence = self.__get_if_event_in_range__(
event, start, duration)
event_occurrence = self.__get_if_event_in_range__(event, start, duration)
if event_occurrence:
events_in_range.extend(event_occurrence)
@ -118,65 +102,37 @@ class CalendarInterface (DataSourceInterface):
end = start + duration
occurrences = []
try:
r_string = ""
r_string=self.__add_timezoneawarness__(event.rrule)
rule=rrulestr(r_string,dtstart=event.begin_datetime)
for occurrence in rule:
if occurrence - end > timedelta(0):
return occurrences
merged_event = self.__merge_event_data__(
event, start=occurrence)
merged_event = self.__merge_event_data__(event, start=occurrence)
if self.__is_onetime_in_range__(merged_event, start, duration):
occurrences.append(merged_event)
return occurrences
except Exception as ex:
print("\"is_repeating_in_range\" failed while processing: dtstart="+str(event.begin_datetime) +
" dtstart.tzinfo="+str(event.begin_datetime.tzinfo)+" rrule="+r_string)
raise ex
def __merge_event_data__ (self, event, start = None):
merged_event = CalendarEvent()
merged_event.begin_datetime = event.begin_datetime
merged_event.end_datetime = event.end_datetime
merged_event.duration = event.duration
merged_event.allday = event.allday
merged_event.multiday = event.multiday
merged_event.rrule = event.rrule
merged_event.title = event.title
merged_event.description = event.description
merged_event.attendees = event.attendees
merged_event.highlight = event.highlight
merged_event.calendar_name = event.calendar_name
merged_event.calendar_url = event.calendar_url
merged_event.location = event.location
merged_event.fetch_datetime = event.fetch_datetime
if start is not None:
merged_event.begin_datetime = start
merged_event.end_datetime = start + event.duration
event.begin_datetime = start
event.end_datetime = start + event.duration
return merged_event
return event
def __add_timezoneawarness__ (self, rrule):
"""UNTIL must be specified in UTC when DTSTART is timezone-aware (which it is)"""
if "UNTIL" not in rrule:
return rrule
timezone_str = "T000000Z"
until_template = "UNTIL=YYYYMMDD"
until_example = "UNTIL=YYYYMMDD"
until_index = rrule.index("UNTIL")
tz_index = until_index + len(until_template)
if until_index < 0 or (tz_index < len(rrule) and rrule[tz_index] is "T"):
tz_index = until_index + len(until_example)
if tz_index < 0 or tz_index >= len(rrule):
return rrule
if rrule[tz_index] is "T":
return rrule
if tz_index == len(rrule):
return rrule + timezone_str
else:
return rrule[:tz_index] + timezone_str + rrule[tz_index:]

View file

@ -1,6 +1,5 @@
from DataSourceInterface import DataSourceInterface
class CryptoInterface(DataSourceInterface):
def __init__(self):
self.crypto_coins = []

View file

@ -6,7 +6,6 @@ from settings import crypto_coins
xpadding = 5
class CryptoListDesign (DesignEntity):
def __init__ (self, size, crypto, text_size = defaultfontsize):
super(CryptoListDesign, self).__init__(size)
@ -19,8 +18,7 @@ class CryptoListDesign (DesignEntity):
if len(self.matrix) > 0:
col_spacing = (self.size[0] / len(self.matrix[0])) * 0.5
table_design = TableDesign(self.size, matrix=self.matrix, col_spacing=col_spacing,
fontsize=self.text_size, mask=False, truncate_rows=True)
table_design = TableDesign(self.size, matrix=self.matrix, col_spacing=col_spacing, fontsize = self.text_size, mask=False, truncate_rows=True)
table_design.pos = (xpadding, 0)
self.draw_design(table_design)
@ -28,8 +26,7 @@ class CryptoListDesign (DesignEntity):
matrix = []
coins = self.crypto.get_coins()
for coin in coins:
row = [coin.symbol.upper(), coin.name, coin.currency + " " +
str(coin.price), "% " + str(coin.day_change)]
row = [ coin.symbol.upper(), coin.name, coin.currency + " " + str(coin.price), "% " + str(coin.day_change) ]
matrix.append(row)
return matrix

View file

@ -1,6 +1,5 @@
class DataSourceInterface (object):
"""Interface for child interfaces that fetch data."""
def is_available (self):
raise NotImplementedError("Functions needs to be implemented")

View file

@ -4,10 +4,8 @@ from TextDesign import TextDesign
header_height = 0.2
class DayBoxDesign (DesignEntity):
"""Represents a day with its events in a box."""
def __init__(self, size, date):
super(DayBoxDesign, self).__init__(size)
self.date = date

View file

@ -1,151 +0,0 @@
from datetime import date, datetime, timedelta, timezone
from settings import line_thickness, general_settings
from DayHeaderDesign import DayHeaderDesign
from HourListDesign import HourListDesign
from DayRowDesign import DayRowDesign
from PanelDesign import PanelDesign
from Assets import colors
from PIL import ImageDraw
HEADER_SIZE = (1, 0.2)
HOURLIST_HEIGHT = 0.3
HOURLIST_SIZE = (1, HOURLIST_HEIGHT)
DAYLIST_YPOS = HEADER_SIZE[1] + HOURLIST_SIZE[1]
DAYLIST_HEIGHT = 1 - HEADER_SIZE[1] - HOURLIST_SIZE[1]
DAYLIST_SIZE = (1, DAYLIST_HEIGHT)
HOURS_COUNT = 6
DAYROW_MIN_FORMAT = 40 / 384
DAYROW_MAX_FORMAT = 60 / 384
PANEL_LINE_THICKNESS = line_thickness
class DayFocusListPanel (PanelDesign):
"""Shows Day-View for today and a short Day-List for
the upcoming days."""
def __init__(self, size):
super(DayFocusListPanel, self).__init__(size)
self.hours_count = HOURS_COUNT
self.__init_modules__()
def __abs_co__(self, coordinates):
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
def __init_modules__(self):
self.__init_header__()
self.__init_hourlist__()
self.__init_daylist__()
def __init_header__(self):
self.__header__ = DayHeaderDesign(
self.__abs_co__(HEADER_SIZE), date.today())
self.__header__.pos = (0, 0)
def __init_hourlist__(self):
start, end = self.__get_current_hour_range__()
size = self.__abs_co__(HOURLIST_SIZE)
self.__hourlist__ = HourListDesign(size, start, end)
self.__hourlist__.pos = (0, self.__header__.size[1])
def __init_daylist__(self):
self.__daylist_rows__ = []
self.__calc_dayrow_size__()
self.__create_day_rows__()
def __calc_dayrow_size__(self):
max_area_height = DAYLIST_HEIGHT * self.size[1]
max_row_number = max_area_height / (DAYROW_MIN_FORMAT * self.size[0])
min_row_number = max_area_height / (DAYROW_MAX_FORMAT * self.size[0])
average_row_number = (max_row_number + min_row_number) / 2
self.dayrow_count = round(average_row_number)
row_height = max_area_height / self.dayrow_count
self.dayrow_size = (1, row_height / self.size[1])
def __create_day_rows__(self):
following_days = self.__get_following_days__()
for i, date in enumerate(following_days):
row = DayRowDesign(self.__abs_co__(self.dayrow_size), date)
row.pos = self.__get_day_row_pos__(i)
self.__daylist_rows__.append(row)
def __get_following_days__(self):
following_days = []
for i in range(self.dayrow_count):
following_days.append(date.today() + timedelta(days=i + 1))
return following_days
def __get_day_row_pos__(self, i):
ypos = self.size[1] * DAYLIST_YPOS
down_shift = i * self.dayrow_size[1] * self.size[1]
return (0, int(ypos + down_shift))
def __finish_panel__(self):
self.draw_design(self.__header__)
self.draw_design(self.__hourlist__)
for row in self.__daylist_rows__:
self.draw_design(row)
self.__draw_daylist_lines__()
def __draw_daylist_lines__(self):
positions = []
for i in range(len(self.__daylist_rows__)):
positions.append(self.__get_day_row_pos__(i)[1])
for ypos in positions:
line_start = (0, ypos)
line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["fg"], width=PANEL_LINE_THICKNESS)
def __get_current_hour_range__(self):
start_hour = datetime.now().hour
additional_hours = self.hours_count - 1
if start_hour + additional_hours > 23:
start_hour = 23 - additional_hours
return start_hour, start_hour + additional_hours
def add_weather(self, weather):
self.__header__.add_weather(weather)
def add_calendar(self, calendar):
allday_ev, timed_ev = self.__split_events__(
calendar.get_today_events())
self.__header__.add_events(allday_ev)
self.__hourlist__.add_events(timed_ev)
self.__add_calendar_daylist__(calendar)
def __split_events__(self, events):
allday_ev = []
timed_ev = []
for event in events:
if event.allday:
allday_ev.append(event)
elif event.multiday:
if self.__is_today__(event.begin_datetime):
timed_ev.append(event)
elif self.__is_today__(event.end_datetime):
timed_ev.append(event)
else:
allday_ev.append(event)
else:
timed_ev.append(event)
return allday_ev, timed_ev
def __is_today__(self, dt):
today = date.today()
return dt.day == today.day and \
dt.month == today.month and \
dt.year == today.year
def __add_calendar_daylist__(self, calendar):
calendar.exclude_calendars(general_settings["extra-excluded-urls"])
for row in self.__daylist_rows__:
row.add_calendar(calendar)
calendar.exclude_calendars()

View file

@ -27,10 +27,8 @@ numberbox_font_color = colors["bg"]
numberbox_background_color = colors["hl"]
weekday_font = fonts["bold"]
class DayHeaderDesign (DesignEntity):
"""Detailed and big view of a given date."""
def __init__ (self, size, date):
super(DayHeaderDesign, self).__init__(size)
self.weather_column_width = 0
@ -40,11 +38,9 @@ class DayHeaderDesign (DesignEntity):
if general_settings["weather-info"] == False:
return
forecast = weather.get_forecast_in_days(
self.date.day - date.today().day)
forecast = weather.get_forecast_in_days(self.date.day - date.today().day)
self.weather_column_width = weathercolumn_y_size[0] * self.size[1]
size = (self.weather_column_width,
weathercolumn_y_size[1] * self.size[1])
size = (self.weather_column_width, weathercolumn_y_size[1] * self.size[1])
pos = (self.size[0] - size[0], 0)
design = WeatherColumnDesign(size, forecast)
@ -54,10 +50,8 @@ class DayHeaderDesign (DesignEntity):
def add_calendar (self, calendar):
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
now = datetime.now(local_tzinfo)
time_until_tomorrow = (datetime(
now.year, now.month, now.day, 0, 0, 0, 0, local_tzinfo) + timedelta(1)) - now
self.__draw_event_list__(
calendar.get_upcoming_events(time_until_tomorrow, now))
time_until_tomorrow = (datetime(now.year, now.month, now.day, 0, 0, 0, 0, local_tzinfo) + timedelta(1)) - now
self.__draw_event_list__(calendar.get_upcoming_events(time_until_tomorrow, now))
def add_events (self, events):
self.__draw_event_list__(events)
@ -79,15 +73,12 @@ class DayHeaderDesign (DesignEntity):
xpadding = eventlist_xpadding * self.size[0]
ypadding = eventlist_ypadding * self.size[1]
monthbox_height = (monthbox_ypadding + month_height) * self.size[1]
pos = (box_xpos + box_height + xpadding,
box_ypos + monthbox_height + ypadding)
size = (self.size[0] - pos[0] - self.weather_column_width,
self.size[1] - pos[1] - box_ypos)
pos = (box_xpos + box_height + xpadding, box_ypos + monthbox_height + ypadding)
size = (self.size[0] - pos[0] - self.weather_column_width, self.size[1] - pos[1] - box_ypos)
fontsize = eventlist_static_fontsize
rel_dates = [self.date for _ in range(len(events))]
event_list = SingelDayEventListDesign(
size, events, fontsize, event_prefix_rel_dates=rel_dates)
event_list = SingelDayEventListDesign(size, events, fontsize, event_prefix_rel_dates = rel_dates)
event_list.pos = pos
self.draw_design(event_list)
@ -127,8 +118,7 @@ class DayHeaderDesign (DesignEntity):
pos = (box_ypos, box_ypos + ypadding)
day_text = self.__get_day_text__()
number = TextDesign(size, text=day_text, background_color=numberbox_background_color,
color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment="center")
number = TextDesign(size, text=day_text, background_color=numberbox_background_color, color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment="center")
number.pos = pos
number.mask = False
self.draw_design(number)
@ -141,8 +131,7 @@ class DayHeaderDesign (DesignEntity):
pos = (box_ypos, box_ypos)
week_day_name = self.date.strftime("%A")
week_day = TextDesign(size, text=week_day_name, background_color=numberbox_background_color, color=numberbox_font_color,
fontsize=font_size, horizontalalignment="center", verticalalignment="center", font=weekday_font)
week_day = TextDesign(size, text=week_day_name, background_color=numberbox_background_color, color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment = "center", font=weekday_font)
week_day.pos = pos
week_day.mask = False
self.draw_design(week_day)

View file

@ -24,11 +24,9 @@ dayrow_max_format = 70 / 384
rss_y_padding = 5
crypto_y_padding = 5
class DayListPanel (PanelDesign):
"""Overview that focuses on the current day and
lists following days in a list below."""
def __init__ (self, size):
super(DayListPanel, self).__init__(size)
self.__day_rows__ = []
@ -62,8 +60,7 @@ class DayListPanel (PanelDesign):
pass
def __draw_rss_infoarea__ (self, rss):
height = infoarea_replacedrowscount * \
self.dayrow_size[1] * self.size[1] - rss_y_padding
height = infoarea_replacedrowscount * self.dayrow_size[1] * self.size[1] - rss_y_padding
ypos = self.size[1] - height
size = (self.size[0], height)
pos = (0, ypos)
@ -73,8 +70,7 @@ class DayListPanel (PanelDesign):
self.draw_design(design)
def __draw_crypto_infoarea__ (self, crypto):
height = infoarea_replacedrowscount * \
self.dayrow_size[1] * self.size[1] - crypto_y_padding
height = infoarea_replacedrowscount * self.dayrow_size[1] * self.size[1] - crypto_y_padding
ypos = self.size[1] - height
size = (self.size[0], height)
pos = (0, ypos)
@ -84,8 +80,7 @@ class DayListPanel (PanelDesign):
design.pos = (pos[0], pos[1] + (height - acutal_height))
self.draw_design(design)
replaced_rows = ceil(
acutal_height / (self.dayrow_size[1] * self.size[1]))
replaced_rows = ceil(acutal_height / (self.dayrow_size[1] * self.size[1]))
self.__day_rows__ = self.__day_rows__[:-replaced_rows]
def __draw_day_rows__ (self):
@ -116,8 +111,7 @@ class DayListPanel (PanelDesign):
return following_days
def __draw_today_header__ (self):
header = DayHeaderDesign(self.__abs_co__(
todayheader_size), date.today())
header = DayHeaderDesign(self.__abs_co__(todayheader_size), date.today())
header.pos = self.__abs_co__(todayheader_pos)
self.__day_rows__.append(header)
@ -128,8 +122,7 @@ class DayListPanel (PanelDesign):
for ypos in positions:
line_start = (0, ypos)
line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["fg"], width=lines_thickness)
ImageDraw.Draw(self.__image__).line([line_start, line_end], fill=colors["fg"], width=lines_thickness)
def __finish_panel__(self):
for design in self.__day_rows__:

View file

@ -20,10 +20,8 @@ eventlist_y_fontsize = 0.2
font = fonts["light"]
class DayRowDesign (DesignEntity):
"""Detailed view of a given date."""
def __init__ (self, size, date):
super(DayRowDesign, self).__init__(size)
self.__init_image__()
@ -52,14 +50,12 @@ class DayRowDesign (DesignEntity):
events = calendar.get_day_events(self.date)
rel_dates = [self.date for _ in range(len(events))]
event_list = SingelDayEventListDesign(
size, events, fontsize, event_prefix_rel_dates=rel_dates)
event_list = SingelDayEventListDesign(size, events, fontsize, event_prefix_rel_dates = rel_dates)
event_list.pos = pos
self.draw_design(event_list)
def __draw_forecast__ (self, weather):
forecast = weather.get_forecast_in_days(
self.date.day - datetime.today().day)
forecast = weather.get_forecast_in_days(self.date.day - datetime.today().day)
if forecast is None:
return
@ -78,16 +74,14 @@ class DayRowDesign (DesignEntity):
def __draw_weekday__ (self):
font_size = int(weekday_fontsize * self.size[1])
size = (weekday_y_size[0] * self.size[1],
weekday_y_size[1] * self.size[1])
size = (weekday_y_size[0] * self.size[1], weekday_y_size[1] * self.size[1])
ypos = weekday_ypos * self.size[1]
pos = (0, ypos)
color = self.__get_day_color__()
week_day_name = self.date.strftime("%a")
week_day = TextDesign(size, text=week_day_name, font=font, color=color,
fontsize=font_size, horizontalalignment="center", verticalalignment="top")
week_day = TextDesign(size, text=week_day_name, font=font, color=color, fontsize=font_size, horizontalalignment="center", verticalalignment="top")
week_day.pos = pos
week_day.mask = False
self.draw_design(week_day)
@ -95,15 +89,13 @@ class DayRowDesign (DesignEntity):
def __draw_day_number__ (self):
font_size = int(daynumber_fontsize * self.size[1])
ypadding = daynumber_ypadding * self.size[1]
size = (daynumber_y_size[0] * self.size[1],
daynumber_y_size[1] * self.size[1])
size = (daynumber_y_size[0] * self.size[1], daynumber_y_size[1] * self.size[1])
pos = (0, ypadding)
day_text = self.__get_day_text__()
color = self.__get_day_color__()
number = TextDesign(size, text=day_text, font=font, color=color,
fontsize=font_size, horizontalalignment="center", verticalalignment="bottom")
number = TextDesign(size, text=day_text, font=font, color=color, fontsize=font_size, horizontalalignment="center", verticalalignment="bottom")
number.pos = pos
self.draw_design(number)

View file

@ -18,11 +18,9 @@ infoarea_replaced_hours = 4
infoarea_borderline_width = 1
infoarea_padding = 5
class DayViewPanel (PanelDesign):
"""Overview that focuses on the current day and
shows a timeline split into hours."""
def __init__ (self, size):
super(DayViewPanel, self).__init__(size)
self.shownhours_count = default_shownhours_count
@ -38,8 +36,7 @@ class DayViewPanel (PanelDesign):
self.__header__.add_weather(weather)
def add_calendar (self, calendar):
allday_ev, timed_ev = self.__split_events__(
calendar.get_today_events())
allday_ev, timed_ev = self.__split_events__(calendar.get_today_events())
self.__header__.add_events(allday_ev)
self.__hourlist__.add_events(timed_ev)
@ -63,12 +60,10 @@ class DayViewPanel (PanelDesign):
line_start = (0, ypos)
line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["fg"], width=infoarea_borderline_width)
ImageDraw.Draw(self.__image__).line([ line_start, line_end ], fill=colors["fg"], width=infoarea_borderline_width)
def __draw_rss_feed__(self, rss):
height = infoarea_replaced_hours * \
self.__hourlist__.row_size[1] - infoarea_padding
height = infoarea_replaced_hours * self.__hourlist__.row_size[1] - infoarea_padding
size = (self.size[0], height)
pos = (0, self.size[1] - size[1])
@ -77,8 +72,7 @@ class DayViewPanel (PanelDesign):
self.draw_design(rss)
def __draw_crypto_feed__(self, crypto):
height = infoarea_replaced_hours * \
self.__hourlist__.row_size[1] - infoarea_padding
height = infoarea_replaced_hours * self.__hourlist__.row_size[1] - infoarea_padding
size = (self.size[0], height)
pos = (0, self.size[1] - size[1])
@ -87,9 +81,9 @@ class DayViewPanel (PanelDesign):
crypto.pos = (pos[0], pos[1] + (height - acutal_height))
self.draw_design(crypto)
def __draw_event_list__(self, calendar):
height = infoarea_replaced_hours * \
self.__hourlist__.row_size[1] - infoarea_padding
height = infoarea_replaced_hours * self.__hourlist__.row_size[1] - infoarea_padding
size = (self.size[0], height)
pos = (0, self.size[1] - size[1])
@ -105,15 +99,13 @@ class DayViewPanel (PanelDesign):
self.draw_design(self.__hourlist__)
def __init_header__ (self):
self.__header__ = DayHeaderDesign(
self.__abs_co__(header_size), date.today())
self.__header__ = DayHeaderDesign(self.__abs_co__(header_size), date.today())
self.__header__.pos = (0, 0)
def __init_hourlist__ (self):
start, end = self.__get_current_hour_range__()
size = self.__abs_co__(hourlist_size)
size = (size[0], size[1] * self.shownhours_count /
default_shownhours_count)
size = (size[0], size[1] * self.shownhours_count / default_shownhours_count)
self.__hourlist__ = HourListDesign(size, start, end)
self.__hourlist__.pos = (0, self.__header__.size[1])

View file

@ -3,10 +3,8 @@ from Assets import weathericons
from datetime import datetime
import traceback
class DebugConsole (DebugInterface):
"""Defines concrete console export of debug objects"""
def print_event (self, event):
print("\nCalendarEvent:")
print("---------------------")

View file

@ -1,6 +1,5 @@
class DebugInterface (object):
"""Defines general interface for debugging operations"""
def print_event (self, event):
raise NotImplementedError("Functions needs to be implemented")

View file

@ -3,19 +3,11 @@ from Assets import colors
masking_threshold = 200
class DesignEntity (object):
"""General entity that can be drawn on to a panel design or
other design entities."""
def __init__ (self, size, mask=False, invert_mask=False, color_key=False):
self.size = size
# Are dimensions >= 0?
if self.size[0] < 0:
self.size = (0, self.size[1])
if self.size[1] < 0:
self.size = (self.size[0], 0)
self.pos = (0, 0)
self.mask = mask
self.invert_mask = invert_mask
@ -25,7 +17,7 @@ class DesignEntity (object):
def __init_image__ (self, color = colors["bg"]):
rounded_size = (int(self.size[0]),int(self.size[1]))
self.__image__ = Image.new('RGBA', rounded_size, color=color)
self.__image__ = Image.new('RGB', rounded_size, color=color)
def get_image (self):
if self.__finished_image__ is False:
@ -37,13 +29,11 @@ class DesignEntity (object):
rounded_pos = (int(pos[0]),int(pos[1]))
img_mask = None
if mask:
img_mask = self.__get_mask__(
subimage, invert_mask=invert_mask, color_key=color_key)
img_mask = self.__get_mask__(subimage, invert_mask=invert_mask, color_key=color_key)
self.__image__.paste(subimage, rounded_pos, mask=img_mask)
def draw_design (self, entity):
self.draw(entity.get_image(), entity.pos, entity.mask,
entity.invert_mask, entity.color_key)
self.draw(entity.get_image(), entity.pos, entity.mask, entity.invert_mask, entity.color_key)
def draw_image (self, path, pos):
self.draw(Image.open(path), pos)
@ -54,8 +44,7 @@ class DesignEntity (object):
def __get_mask__ (self, image, invert_mask, color_key):
mask = image.convert('L')
if color_key:
mask = mask.point(lambda p: 0 if p <
masking_threshold else 255, '1').convert('L')
mask = mask.point(lambda p : 0 if p < masking_threshold else 255, '1').convert('L')
if invert_mask:
mask = ImageOps.invert(mask)
return ImageOps.invert(mask)

View file

@ -4,7 +4,6 @@ from settings import language
'''Takes a collection of phrases and outputs the necessary text
according to the language and inserts parameters.'''
def get_text(dictionary, *params):
text = dictionary[default_language]
if language in dictionary.keys():
@ -12,7 +11,6 @@ def get_text(dictionary, *params):
return __insert_params__(text, params)
def __insert_params__(text, params):
index = 0
while '*%d' % index in text and index < len(params):

View file

@ -1,6 +1,5 @@
class DisplayAdapter (object):
"""Interface for CalendarDesign output channels."""
def __init__(self, width, height):
self.width = width
self.height = height

View file

@ -13,14 +13,12 @@ from Assets import path
from LoopTimer import LoopTimer
import locale
from DebugConsole import DebugConsole
from settings import datetime_encoding, language, render_to_display, render_to_file, display_colours, location, api_key, owm_paid_subscription, choosen_design, ical_urls, highlighted_ical_urls, rss_feeds, update_interval, calibrate_hours, crypto_coins, max_loop_count, run_on_hour
from settings import datetime_encoding, language, render_to_display, render_to_file, display_colours, location, api_key, owm_paid_subscription, choosen_design, ical_urls, highlighted_ical_urls, rss_feeds, update_interval, calibrate_hours, crypto_coins
from MonthOvPanel import MonthOvPanel
from DayListPanel import DayListPanel
from DayViewPanel import DayViewPanel
from DayFocusListPanel import DayFocusListPanel
from MonthViewPanel import MonthViewPanel
from AgendaListPanel import AgendaListPanel
from ImageFramePanel import ImageFramePanel
import OwmForecasts
import IcalEvents
import RssParserPosts
@ -28,10 +26,8 @@ import GeckoCrypto
all_locales = locale.locale_alias
if language.lower() not in all_locales.keys():
raise Exception(
"The locale for \"%s\" is currently not supported! If you need support, please open an issue on github." % language)
locale.setlocale(locale.LC_ALL, "%s.%s" % (
all_locales[language.lower()].split('.')[0], datetime_encoding))
raise Exception("The locale for \"%s\" is currently not supported! If you need support, please open an issue on github." % language)
locale.setlocale(locale.LC_ALL, "%s.%s" % (all_locales[language.lower()].split('.')[0], datetime_encoding))
debug = DebugConsole()
output_adapters = []
@ -55,21 +51,15 @@ available_panels = {
"day-list" : DayListPanel,
"month-overview" : MonthOvPanel,
"day-view" : DayViewPanel,
"day-focus-list": DayFocusListPanel,
"agenda-list" : AgendaListPanel,
"month-view": MonthViewPanel,
"image-frame": ImageFramePanel,
"month-view" : MonthViewPanel
}
loop_timer = LoopTimer(
update_interval, run_on_hour=run_on_hour, max_loop_count=max_loop_count)
loop_timer = LoopTimer(update_interval, run_on_hour=True)
"""Main loop starts from here"""
def main():
owm = OwmForecasts.OwmForecasts(
location, api_key, paid_api=owm_paid_subscription)
owm = OwmForecasts.OwmForecasts(location, api_key, paid_api=owm_paid_subscription)
events_cal = IcalEvents.IcalEvents(ical_urls, highlighted_ical_urls)
rss = RssParserPosts.RssParserPosts(rss_feeds)
crypto = GeckoCrypto.GeckoCrypto(crypto_coins)
@ -86,8 +76,7 @@ def main():
if choosen_design in available_panels.keys():
design = available_panels[choosen_design]((epd.width, epd.height))
else:
raise ImportError(
"choosen_design must be valid (" + choosen_design + ")")
raise ImportError("choosen_design must be valid (" + choosen_design + ")")
debug.print_line("Fetching weather information from open weather map")
owm.reload()
@ -109,27 +98,18 @@ def main():
for i, output in enumerate(output_adapters):
try:
output.render(design)
debug.print_line(str(i + 1) + " of " +
str(len(output_adapters)) + " rendered")
debug.print_line(str(i + 1) + " of " + str(len(output_adapters)) + " rendered")
except BaseException as ex:
debug.print_err(ex, "Failed to render output " +
str(i + 1) + " of " + str(len(output_adapters)))
debug.print_err(ex, "Failed to render output " + str(i + 1) + " of " + str(len(output_adapters)))
debug.print_line("=> Finished rendering" + "\n")
loop_timer.end_loop()
if loop_timer.was_last_loop():
debug.print_line("Maximum loop count " +
str(loop_timer.loop_count) + " reached, exiting.")
return
sleep_time = loop_timer.time_until_next()
debug.print_line("This loop took " +
str(loop_timer.get_last_duration()) + " to execute.")
debug.print_line("This loop took " + str(loop_timer.get_last_duration()) + " to execute.")
debug.print_line("Sleeping " + str(sleep_time) + " until next loop.")
sleep(sleep_time.total_seconds())
if __name__ == '__main__':
main()

View file

@ -1,14 +1,10 @@
from BoxDesign import BoxDesign
from PIL import ImageDraw, ImageOps
class EllipseDesign (BoxDesign):
"""Redefinition of ImageDraw.Draw.Rectangle"""
def __init__(self, size, fill=None, outline=None, width=0):
super(EllipseDesign, self).__init__(
size, fill=fill, outline=outline, width=width)
super(EllipseDesign, self).__init__(size, fill=fill, outline=outline, width=width)
def __finish_image__ (self):
ImageDraw.Draw(self.__image__).ellipse(
self.corners, fill=self.fill, outline=self.outline, width=self.width)
ImageDraw.Draw(self.__image__).ellipse(self.corners, fill=self.fill, outline=self.outline, width=self.width)

View file

@ -2,7 +2,6 @@ from EpdAdapter import EpdAdapter, DISPLAY_REFRESH, DATA_START_TRANSMISSION_1
from settings import display_colours
from PIL import Image, ImageDraw
class Epd7in5Adapter (EpdAdapter):
def __init__ (self):
super(Epd7in5Adapter, self).__init__(384, 640)

View file

@ -4,7 +4,6 @@ from PIL import Image, ImageDraw
from math import sqrt, pow
import numpy as np
class Epd7in5bAdapter (EpdAdapter):
def __init__ (self):
super(Epd7in5bAdapter, self).__init__(384, 640)
@ -55,11 +54,9 @@ class Epd7in5bAdapter (EpdAdapter):
if image_buf[x, y, 1] == 255: #White
buf[int((y + x * self.height) / 4)] |= 0xC0 >> (y % 4 * 2)
elif image_buf[x, y, 0] == 0: #Black
buf[int((y + x * self.height) / 4)
] &= ~(0xC0 >> (y % 4 * 2))
buf[int((y + x * self.height) / 4)] &= ~(0xC0 >> (y % 4 * 2))
else: #Red
buf[int((y + x * self.height) / 4)
] &= ~(0xC0 >> (y % 4 * 2))
buf[int((y + x * self.height) / 4)] &= ~(0xC0 >> (y % 4 * 2))
buf[int((y + x * self.height) / 4)] |= 0x40 >> (y % 4 * 2)
return buf

View file

@ -48,10 +48,8 @@ AUTO_MEASUREMENT_VCOM = 0x80
READ_VCOM_VALUE = 0x81
VCM_DC_SETTING = 0x82
class EpdAdapter (DisplayAdapter):
"""Generalized adapter for epd7in5 and epd7in5b"""
def __init__ (self, width, height):
super(EpdAdapter, self).__init__(width, height)
@ -73,7 +71,7 @@ class EpdAdapter (DisplayAdapter):
print('Converting image to data and sending it to the display')
print('This may take a while...' + '\n')
prepared_image = design.get_image().rotate(270, expand=1).convert("RGB")
prepared_image = design.get_image().rotate(270, expand=1)
self.display_frame(self.get_frame_buffer(prepared_image))
# Powering off the E-Paper until the next loop
@ -159,14 +157,9 @@ class EpdAdapter (DisplayAdapter):
self.send_command(DEEP_SLEEP)
self.send_data(0xa5)
def wait_until_idle(self, max_wait_seconds=60):
wait_ms = 100
count = 0
while(self.digital_read(self.busy_pin) == 0 and wait_ms * count < max_wait_seconds * 1000): # 0: busy, 1: idle
self.delay_ms(wait_ms)
count += 1
if wait_ms * count >= max_wait_seconds * 1000:
print("Skipped idle confirmation")
def wait_until_idle (self):
while(self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle
self.delay_ms(100)
def reset (self):
self.digital_write(self.reset_pin, GPIO.LOW) # module reset

View file

@ -5,11 +5,9 @@ from TextFormatter import date_str
from DictionaryMapper import get_text
from Dictionary import more_events
class EventListDesign (DesignEntity):
"""Creates a TableDesign filled with event
begin date and title"""
def __init__ (self, size, events, text_size = defaultfontsize, line_spacing = 0, col_spacing = 10, event_prefix_rel_dates = [], event_prefix_func = None, font_family = None, general_color = colors["fg"], background_color = colors["bg"], highlight_color = colors["hl"], show_more_info = False):
super(EventListDesign, self).__init__(size)
self.events = events
@ -29,18 +27,15 @@ class EventListDesign (DesignEntity):
self.event_prefix_func = lambda x, y : date_str(x.begin_datetime)
def __finish_image__ (self):
self.visible_event_count = int(int(
self.size[1] + self.line_spacing) // (self.line_spacing + int(self.text_size)))
self.visible_event_count = int(int(self.size[1] + self.line_spacing) // (self.line_spacing + int(self.text_size)))
self.__fill_event_matrix__()
col_hori_alignment = [ 'right', 'left' ]
table_design = TableDesign(self.size, background_color=self.background_color, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing,
matrix=self.__event_matrix__, fontsize=self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__)
table_design = TableDesign(self.size, background_color = self.background_color, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing, matrix=self.__event_matrix__, fontsize = self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__)
self.draw_design(table_design)
def __get_formatted_event__ (self, event, index):
rel_date = None if index < 0 or index >= len(
self.event_prefix_rel_dates) else self.event_prefix_rel_dates[index]
rel_date = None if index < 0 or index >= len(self.event_prefix_rel_dates) else self.event_prefix_rel_dates[index]
prefix = self.event_prefix_func(event, rel_date)
return [ prefix, event.title ]

View file

@ -12,7 +12,6 @@ api_price_url = api_url + "simple/price"
price_currency = "usd"
price_currency_sign = "$"
class GeckoCrypto(CryptoInterface):
def __init__(self, coins):
self.coin_ids = coins
@ -27,15 +26,11 @@ class GeckoCrypto(CryptoInterface):
return False
def __get_coins__(self):
if len(self.coin_ids) == 0:
return []
self.__prepare_metadata__()
coins = []
for id in self.coin_ids:
try:
data = urlopen(api_price_url + "?include_24hr_change=true&ids=" +
self.metadata[id]['id'] + "&vs_currencies=" + price_currency).read()
data = urlopen(api_price_url + "?include_24hr_change=true&ids=" + self.metadata[id]['id'] + "&vs_currencies=" + price_currency).read()
dataJSON = json.loads(data.decode('utf-8'))
raw = dataJSON[id][price_currency]
price = math.ceil(raw*100) / 100
@ -64,5 +59,4 @@ class GeckoCrypto(CryptoInterface):
self.metadata = None
data = urlopen(api_metadata_url).read()
dataJSON = json.loads(data.decode('utf-8'))
self.metadata = {coin['id'].lower(
): coin for coin in dataJSON if coin['id'].lower() in self.coin_ids}
self.metadata = { coin['id'].lower() : coin for coin in dataJSON if coin['id'].lower() in self.coin_ids }

View file

@ -19,11 +19,9 @@ currenttimeline_thickness = line_thickness
event_title_font = fonts['bold']
class HourListDesign (DesignEntity):
"""Hours of a day are listed vertically and
resemble a timeline."""
def __init__ (self, size, first_hour = 0, last_hour = 23):
super(HourListDesign, self).__init__(size)
self.first_hour = first_hour
@ -65,8 +63,7 @@ class HourListDesign (DesignEntity):
for _ in range(self.number_columns):
column_events.append(None)
for event in self.events:
column_events = self.__update_columns_events__(
column_events, event)
column_events = self.__update_columns_events__(column_events, event)
self.__draw_event__(event, column_events.index(event))
def __update_columns_events__ (self, column_events, new_event):
@ -94,13 +91,11 @@ class HourListDesign (DesignEntity):
pos = (0, self.__get_ypos_for_time__(hour) + ypadding)
fontsize = size[1] * hour_box_fontsize
txt = TextDesign(size, text=self.__get_hour_text__(
hour), fontsize=fontsize, verticalalignment="bottom", horizontalalignment="center")
txt = TextDesign(size, text=self.__get_hour_text__(hour), fontsize=fontsize, verticalalignment="bottom", horizontalalignment="center")
txt.pos = pos
self.draw_design(txt)
sub = TextDesign((width, subtext_height), text=self.__get_hour_sub_text__(
hour), fontsize=sub_fontsize, verticalalignment="top", horizontalalignment="center")
sub = TextDesign((width, subtext_height), text=self.__get_hour_sub_text__(hour), fontsize=sub_fontsize, verticalalignment="top", horizontalalignment="center")
sub.pos = (0, height + self.__get_ypos_for_time__(hour))
self.draw_design(sub)
@ -109,8 +104,7 @@ class HourListDesign (DesignEntity):
ypos = i * self.row_size[1]
line_start = (0, ypos)
line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["fg"], width=line_thickness)
ImageDraw.Draw(self.__image__).line([ line_start, line_end ], fill=colors["fg"], width=line_thickness)
def __get_hour_sub_text__ (self, hour):
if hours == "12":
@ -151,29 +145,23 @@ class HourListDesign (DesignEntity):
text = event.title
text_color = colors["bg"]
textbox_size = (size[0] - event_title_xpadding,
size[1] - event_title_ypadding)
txt = TextDesign(textbox_size, text=text, font=event_title_font,
fontsize=event_title_fontsize, color=text_color, background_color=box_color, wrap=True)
textbox_size = (size[0] - event_title_xpadding, size[1] - event_title_ypadding)
txt = TextDesign(textbox_size, text = text, font=event_title_font, fontsize=event_title_fontsize, color=text_color, background_color=box_color, wrap=True)
txt.mask = False
txt.pos = (pos[0] + event_title_xpadding,
pos[1] + event_title_ypadding)
txt.pos = (pos[0] + event_title_xpadding, pos[1] + event_title_ypadding)
self.draw_design(txt)
half_ypadding = int(event_title_ypadding / 2)
line_start = (pos[0] + event_title_xpadding, pos[1] + half_ypadding)
line_end = (pos[0] + size[0] - event_title_xpadding,
pos[1] + half_ypadding)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["bg"], width=1)
line_end = (pos[0] + size[0] - event_title_xpadding, pos[1] + half_ypadding)
ImageDraw.Draw(self.__image__).line([ line_start, line_end ], fill=colors["bg"], width=1)
def __get_max_num_simultaneous_events__ (self):
parallelity_count = 1
for index, event in enumerate(self.events):
current_parallelity = 1
# Assumption: Events are ordered chronologically
preceding = self.events[:index]
preceding = self.events[:index] #Assumption: Events are ordered chronologically
for pre_event in preceding:
if self.__are_simultaneous__(pre_event, event):
current_parallelity += 1
@ -195,5 +183,4 @@ class HourListDesign (DesignEntity):
line_start = (0, ypos)
line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["hl"], width=currenttimeline_thickness)
ImageDraw.Draw(self.__image__).line([ line_start, line_end ], fill=colors["hl"], width=currenttimeline_thickness)

View file

@ -6,10 +6,8 @@ import re
from settings import week_starts_on
from urllib.request import urlopen
class IcalEvents(CalendarInterface):
"""Fetches events from ical addresses."""
def __init__(self, urls, highlighted_urls=None):
self.urls = urls
self.highlighted_urls = highlighted_urls
@ -51,7 +49,6 @@ class IcalEvents(CalendarInterface):
ical = Calendar(decode)
for event in ical.events:
cal_event = CalendarEvent()
cal_event.calendar_url = calendar
cal_event.fetch_datetime = datetime.now(timezone.utc)
cal_event.begin_datetime = event.begin.datetime
@ -63,10 +60,8 @@ class IcalEvents(CalendarInterface):
cal_event.allday = event.all_day
cal_event.rrule = self.__extract_rrule__(event)
cal_event.begin_datetime = cal_event.begin_datetime.astimezone(
None)
cal_event.end_datetime = cal_event.end_datetime.astimezone(
None)
cal_event.begin_datetime = cal_event.begin_datetime.astimezone(None)
cal_event.end_datetime = cal_event.end_datetime.astimezone(None)
if cal_event.allday:
cal_event = self.__fix_allday__(cal_event)
@ -84,10 +79,8 @@ class IcalEvents(CalendarInterface):
begin_utc = event.begin_datetime.astimezone(timezone.utc)
end_utc = event.end_datetime.astimezone(timezone.utc)
event.begin_datetime = datetime(
begin_utc.year, begin_utc.month, begin_utc.day, 0, 0, 0, 0, local_tzinfo)
event.end_datetime = datetime(
end_utc.year, end_utc.month, end_utc.day, 0, 0, 0, 0, local_tzinfo) - timedelta(1)
event.begin_datetime = datetime(begin_utc.year, begin_utc.month, begin_utc.day, 0, 0, 0, 0, local_tzinfo)
event.end_datetime = datetime(end_utc.year, end_utc.month, end_utc.day, 0, 0, 0, 0, local_tzinfo) - timedelta(1)
event.duration = event.end_datetime - event.begin_datetime
return event
@ -102,9 +95,7 @@ class IcalEvents(CalendarInterface):
beginAlarmIndex = decode.find(alarm_begin)
if beginAlarmIndex >= 0:
endAlarmIndex = decode.find(alarm_end, beginAlarmIndex)
decode = decode[:beginAlarmIndex] + \
decode[endAlarmIndex +
len(alarm_end) + len(lineseparation):]
decode = decode[:beginAlarmIndex] + decode[endAlarmIndex + len(alarm_end) + len(lineseparation):]
return decode
def __extract_rrule__(self, event):

View file

@ -1,86 +0,0 @@
from DesignEntity import DesignEntity
from Assets import path as application_path
from PIL import Image, ExifTags
class ImageDesign (DesignEntity):
"""Creates a TableDesign filled with rss post
date and title"""
# fill: "none" : original size, "stretch" : strech to fill, "scale" : scale to fill, "border" : scale until one side touches border
def __init__(self, size, path, fill="none", color="RGBA", dither=None):
super(ImageDesign, self).__init__(size)
self.set_path(path)
self.fill = fill
self.color = color
self.dither = dither
def set_path(self, path):
path = path.replace('\\', '/')
if path[0] != '/' and ':' not in path[0:3]:
path = application_path + '/' + path
self.path = path
def __finish_image__(self):
img = Image.open(self.path)
img = img.convert(self.color, dither=self.dither)
img = self.__fix_orientation__(img)
img = self.__resize_image__(img)
pos = self.__get_centered_position__(img)
self.__init_image__("#00000000")
self.draw(img, pos)
def __resize_image__(self, img):
if self.fill is "none":
return img
if self.fill is "stretch":
img = img.resize(self.size, resample=Image.LANCZOS)
if self.fill is "scale":
size = self.size
img_proportions = img.width / img.height
if img_proportions < size[0] / size[1]:
size = (size[0], int(size[0] * (1 / img_proportions)))
else:
size = (int(size[1] * img_proportions), size[1])
img = img.resize(size, resample=Image.LANCZOS)
if self.fill is "border":
size = self.size
img_proportions = img.width / img.height
if img_proportions < size[0] / size[1]:
size = (int(size[1] * img_proportions), size[1])
else:
size = (size[0], int(size[0] * (1 / img_proportions)))
img = img.resize(size, resample=Image.LANCZOS)
return img
def __get_centered_position__(self, img):
screen_size = self.size
img_size = img.size
delta_size = [s - i for s, i in zip(screen_size, img_size)]
delta_center_pos = [s / 2 for s in delta_size]
return delta_center_pos
def __fix_orientation__(self, img):
if "parsed_exif" not in img.info.keys():
return img
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = img.info["parsed_exif"]
if exif[orientation] == 3:
img = img.rotate(180, expand=True)
elif exif[orientation] == 6:
img = img.rotate(270, expand=True)
elif exif[orientation] == 8:
img = img.rotate(90, expand=True)
return img

View file

@ -1,10 +1,8 @@
from DisplayAdapter import DisplayAdapter
from Assets import path
class ImageFileAdapter (DisplayAdapter):
"""Saves design to an image file, can be used for debugging"""
def __init__ (self, file_path = ""):
super(ImageFileAdapter, self).__init__(384, 640)
self.file_path = file_path

View file

@ -1,46 +0,0 @@
from PanelDesign import PanelDesign
from settings import general_settings
from Assets import supported_img_formats, path as application_path
from os import listdir
from os.path import isfile, join
from ImageDesign import ImageDesign
from random import choice
class ImageFramePanel (PanelDesign):
"""Converts the display into a digital frame and
shows a slide show of images, iterating on each update"""
def __init__(self, size):
super(ImageFramePanel, self).__init__(size)
self.overlay_path = self.__complete_path__(
general_settings["overlay-image"])
self.image_folder_path = self.__complete_path__(
general_settings["image-folder"])
self.images = self.__extract_valid_img_paths__()
self.__first_render__()
def __extract_valid_img_paths__(self):
images = []
for file in listdir(self.image_folder_path):
file_path = join(self.image_folder_path, file).replace('\\', '/')
if isfile(file_path) and self.overlay_path != file_path:
if file.split('.')[-1].upper() in supported_img_formats:
images.append(file_path)
return images
def __complete_path__(self, path):
path = path.replace('\\', '/')
if path[0] != '/' and ':' not in path[0:3]:
path = join(application_path, path)
return path
def __first_render__(self):
current_image = choice(self.images)
img = ImageDesign(self.size, current_image, fill="scale", color="1")
self.draw_design(img)
if self.overlay_path != "":
overlay = ImageDesign(self.size, self.overlay_path)
overlay.__finish_image__()
self.__image__.alpha_composite(overlay.__image__)

View file

@ -3,21 +3,17 @@ from datetime import datetime, timedelta
min_sleep_minutes = 0
max_history_entries = 25
class LoopTimer (object):
"""Manages loop times and sleeps until
next loop."""
def __init__(self, loop_interval, run_on_hour=False, max_loop_count=0):
def __init__ (self, loop_interval, run_on_hour = False):
self.interval = int(str(loop_interval))
self.on_hour = run_on_hour
self.loop_history = []
self.loop_count = 0
self.max_loop_count = int(str(max_loop_count))
def begin_loop (self):
begin_time = datetime.now()
print('\n__________Starting new loop [' + str(self.loop_count) + ']__________')
print('\n__________Starting new loop__________')
print('Datetime: ' + str(begin_time) + '\n')
self.__add_beginning__(begin_time)
@ -36,15 +32,6 @@ class LoopTimer (object):
end_time = datetime.now()
self.__add_ending__(end_time)
self.loop_count += 1
while self.loop_count > 86400:
self.loop_count -= 86400
def was_last_loop(self):
if self.max_loop_count == 0:
return False
return self.max_loop_count <= self.loop_count
def get_current (self):
return self.loop_history[-1]

View file

@ -11,11 +11,9 @@ dayhighlightboxsize = (0.143, 0.14)
daynumbersize = daynumberboxsize[0] * 0.45
day_number_ypadding = -0.002
class MonthBlockDesign (DesignEntity):
"""Creates a view containing one week of the month in
one row"""
def __init__(self, size, datetime_month, highlight_today = False):
super(MonthBlockDesign, self).__init__(size, mask=True)
self.month = datetime_month.month
@ -32,12 +30,10 @@ class MonthBlockDesign (DesignEntity):
cal = callib.monthcalendar(self.year, self.month)
for week in cal:
for numbers in week:
self.__draw_day_number__(numbers, self.get_day_pos(
cal.index(week), week.index(numbers)))
self.__draw_day_number__(numbers, self.get_day_pos(cal.index(week), week.index(numbers)))
if self.highlight_today:
self.__draw_highlight_box__(self.__abs_pos__(
dayhighlightboxsize), self.__get_today_box_pos__(), width=3)
self.__draw_highlight_box__(self.__abs_pos__(dayhighlightboxsize), self.__get_today_box_pos__(), width=3)
def __draw_highlight_box__ (self, size, pos, color=colors["fg"], width=1):
design = BoxDesign(size, outline=color, width = width)
@ -47,8 +43,7 @@ class MonthBlockDesign (DesignEntity):
def __draw_day_number__ (self, number, pos):
if number <= 0:
return
txt = TextDesign(self.__abs_pos__(daynumberboxsize), fontsize=daynumbersize *
self.size[0], text=str(number), verticalalignment="center", horizontalalignment="center")
txt = TextDesign(self.__abs_pos__(daynumberboxsize), fontsize=daynumbersize * self.size[0], text=str(number), verticalalignment="center", horizontalalignment="center")
txt.pos = (pos[0], pos[1] + day_number_ypadding * self.size[1])
self.draw_design(txt)
@ -59,8 +54,7 @@ class MonthBlockDesign (DesignEntity):
return (int(rel_pos[0] + day_of_week * partialwidth), int(rel_pos[1] + week_in_month * partialheight))
def __get_today_box_pos__ (self):
x, y = self.get_day_pos(self.__get_week_of_month__(
datetime.now()), self.__get_day_of_week__(datetime.now()))
x, y = self.get_day_pos(self.__get_week_of_month__(datetime.now()), self.__get_day_of_week__(datetime.now()))
return (x, int(y + (self.__abs_pos__(daynumberboxsize)[1] - self.__abs_pos__(dayhighlightboxsize)[1]) / 2))
def __get_week_of_month__ (self, date):
@ -80,8 +74,7 @@ class MonthBlockDesign (DesignEntity):
weekdays = []
for i in range(7):
weekdays.append(
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
weekdays.append((datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
return weekdays

View file

@ -29,11 +29,9 @@ weekdaytextpadding = -0.001
weekrownameboxsize = (0.143, 0.044)
eventcirclehorizontalsize = 0.100
class MonthOvPanel (PanelDesign):
"""Overview that focuses on the current month and
some additional information in the bottom."""
def __init__ (self, size):
super(MonthOvPanel, self).__init__(size)
self.weather_header_height = 0
@ -54,8 +52,7 @@ class MonthOvPanel (PanelDesign):
if general_settings["weather-info"]:
self.__draw_seperator__()
self.month_block = MonthBlockDesign(self.__abs_pos__(
monthovsize), datetime.now(), highlight_today=True)
self.month_block = MonthBlockDesign(self.__abs_pos__(monthovsize), datetime.now(), highlight_today = True)
pos = self.__abs_pos__(monthovposition)
pos = (pos[0], pos[1] + self.weather_header_height)
self.month_block.pos = pos
@ -64,8 +61,7 @@ class MonthOvPanel (PanelDesign):
def add_weather (self, weather):
if general_settings["weather-info"] == False:
return
self.draw_design(WeatherHeaderDesign(
self.__abs_pos__(weatherheadersize), weather))
self.draw_design(WeatherHeaderDesign(self.__abs_pos__(weatherheadersize), weather))
def add_rssfeed (self, rss):
if general_settings["info-area"] is "rss":
@ -80,8 +76,7 @@ class MonthOvPanel (PanelDesign):
def add_calendar (self, calendar):
if general_settings["highlight-event-days"]:
month_events = list(set([(event.begin_datetime.day, event.begin_datetime.month,
event.begin_datetime.year) for event in calendar.get_month_events()]))
month_events = list(set([ (event.begin_datetime.day, event.begin_datetime.month, event.begin_datetime.year) for event in calendar.get_month_events()]))
for event in month_events:
self.__draw_highlight_event_day__(event)
@ -91,35 +86,29 @@ class MonthOvPanel (PanelDesign):
def __draw_rss_post_list_to_bottom__ (self, rss):
month_pos = self.__abs_pos__(monthovposition)
month_height = self.month_block.get_real_height()
size = (self.size[0], self.size[1] - (month_pos[1] +
month_height + self.weather_header_height))
size = (self.size[0], self.size[1] - (month_pos[1] + month_height + self.weather_header_height))
info_list = RssPostListDesign(size, rss)
info_list.pos = (
int(month_pos[0]), month_pos[1] + month_height + self.weather_header_height)
info_list.pos = (int(month_pos[0]), month_pos[1] + month_height + self.weather_header_height)
self.draw_design(info_list)
def __draw_crypto_post_list_to_bottom__ (self, crypto):
month_pos = self.__abs_pos__(monthovposition)
month_height = self.month_block.get_real_height()
size = (self.size[0], self.size[1] - (month_pos[1] +
month_height + self.weather_header_height))
size = (self.size[0], self.size[1] - (month_pos[1] + month_height + self.weather_header_height))
info_list = CryptoListDesign(size, crypto)
list_height = info_list.get_estimated_height()
info_list.pos = (int(month_pos[0]), month_pos[1] + month_height +
self.weather_header_height + (size[1] - list_height))
info_list.pos = (int(month_pos[0]), month_pos[1] + month_height + self.weather_header_height + (size[1] - list_height))
self.draw_design(info_list)
def __draw_event_list_to_bottom__ (self, calendar):
month_pos = self.__abs_pos__(monthovposition)
month_height = self.month_block.get_real_height()
size = (self.size[0], self.size[1] - (month_pos[1] +
month_height + self.weather_header_height))
size = (self.size[0], self.size[1] - (month_pos[1] + month_height + self.weather_header_height))
events = calendar.get_upcoming_events()
info_list = EventListDesign(size, events)
info_list.pos = (int(month_pos[0]), int(
month_pos[1] + month_height + self.weather_header_height))
info_list.pos = (int(month_pos[0]), int(month_pos[1] + month_height + self.weather_header_height))
self.draw_design(info_list)
def __draw_highlight_event_day__ (self, date):
@ -128,12 +117,9 @@ class MonthOvPanel (PanelDesign):
side_length = int(eventcirclehorizontalsize * self.size[0])
circle_size = (side_length,side_length)
pos = self.month_block.get_day_pos(cur_date.isocalendar(
)[1] - first_month_week, self.__get_day_of_week__(cur_date), rel_pos=self.month_block.pos)
place_size = (self.month_block.size[0] * daynumberboxsize[0],
self.month_block.size[1] * daynumberboxsize[1])
pos = (int(pos[0] + (place_size[0] - circle_size[0]) / 2),
int(pos[1] + (place_size[1] - circle_size[1]) / 2))
pos = self.month_block.get_day_pos(cur_date.isocalendar()[1] - first_month_week, self.__get_day_of_week__(cur_date), rel_pos = self.month_block.pos)
place_size = (self.month_block.size[0] * daynumberboxsize[0], self.month_block.size[1] * daynumberboxsize[1])
pos = (int(pos[0] + (place_size[0] - circle_size[0]) / 2), int(pos[1] + (place_size[1] - circle_size[1]) / 2))
self.__draw_highlight_circle__(circle_size, pos, 'red', width=2)
def __abs_pos__ (self, pos, size = None):
@ -143,28 +129,24 @@ class MonthOvPanel (PanelDesign):
def __draw_seperator__ (self):
"""Draw a line seperating the weather and Calendar section"""
ImageDraw.Draw(self.__image__).line([self.__abs_pos__(
seperatorplace), self.__abs_pos__((1, seperatorplace[1]))], fill='red', width=5)
ImageDraw.Draw(self.__image__).line([ self.__abs_pos__(seperatorplace), self.__abs_pos__((1, seperatorplace[1])) ], fill='red', width=5)
def __draw_month_name__ (self):
"""Draw the icon with the current month's name"""
month = datetime.now().strftime("%B")
txt = TextDesign(self.__abs_pos__(monthboxsize), fontsize=monthtextsize *
self.size[1], text=month, verticalalignment="center", horizontalalignment="center")
txt = TextDesign(self.__abs_pos__(monthboxsize), fontsize=monthtextsize * self.size[1], text=month, verticalalignment="center", horizontalalignment="center")
pos = self.__abs_pos__(monthplace)
txt.pos = (pos[0], pos[1] + self.weather_header_height)
self.draw_design(txt)
def __draw_week_row__ (self):
for day_of_week, day in enumerate(self.__week_days__):
txt = TextDesign(self.__abs_pos__(weekrownameboxsize), fontsize=weekdaytextsize *
self.size[1], text=str(day), verticalalignment="center", horizontalalignment="center")
txt = TextDesign(self.__abs_pos__(weekrownameboxsize), fontsize=weekdaytextsize * self.size[1], text=str(day), verticalalignment="center", horizontalalignment="center")
pos = self.__get_week_day_pos__(day_of_week)
txt.pos = (pos[0], pos[1] + weekdaytextpadding * self.size[1])
self.draw_design(txt)
self.__draw_highlight_box__(self.__abs_pos__(weekrownameboxsize), self.__get_week_day_pos__(
self.__get_day_of_week__(datetime.now())), width=1)
self.__draw_highlight_box__(self.__abs_pos__(weekrownameboxsize), self.__get_week_day_pos__(self.__get_day_of_week__(datetime.now())), width=1)
def __get_week_day_pos__ (self, day_of_week):
maxwidth, _ = self.__abs_pos__(monthovsize)
@ -190,8 +172,7 @@ class MonthOvPanel (PanelDesign):
weekdays = []
for i in range(7):
weekdays.append(
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
weekdays.append((datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
return weekdays

View file

@ -15,11 +15,9 @@ info_height = 0.25
info_padding = 5
seperator_width = line_thickness
class MonthViewPanel (PanelDesign):
"""Displays a grid of the day of the current month
with detailed event descriptions."""
def __init__(self, size, month = None, year = None):
super(MonthViewPanel, self).__init__(size)
self.day_table = []
@ -89,8 +87,7 @@ class MonthViewPanel (PanelDesign):
pos = (0, self.size[1] - size[1])
crypto = CryptoListDesign(size, crypto)
crypto.pos = (pos[0], pos[1] + (size[1] -
crypto.get_estimated_height()))
crypto.pos = (pos[0],pos[1] + (size[1] - crypto.get_estimated_height()))
self.draw_design(crypto)
def __finish_panel__(self):
@ -105,8 +102,7 @@ class MonthViewPanel (PanelDesign):
self.draw_design(table)
def __draw_seperator__ (self, height, color):
ImageDraw.Draw(self.__image__).line(
[(0, height * self.size[1]), (self.size[0], height * self.size[1])], fill=color, width=seperator_width)
ImageDraw.Draw(self.__image__).line([ (0, height * self.size[1]), (self.size[0], height * self.size[1]) ], fill=color, width=seperator_width)
def __init_day_boxes__(self):
if week_starts_on == "Monday":
@ -124,8 +120,7 @@ class MonthViewPanel (PanelDesign):
if day == None or day == 0:
return None
design = DayBoxDesign(self.day_box_size, date(
self.year, self.month, int(day)))
design = DayBoxDesign(self.day_box_size, date(self.year, self.month, int(day)))
return design

View file

@ -5,17 +5,14 @@ from datetime import datetime
from settings import units, language
from Translator import translate
class OwmForecasts (WeatherInterface):
"""Fetches weather through the Openweathermap-api."""
def __init__ (self, location, api_key, paid_api=False):
self.subscription = "pro" if paid_api else None
self.api_key = api_key
self.units = units
self.location = location
self.api = pyowm.OWM(
self.api_key, subscription_type=self.subscription, language=language)
self.api = pyowm.OWM(self.api_key, subscription_type=self.subscription, language=language)
def is_available (self):
try:
@ -61,41 +58,30 @@ class OwmForecasts (WeatherInterface):
forecast_object.units = self.units
forecast_object.fetch_datetime = datetime.now()
forecast_object.location = location
forecast_object.datetime = weather.get_reference_time(
timeformat='date')
forecast_object.datetime = weather.get_reference_time(timeformat='date')
forecast_object.icon = weather.get_weather_icon_name()
forecast_object.air_humidity = str(weather.get_humidity())
forecast_object.clouds = str(weather.get_clouds())
forecast_object.short_description = translate(
str(weather.get_status()))
forecast_object.detailed_description = str(
weather.get_detailed_status())
forecast_object.short_description = translate(str(weather.get_status()))
forecast_object.detailed_description = str(weather.get_detailed_status())
forecast_object.air_pressure = str(weather.get_pressure()['press'])
if 'deg' in weather.get_wind().keys():
forecast_object.wind_deg = str(int(weather.get_wind()['deg']))
if forecast_object.units == "metric":
forecast_object.air_temperature = str(
int(weather.get_temperature(unit='celsius')['temp']))
forecast_object.wind_speed = str(
int(weather.get_wind()['speed'])) # kmh
forecast_object.air_temperature = str(int(weather.get_temperature(unit='celsius')['temp']))
forecast_object.wind_speed = str(int(weather.get_wind()['speed'])) #kmh
if forecast_object.units == "aviation":
forecast_object.air_temperature = str(
int(weather.get_temperature(unit='celsius')['temp']))
forecast_object.wind_speed = str(
int(weather.get_wind()['speed'] * 1.94384)) # knots
forecast_object.air_temperature = str(int(weather.get_temperature(unit='celsius')['temp']))
forecast_object.wind_speed = str(int(weather.get_wind()['speed'] * 1.94384)) #knots
if forecast_object.units == "imperial":
forecast_object.air_temperature = str(
int(weather.get_temperature('fahrenheit')['temp']))
forecast_object.wind_speed = str(
int(weather.get_wind()['speed'] * 0.621)) # mph
forecast_object.air_temperature = str(int(weather.get_temperature('fahrenheit')['temp']))
forecast_object.wind_speed = str(int(weather.get_wind()['speed'] * 0.621)) #mph
forecast_object.sunrise = datetime.fromtimestamp(
int(weather.get_sunrise_time(timeformat='unix')))
forecast_object.sunset = datetime.fromtimestamp(
int(weather.get_sunset_time(timeformat='unix')))
forecast_object.sunrise = datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix')))
forecast_object.sunset = datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix')))
return forecast_object

View file

@ -3,28 +3,26 @@ from TechnicalDataDesign import TechnicalDataDesign
from settings import print_technical_data
from datetime import datetime
class PanelDesign (DesignEntity):
"""Defined general interface for panel designs."""
def __init__ (self, size):
super(PanelDesign, self).__init__(size)
self.start_timestamp = datetime.now()
def add_weather (self, weather):
pass
raise NotImplementedError("Function needs to be implemented")
def add_calendar (self, calendar):
pass
raise NotImplementedError("Function needs to be implemented")
def add_rssfeed (self, rss):
pass
raise NotImplementedError("Function needs to be implemented")
def add_tasks (self, tasks):
pass
raise NotImplementedError("Function needs to be implemented")
def add_crypto (self, crypto):
pass
raise NotImplementedError("Function needs to be implemented")
def __finish_panel__(self):
pass
@ -33,7 +31,6 @@ class PanelDesign (DesignEntity):
self.__finish_panel__()
if print_technical_data:
td = TechnicalDataDesign(
self.size, self.start_timestamp, datetime.now())
td = TechnicalDataDesign(self.size, self.start_timestamp, datetime.now())
td.mask = True
self.draw_design(td)

View file

@ -1,10 +1,8 @@
from DataSourceInterface import DataSourceInterface
from datetime import datetime, timezone, timedelta
class RssInterface(DataSourceInterface):
"""Interface for fetching and processing rss post information."""
def __init__(self):
self.loaded_posts = []

View file

@ -6,10 +6,8 @@ from urllib.request import urlopen
max_range_days = 14
class RssParserPosts (RssInterface):
"""Fetches posts from url-addresses via rss parser."""
def __init__(self, urls):
self.urls = urls
super(RssParserPosts, self).__init__()
@ -55,3 +53,4 @@ class RssParserPosts (RssInterface):
start_index = link.find('://') + 3
end_index = link[start_index:].find('/') + start_index
return link[start_index : end_index]

View file

@ -1,6 +1,5 @@
class RssPost(object):
"""Defines a rss post, independent of any implementation"""
def __init__ (self):
self.title = None
self.description = None

View file

@ -2,11 +2,9 @@ from DesignEntity import DesignEntity
from TableDesign import TableDesign
from Assets import defaultfontsize
class RssPostListDesign (DesignEntity):
"""Creates a TableDesign filled with rss post
date and title"""
def __init__ (self, size, rssfeed, text_size = defaultfontsize):
super(RssPostListDesign, self).__init__(size)
self.rssfeed = rssfeed
@ -16,8 +14,7 @@ class RssPostListDesign (DesignEntity):
def __finish_image__ (self):
self.__fill_post_matrix__()
table_design = TableDesign(self.size, line_spacing=2, col_spacing=3, matrix=self.__post_matrix__,
fontsize=self.text_size, mask=False, wrap=True, truncate_rows=True)
table_design = TableDesign(self.size, line_spacing=2, col_spacing=3, matrix=self.__post_matrix__, fontsize = self.text_size, mask=False, wrap=True, truncate_rows=True)
self.draw_design(table_design)
def __get_formatted_post__ (self, post):

View file

@ -5,11 +5,8 @@ from TextFormatter import event_prefix_str_sum
font = fonts["regular"]
class SingelDayEventListDesign (EventListDesign):
"""Specialized event list for day list design."""
def __init__ (self, size, events, font_size = defaultfontsize, line_spacing=0, event_prefix_rel_dates = [], col_spacing=5, general_color=colors["fg"], background_color=colors["bg"], highlight_color=colors["hl"]):
def prefix_func(x, rel_date): return event_prefix_str_sum(x, rel_date)
super().__init__(size, events, text_size=font_size, line_spacing=line_spacing, col_spacing=col_spacing, event_prefix_rel_dates=event_prefix_rel_dates,
event_prefix_func=prefix_func, font_family=font, show_more_info=True, general_color=general_color, background_color=background_color, highlight_color=highlight_color)
prefix_func = lambda x, rel_date : event_prefix_str_sum(x, rel_date)
super().__init__(size, events, text_size=font_size, line_spacing=line_spacing, col_spacing=col_spacing, event_prefix_rel_dates = event_prefix_rel_dates, event_prefix_func=prefix_func, font_family=font, show_more_info=True, general_color=general_color, background_color=background_color, highlight_color = highlight_color)

View file

@ -10,14 +10,11 @@ default_props = {
"font-size" : defaultfontsize
}
class TableDesign (TextDesign):
"""Gets a matrix with text or designs that is than
displayed in a table without borders."""
def __init__ (self, size, matrix, max_col_size = None, max_row_size = None, font = None, fontsize = defaultfontsize, column_horizontal_alignments = [], mask = True, line_spacing = 0, col_spacing = 0, truncate_rows = True, truncate_cols = True, wrap = False, truncate_text=True, truncate_suffix="...", cell_properties=None, background_color = colors["bg"]):
super(TableDesign, self).__init__(
size, font=font, fontsize=fontsize, mask=mask)
super(TableDesign, self).__init__(size, font=font, fontsize=fontsize, mask=mask)
self.__init_image__(background_color)
self.matrix = matrix
self.max_col_size = max_col_size
@ -88,11 +85,9 @@ class TableDesign (TextDesign):
return size
elif type(content) == str:
font = self.__get_font__()
# get width of text in that row/col
width = font.getsize(self.matrix[r][c])[0]
width = font.getsize(self.matrix[r][c])[0] #get width of text in that row/col
if self.wrap and self.max_col_size != None:
content = wrap_text_with_font(
content, self.max_col_size[c], font)
content = wrap_text_with_font(content, self.max_col_size[c], font)
line_count = content.count('\n') + 1
height = font.font.height * line_count #get height of text in that col/row
size = (width, height)
@ -128,8 +123,7 @@ class TableDesign (TextDesign):
bg_color = self.__get_cell_prop__(row, col, "background_color")
fontsize = self.__get_cell_prop__(row, col, "font-size")
design = TextDesign(size, text=self.matrix[row][col], font=self.font_family, color=color, background_color=bg_color, fontsize=fontsize,
horizontalalignment=self.__get_col_hori_alignment__(col), wrap=self.wrap, truncate=self.truncate_text, truncate_suffix=self.truncate_suffix)
design = TextDesign(size, text=self.matrix[row][col], font=self.font_family, color=color, background_color=bg_color, fontsize=fontsize, horizontalalignment=self.__get_col_hori_alignment__(col), wrap=self.wrap, truncate=self.truncate_text, truncate_suffix=self.truncate_suffix)
design.pos = pos
design.mask = False
self.draw_design(design)

View file

@ -4,10 +4,8 @@ from Assets import colors
font_size = 20
class TechnicalDataDesign(DesignEntity):
'''Prints data about the current loop ontop of the panel'''
def __init__(self, size, start, stop):
super(TechnicalDataDesign, self).__init__(size, mask = True)
self.start = start

View file

@ -5,11 +5,9 @@ from TextWraper import wrap_text_with_font
truncateerror_fontsize = 0.5
class TextDesign (DesignEntity):
"""Object that manages all information relevant to text
and prints it to an image"""
def __init__ (self, size, color=colors["fg"], background_color=colors["bg"], font = None, fontsize = defaultfontsize, text = "", horizontalalignment = "left", verticalalignment = "top", mask=True, truncate=False, truncate_suffix = '...', wrap=False):
super(TextDesign, self).__init__(size, mask = mask)
if font is None:
@ -38,15 +36,12 @@ class TextDesign (DesignEntity):
if self.wrap:
self.__wrap_text__()
pos = self.__pos_from_alignment__()
ImageDraw.Draw(self.__image__).text(
pos, self.text, fill=self.color, font=self.__font__)
ImageDraw.Draw(self.__image__).text(pos, self.text, fill=self.color, font=self.__font__)
def __truncate_text__ (self):
# does not need truncating
if self.__font__.getsize_multiline(self.text)[0] <= self.size[0]:
if self.__font__.getsize_multiline(self.text)[0] <= self.size[0]: #does not need truncating
return
suffix_length = self.__font__.getsize_multiline(
self.truncate_suffix)[0]
suffix_length = self.__font__.getsize_multiline(self.truncate_suffix)[0]
while len(self.text) > 1 and self.__font__.getsize_multiline(self.text)[0] + suffix_length >= self.size[0]:
self.text = self.text[0:-1]
self.text = self.text.rstrip(' ')

View file

@ -12,7 +12,6 @@ until_character = ' - '
allday_character = ""
multiday_character = allday_character + allday_character
def time_str (dt):
if hours is "12":
return dt.strftime("%I:%M%p")
@ -21,7 +20,6 @@ def time_str(dt):
else:
return str(dt)
def event_prefix_str_md_dif (event, relative_date=None):
if relative_date is None:
relative_date = event.begin_datetime.date()
@ -44,7 +42,6 @@ def event_prefix_str_md_dif(event, relative_date=None):
event.allday = True
return multiday_end_character + event_time_summary(event) + multiday_begin_character
def event_prefix_str (event, relative_date=None):
if relative_date is None:
relative_date = event.begin_datetime.date()
@ -54,7 +51,6 @@ def event_prefix_str(event, relative_date=None):
else:
return event_time_detailed(event)
def event_prefix_str_sum (event, relative_date=None):
if relative_date is None:
relative_date = event.begin_datetime.date()
@ -64,31 +60,26 @@ def event_prefix_str_sum(event, relative_date=None):
else:
return event_time_summary(event)
def event_time_summary (event):
if event.allday:
return allday_character
else:
return time_str(event.begin_datetime)
def event_time_detailed (event):
if event.allday:
return get_text(allday_events)
else:
return time_str(event.begin_datetime) + until_character + time_str(event.end_datetime)
def date_str(dt):
return remove_leading_zero(dt.strftime('%d. %b'))
def remove_leading_zero (text):
while text[0] is '0':
text = text[1:]
return text
def date_summary_str(dt):
day = remove_leading_zero(dt.strftime("%d"))
if language is "en":
@ -98,13 +89,11 @@ def date_summary_str(dt):
else:
return dt.strftime('%a ' + day + '. %b')
def __equal__(dt1, dt2):
return dt1.day == dt2.day and \
dt1.month == dt2.month and \
dt1.year == dt2.year
def __day_duration__(dt):
day_begin = datetime(dt.year, dt.month, dt.day, 0, 0, 0, 0, timezone.utc)
return dt - day_begin

View file

@ -1,11 +1,10 @@
from Assets import path, defaultfont
from PIL import ImageFont
def wrap_text_with_font (text, width, font):
words = text.split(' ')
result = ""
for word in words:
for index, word in enumerate(words):
until_current = (result + " " + word).strip()
txt_width, _ = font.getsize_multiline(until_current)
if txt_width > width:
@ -15,6 +14,5 @@ def wrap_text_with_font(text, width, font):
result += word
return result.strip()
def wrap_text (text, width, font_size, font_family = defaultfont):
return wrap_text_with_font(text, width, ImageFont.truetype(path + font_family, int(font_size)))

View file

@ -4,7 +4,6 @@ from settings import language
'''Looks up a phrase in a given dictionary-collection
and returns the translated phrase'''
def translate(phrase, target_lang = language, dictionary_collection = dictionary_collection) :
dictionary = find_dictionary(phrase, dictionary_collection)
@ -18,7 +17,6 @@ def translate(phrase, target_lang=language, dictionary_collection=dictionary_col
else:
return dictionary[default_language]
def find_dictionary(phrase, dictionary_collection = dictionary_collection):
for dictionary in dictionary_collection:
if phrase in dictionary.values():

View file

@ -13,10 +13,8 @@ info_yresize = -0.05
fontsize_static = defaultfontsize
max_symbol_y_width = 0.15
class WeatherColumnDesign (DesignEntity):
"""Displays weather information in a column"""
def __init__ (self, size, forecast):
super().__init__(size)
self.forecast = forecast
@ -30,8 +28,7 @@ class WeatherColumnDesign (DesignEntity):
self.__draw_infos__(self.forecast)
def __draw_infos__ (self, forecast):
temperature = forecast.air_temperature + \
" " + self.__get_unit__(("°C", "°F"))
temperature = forecast.air_temperature + " " + self.__get_unit__(("°C", "°F"))
humidity = forecast.air_humidity + "%"
if forecast.units== "aviation":
if forecast.wind_deg == None:
@ -41,15 +38,11 @@ class WeatherColumnDesign (DesignEntity):
elif len(forecast.wind_deg)==2:
forecast.wind_deg = "0" + forecast.wind_deg
if int(forecast.wind_speed)<10:
windspeed = forecast.wind_deg + "@" + "0" + forecast.wind_speed + \
self.__get_unit__(
("", "")) # added degrees, if wind<10 add a 0 to make two digit
windspeed = forecast.wind_deg + "@" + "0" + forecast.wind_speed + self.__get_unit__(("", "")) #added degrees, if wind<10 add a 0 to make two digit
else:
windspeed = forecast.wind_deg + "@" + forecast.wind_speed + \
self.__get_unit__(("", "")) # added degrees
windspeed = forecast.wind_deg + "@" + forecast.wind_speed + self.__get_unit__(("", "")) #added degrees
else:
windspeed = forecast.wind_speed + " " + \
self.__get_unit__(("km/h", "mph"))
windspeed = forecast.wind_speed + " " + self.__get_unit__(("km/h", "mph"))
numbers_list = [ [ forecast.short_description ],
[ temperature ],
@ -58,13 +51,10 @@ class WeatherColumnDesign (DesignEntity):
ypos = info_x_ypos * self.size[0]
pos = (0, ypos)
size = (self.size[0], self.size[1] +
info_yresize * self.size[1] - pos[1])
line_spacing = (size[1] - len(numbers_list) *
fontsize_static) / (len(numbers_list) + 1)
size = (self.size[0], self.size[1] + info_yresize * self.size[1] - pos[1])
line_spacing = (size[1] - len(numbers_list) * fontsize_static) / (len(numbers_list) + 1)
table = TableDesign(size, numbers_list, fontsize=fontsize_static, line_spacing=line_spacing, column_horizontal_alignments=[
"center"], max_col_size=[size[0]], truncate_text=False, truncate_rows=False)
table = TableDesign(size, numbers_list, fontsize=fontsize_static, line_spacing=line_spacing, column_horizontal_alignments=[ "center" ], max_col_size=[ size[0] ], truncate_text=False, truncate_rows=False)
table.pos = pos
self.draw_design(table)
@ -75,8 +65,7 @@ class WeatherColumnDesign (DesignEntity):
ypos = icon_x_ypos * self.size[0]
pos = (xpos, ypos)
self.__draw_resized_path_at__(
wpath + weathericons[icon_id] + ".jpeg", pos, size)
self.__draw_resized_path_at__(wpath + weathericons[icon_id] + ".jpeg", pos, size)
def __draw_no_response__ (self):
width = int(icon_width * self.size[0])
@ -96,6 +85,7 @@ class WeatherColumnDesign (DesignEntity):
resized_img = img.resize(size, resample=Image.LANCZOS)
self.draw(resized_img, pos)
def __get_unit__ (self, tuple):
if self.forecast.units == "metric" or self.forecast.units == "aviation":
return tuple[0]

View file

@ -1,6 +1,5 @@
class WeatherForecast (object):
"""Defines a weather forecast, independent of any implementation"""
def __init__ (self):
self.air_temperature = None
self.air_pressure = None

View file

@ -10,10 +10,8 @@ windiconspace = (0.206, 0)
sunriseplace = (0.55, 0)
sunsetplace = (0.55, 0.486)
class WeatherHeaderDesign (DesignEntity):
"""Defines a top area that displays basic weather information"""
def __init__ (self, size, weather):
super(WeatherHeaderDesign, self).__init__(size)
self.__weather__ = weather
@ -30,8 +28,7 @@ class WeatherHeaderDesign (DesignEntity):
self.__render_missing_connection__()
return
temperature = cur_weather.air_temperature + \
" " + self.__get_unit__(("°C", "°F"))
temperature = cur_weather.air_temperature + " " + self.__get_unit__(("°C", "°F"))
if units== "aviation": #pick up aviation
if cur_weather.wind_deg == None:
cur_weather.wind_deg = ""
@ -40,35 +37,25 @@ class WeatherHeaderDesign (DesignEntity):
elif len(cur_weather.wind_deg)==2:
cur_weather.wind_deg = "0" + cur_weather.wind_deg
if int(cur_weather.wind_speed)<10:
windspeed = cur_weather.wind_deg + "@" + "0" + cur_weather.wind_speed + \
self.__get_unit__(
("", "")) # added degrees, if wind<10 add a 0 to make two digit
windspeed = cur_weather.wind_deg + "@" + "0" + cur_weather.wind_speed + self.__get_unit__(("", "")) #added degrees, if wind<10 add a 0 to make two digit
else:
windspeed = cur_weather.wind_deg + "@" + cur_weather.wind_speed + \
self.__get_unit__(("", "")) # added degrees
windspeed = cur_weather.wind_deg + "@" + cur_weather.wind_speed + self.__get_unit__(("", "")) #added degrees
else:
windspeed = cur_weather.wind_speed + " " + \
self.__get_unit__(("km/h", "mph"))
windspeed = cur_weather.wind_speed + " " + self.__get_unit__(("km/h", "mph"))
self.__draw_text__(temperature, self.__abs_pos__((0.87, 0)), (50,35))
self.__draw_text__(windspeed, self.__abs_pos__(
(0.297, 0.05)), (100, 35))
self.__draw_text__(self.__get_time__(cur_weather.sunrise),
self.__abs_pos__((0.64, 0)), (50, 35))
self.__draw_text__(self.__get_time__(cur_weather.sunset),
self.__abs_pos__((0.64, 0.486)), (50, 35))
self.__draw_text__(cur_weather.air_humidity + " %",
self.__abs_pos__((0.87, 0.486)), (50, 35))
self.__draw_text__(cur_weather.short_description,
self.__abs_pos__((0.182, 0.486)), (144, 35))
self.__draw_text__(windspeed, self.__abs_pos__((0.297, 0.05)), (100,35))
self.__draw_text__(self.__get_time__(cur_weather.sunrise), self.__abs_pos__((0.64,0)), (50,35))
self.__draw_text__(self.__get_time__(cur_weather.sunset), self.__abs_pos__((0.64,0.486)), (50,35))
self.__draw_text__(cur_weather.air_humidity + " %", self.__abs_pos__((0.87,0.486)), (50,35))
self.__draw_text__(cur_weather.short_description, self.__abs_pos__((0.182,0.486)), (144,35))
self.draw(windicon, self.__abs_pos__(windiconspace))
self.draw(sunseticon, self.__abs_pos__(sunsetplace))
self.draw(sunriseicon, self.__abs_pos__(sunriseplace))
self.draw(humicon, self.__abs_pos__(humplace))
self.draw(tempicon, self.__abs_pos__(tempplace))
self.draw_image(
wpath + weathericons[cur_weather.icon] + '.jpeg', self.__abs_pos__(wiconplace))
self.draw_image(wpath + weathericons[cur_weather.icon] + '.jpeg', self.__abs_pos__(wiconplace))
def __render_missing_connection__ (self):
self.draw(no_response, self.__abs_pos__(wiconplace))
@ -77,8 +64,7 @@ class WeatherHeaderDesign (DesignEntity):
return (int(pos[0] * self.size[0]), int(pos[1] * self.size[1]))
def __draw_text__ (self, text, pos, size):
txt = TextDesign(size, fontsize=18, text=text,
verticalalignment="center", horizontalalignment="center")
txt = TextDesign(size, fontsize=18, text=text, verticalalignment="center", horizontalalignment="center")
txt.pos = pos
self.draw_design(txt)

View file

@ -1,9 +1,7 @@
from DataSourceInterface import DataSourceInterface
class WeatherInterface (DataSourceInterface):
"""Interface for fetching and processing weather forecast information."""
def get_forecast_in_days (self, offset_by_days, location=None):
raise NotImplementedError("Functions needs to be implemented")

View file

@ -1,20 +1,26 @@
""" To quickly get started, fill in the following details:"""
ical_urls = [
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
]
highlighted_ical_urls = [
]
rss_feeds = [
"http://feeds.bbci.co.uk/news/world/rss.xml#"
]
crypto_coins = [
"bitcoin",
"litecoin",
"ethereum",
"binancecoin"
]
api_key = ""
owm_paid_subscription = False
location = "Berlin, DE"
location = "Julich, DE"
week_starts_on = "Monday"
display_colours = "bwr"
language = "en"
@ -22,22 +28,17 @@ datetime_encoding = "UTF-8" # UTF-8
units = "metric" #aviation (celcius,degrees/knots), metric (celcius,kmh), imperial(f,mph)
hours = "24"
update_interval = 60
max_loop_count = 0 # From 0 to 86400 representing the maximum number the loop is executed, with 0 being unlimited
run_on_hour = True # If True, updates calendar every full hour, ignoring differing update_interval
"""DESIGN"""
font_size = 14 # does not affect every text
font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold
line_thickness = 1 # 1-3 Thickness advised
choosen_design = "month-overview" # month-overview, day-list, day-view, day-focus-list, agenda-list, month-view, image-frame
choosen_design = "month-overview" # month-overview, day-list, day-view, agenda-list, month-view
general_settings = { # General settings that designs may use
"info-area" : "rss", # empty, events, rss, crypto
"highlight-event-days" : True,
"weather-info" : True,
"image-folder" : "",
"overlay-image" : "", # Size must be 384x640px with default display
"extra-excluded-urls" : []
"weather-info" : True
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View file

@ -21,7 +21,7 @@ if [ -z "$option" ]; then
fi
if [ "$option" = 3 ]; then
echo -e "Removing the E-Paper software now..."
pip3 uninstall feedparser -y && sudo pip3 uninstall feedparser -y && pip3 uninstall numpy -y && sudo pip3 uninstall numpy -y && pip3 uninstall Pillow -y && sudo pip3 uninstall Pillow -y && sudo pip3 uninstall pyowm -y&& sudo pip3 uninstall ics -y && pip3 uninstall pyowm -y && pip3 uninstall ics -y && sudo apt-get remove supervisor -y && sudo apt-get clean && sudo apt-get autoremove -y
pip3 uninstall json -y && sudo pip3 uninstall json -y && pip3 uninstall feedparser -y && sudo pip3 uninstall feedparser -y && pip3 uninstall numpy -y && sudo pip3 uninstall numpy -y && pip3 uninstall Pillow -y && sudo pip3 uninstall Pillow -y && sudo pip3 uninstall pyowm -y&& sudo pip3 uninstall ics -y && pip3 uninstall pyowm -y && pip3 uninstall ics -y && sudo apt-get remove supervisor -y && sudo apt-get clean && sudo apt-get autoremove -y
if [ -e /etc/supervisor/conf.d/E-Paper.conf ]; then
sudo rm /etc/supervisor/conf.d/E-Paper.conf
fi
@ -83,10 +83,12 @@ if [ "$option" = 2 ]; then
sudo pip3 install ics
sudo pip3 install feedparser
sudo pip3 install numpy
sudo pip3 install json
pip3 install pyowm
pip3 install ics
pip3 install feedparser
pip3 install numpy
pip3 install json
echo -e "\e[1;36m"Finished installing libraries"\e[0m"
fi

View file

@ -1,9 +1,8 @@
# NOT ACTIVELY MAINTAINED ANY LONGER
Since [aceisace/Inkycal](https://github.com/aceisace/Inkycal) has grown significantly and is far superior in many ways, I will no longer focus on mainting my own fork. The ical implementation might also not be super reliable in this project.
# E-Paper Calendar
This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online. It can also be used as an image frame.
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/0962be8b02e947e9aa4326e73b387e01)](https://app.codacy.com/app/m.giller.dev/E-Paper-Calendar?utm_source=github.com&utm_medium=referral&utm_content=mgfcf/E-Paper-Calendar&utm_campaign=Badge_Grade_Dashboard)
This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online.
This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH).
@ -16,21 +15,18 @@ This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E
* **Added Support for the 2-Colour E-Paper Display as well!** (Late September 2018)
* **Added Support for Raspbian Stretch lite.** (Late September 2018)
<img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/month-overview_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/agenda-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-view_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-focus-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/image-frame_example.png" width="270">
<img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/day-list_example.png" width="270"><img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/month-overview_example.png" width="270"><img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/agenda-list_example.png" width="270"><img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/day-view_example.png" width="270">
1.: Day-List Panel&ensp;
2.: Month-Overview Panel&ensp;
3.: Agenda-List Panel<br>
4.: Day-View Panel&ensp;
5.: Day-Focus-List Panel&ensp;
6.: Image-Frame Panel
1.: Day-List Panel
2.: Month-Overview Panel
3.: Agenda-List Panel
4.: Day-View Panel
## Main features
* Display a calendar with one of multiple designes
* Optionally get RSS-Feed fetched and shown
* Syncronise events from any online calendar (like google, yahoo, etc.)
* Get live weather data (including temperature, humidity, etc.) using openweathermap api
* Only show a slideshow of images
## Hardware required
* 7.5" 3-Colour E-Paper Display (Black, White, Red/Yellow) with driver hat from [waveshare](https://www.waveshare.com/product/7.5inch-e-paper-hat-b.htm)
@ -63,7 +59,7 @@ If the Installer should fail for any reason, kindly open an issue and paste the
This is how the installer will run:
<img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/Installer-v1.2-screenshot.png" width="700">
<img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/Installer-v1.2-screenshot.png" width="700">
## Adding details to the programm
Once the packages are installed, navigate to the home directory, open 'E-Paper-Master' and open the file 'settings.py' inside the Calendar folder. Adjust the values as needed. You can use the table below as a reference.
@ -72,20 +68,18 @@ Once the packages are installed, navigate to the home directory, open 'E-Paper-M
| Parameter | Description |
| --- | --- |
| ical_urls | Your iCalendar URL/s. To add more than one URL, seperate each with a comma. |
| highlighted_ical_urls | Your iCalendar URL/s that should be higlighted in comparison to the ical_urls. To add more than one URL, seperate each with a comma. |
| highlighted_ical_urls | Your iCalendar URL/s that should be higlighted in comparison the ical_urls. To add more than one URL, seperate each with a comma. |
| rss_feeds | All the sources for your rss-feed. To add more than one URL, seperate each with a comma. |
| api_key | Your __personal__ openweathermap API-key which you can generate and find in your Account info. |
| owm_paid_subscription | If you have a paid owm subscription you can set it to `True` and in some panels receive forecast information. |
| location | Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on [openweathermap](https://openweathermap.org/). The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: [(find iso-code)](https://countrycode.org/). |
| week_starts_on | When does the work start in your region? Possible options are `"Monday"` or `"Sunday"`. |
| week_starts_on | When does the work start on your Region? Possible options are `"Monday"` or `"Sunday"`. |
| display_colours | This should normally be set by the installer when you choose the type of your display. Options include `"bw"` if you're using the black and white E-Paper or `"bwr"` when you're using the black-white-red or black-white-yellow E-Paper.|
| language | Sets the language and the related locale for datetime-information. Some texts depend on additional translations that can be added to the dictionary-file.|
| datetime_encoding | Sets the encoding that will be used in the datetime-texts (month, weekday, ...). Default is `"UTF-8"`. Not to confuse with the full locale, this is only the encoding. |
|units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `"metric"`, `"imperial"` or `"aviation"` (Celsius, Degree/Knots). |
|hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `"24"` for 24-hours and `"12"` for 12-hours.|
|update_interval | The update delay between two updates in minutes. By default there is always an update on a full hour.|
|max_loop_count | From 0 to 86400 representing the maximum number of times the loop is going to be executed, with `0` being unlimited.|
|run_on_hour | If `True`, updates calendar every full hour, ignoring differing update_interval.|
### Design
| Parameter | Description |
@ -93,14 +87,11 @@ Once the packages are installed, navigate to the home directory, open 'E-Paper-M
| font_size | Varies the size of the font. Can be any number >0. In some cases the size of the font is fixed by the design. Default value is `14`. |
| font_boldness | Varies the boldness of the font. Can be one of `extralight`, `light`, `regular`, `semibold`, `bold` or `extrabold`. In some cases the boldness of the font is fixed by the design. Default value is `regular`. |
| choosen_design | Sets the desired panel design. |
| line_thickness | Varies the boldness of separation lines in the chosen design. |
| line_thickness | Varies the boldness of separation lines in the choosen design. |
| general_settings | A dictionary containing options that some designs use to optimize there design. Possible options are as follows: |
| `"info-area"` | Defines the content type of an additionaly info area on the design. Can be one of `"rss"`, `"events"` or empty, to remove this area or keep it clean. |
| `"highlight-event-days"` | If set to `True`, days with events are highlighted in contrast to days without events. |
| `"weather-info"` | If set to `False`, weather info areas disappear and make room for events/rss/etc. (depends on the design). |
| `"image-folder"` | Set a relative or absolute path to a folder containing images that you want to see fullscreen with the `"image-frame"` design activated. |
| `"overlay-image"` | Set a relative or absolute path to an image with the same size as the screen (default: 384x640px) to show some static information over every image shown in the `"image-frame"` design. If the overlay image is contained within the `"image-folder"`, it will not be included into the slideshow. |
| `"extra-excluded-urls"` | A list of calendar urls that may be excluded in some panels in certain areas. |
### Debug
| Parameter | Description |