Compare commits

..

No commits in common. "master" and "v1.3" have entirely different histories.
master ... v1.3

267 changed files with 2062 additions and 5093 deletions

21
.gitignore vendored
View file

@ -1,21 +0,0 @@
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################
/.vs
/Calendar/settings.py
/Calendar/__pycache__/settings.cpython-36.pyc
/Calendar/__pycache__
/Calendar/design_exported.png
/design_exported.png
/Calendar/icon_positions_locations.pyc
/Calendar/DebugInterface.pyc
/Calendar/DebugConsole.pyc
/Calendar/DataSourceInterface.pyc
/Calendar/CalendarInterface.pyc
/Calendar/CalendarEvent.pyc
/Calendar/design_exported_old.png
/Calendar/settings_dev.py
/Calendar/settings_pers.py
/.vscode
/Calendar/images/

View file

@ -1,101 +0,0 @@
from DesignEntity import DesignEntity
from Assets import defaultfontsize, colors, defaultfont, path
from datetime import datetime, date, timedelta
from TableDesign import TableDesign
from PIL import ImageDraw, ImageFont
from TextFormatter import date_summary_str, event_prefix_str
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):
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
def __finish_image__(self):
self.__calculate_parameter__()
self.__create_infos_events__()
self.__draw_infos__()
self.__draw_lines__()
def __calculate_parameter__(self):
self.__line_height__ = self.line_spacing + self.__get_text_height__()
self.__event_number__ = int(int(self.size[1]) // self.__line_height__)
self.__date_fontsize__ = self.text_size
self.__date_linespace__ = self.line_spacing
def __create_infos_events__(self):
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:
day_events = self.calendar.get_day_events(fetch_day)
fetch_day_added_once = False
for event in day_events:
row = [""]
if fetch_day_added_once is False:
row.append(date_summary_str(fetch_day))
fetch_day_added_once = True
else:
row.append("")
row.append(event_prefix_str(event, fetch_day))
row.append(event.title)
self.cell_props.append(self.__get_row_props__(event))
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), "", ""]
props = self.__get_row_props__()
self.infos.insert(0, row)
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)
self.draw_design(table)
def __draw_lines__(self):
for i, (_, date, _, _) in enumerate(self.infos[1:]):
if date is not "":
self.__draw_line__(i + 1)
def __draw_line__(self, index):
ypos = index * self.__line_height__ - self.line_spacing / 2
pos = (0, ypos)
positions = [pos, (self.size[0], ypos)]
ImageDraw.Draw(self.__image__).line(
positions, fill=colors["fg"], width=separator_width)
def __get_row_props__(self, event=None):
color = colors["fg"]
bg_color = colors["bg"]
default_cell = {
"color": color,
"background_color": bg_color
}
if event is not None and event.highlight:
color = colors["hl"]
cell = {
"color": color,
"background_color": bg_color
}
return [default_cell, default_cell, cell, cell]
def __get_text_height__(self):
return ImageFont.truetype(path + defaultfont, self.text_size).font.height

View file

@ -1,90 +0,0 @@
from PanelDesign import PanelDesign
from AgendaListDesign import AgendaListDesign
from WeatherHeaderDesign import WeatherHeaderDesign
from settings import general_settings, line_thickness
from PIL import ImageDraw
from Assets import colors
from RssPostListDesign import RssPostListDesign
from CryptoListDesign import CryptoListDesign
agenda_ypadding = 5
weatherheader_height = 0.113
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)
def add_weather(self, weather):
self.weather = weather
def add_calendar(self, calendar):
self.calendar = calendar
def add_rssfeed(self, rss):
if general_settings["info-area"] != "rss":
return
self.info_size = self.__abs_pos__(infolist_size)
pos = (0, self.size[1] - self.info_size[1] + infolist_padding)
list = RssPostListDesign(self.info_size, rss)
list.pos = pos
self.draw_design(list)
self.__draw_seperator__(1-infolist_size[1], colors["fg"])
def add_tasks(self, tasks):
pass
def add_crypto(self, crypto):
if general_settings["info-area"] != "crypto":
return
self.info_size = self.__abs_pos__(infolist_size)
pos = (0, self.size[1] - self.info_size[1] + infolist_padding)
list = CryptoListDesign(self.info_size, crypto)
height = list.get_estimated_height()
list.pos = (pos[0], pos[1] + (self.info_size[1] - height))
self.draw_design(list)
self.info_size = (self.size[0], height)
self.__draw_seperator__(list.pos[1] / self.size[1], colors["fg"])
def __finish_panel__(self):
self.__draw_calendar__()
if general_settings["weather-info"]:
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)
def __abs_pos__(self, pos, size=None):
if size is None:
size = self.size
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)
agenda = AgendaListDesign(size, self.calendar)
agenda.pos = (0, agenda_ypadding + self.weather_size[1])
self.draw_design(agenda)
def __draw_weather__(self):
header = WeatherHeaderDesign(self.weather_size, self.weather)
self.draw_design(header)
self.__draw_seperator__(weatherheader_height, colors["hl"])

View file

@ -1,95 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PIL import Image, ImageFont
from settings import font_boldness, font_size
import os
im_open = Image.open
path = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
if path != "" and path[-1] != "/":
path += "/"
wpath = path+'weather-icons/'
opath = path+'other/'
fpath = 'fonts/'
tempicon = im_open(opath+'temperature.jpeg')
humicon = im_open(opath+'humidity.jpeg')
no_response = im_open(opath+'cloud-no-response.jpeg')
sunriseicon = im_open(opath+'wi-sunrise.jpeg')
sunseticon = im_open(opath+'wi-sunset.jpeg')
windicon = im_open(opath+'wi-strong-wind.jpeg')
fonts = {
"extralight": fpath + "Assistant-ExtraLight.otf",
"light": fpath + "Assistant-Light.otf",
"regular": fpath + "Assistant-Regular.otf",
"semibold": fpath + "Assistant-SemiBold.otf",
"bold": fpath + "Assistant-Bold.otf",
"extrabold": fpath + "Assistant-ExtraBold.otf"
}
defaultfont = fonts[font_boldness]
defaultfontsize = int(font_size)
weathericons = {
'01d': 'wi-day-sunny', '02d': 'wi-day-cloudy', '03d': 'wi-cloudy',
'04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d': 'wi-rain',
'11d': 'wi-thunderstorm', '13d': 'wi-snow', '50d': 'wi-fog',
'01n': 'wi-night-clear', '02n': 'wi-night-cloudy',
'03n': 'wi-night-cloudy', '04n': 'wi-night-cloudy',
'09n': 'wi-night-showers', '10n': 'wi-night-rain',
'11n': 'wi-night-thunderstorm', '13n': 'wi-night-snow',
'50n': 'wi-night-alt-cloudy-windy'}
colors = {
"hl": "red",
"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",
]

BIN
Calendar/Assistant-Bold.ttf Normal file

Binary file not shown.

View file

@ -1,23 +0,0 @@
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
self.__define_corners__()
self.fill = fill
self.outline = outline
self.width = width
def __define_corners__(self):
topleft = (0, 0)
bottomright = self.size
self.corners = [topleft, bottomright]
def __finish_image__(self):
ImageDraw.Draw(self.__image__).rectangle(
self.corners, fill=self.fill, outline=self.outline, width=self.width)

View file

@ -1,24 +0,0 @@
class CalendarEvent (object):
"""Defines a calendar event, independent of any implementation"""
def __init__(self):
self.begin_datetime = None
self.end_datetime = None
self.duration = None
self.allday = None
self.multiday = None
self.rrule = None
self.title = None
self.description = None
self.attendees = []
self.highlight = None
self.calendar_name = None
self.calendar_url = None
self.location = None
self.fetch_datetime = None
def __repr__(self):
return self.title

View file

@ -1,182 +0,0 @@
from DataSourceInterface import DataSourceInterface
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:
return
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
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]
return multiday + allday + timed
def __get_events__(self):
raise NotImplementedError("Functions needs to be implemented")
def get_upcoming_events(self, timespan=None, start_time=None):
if timespan is None:
timespan = timedelta(31)
if start_time == None:
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
start_time = datetime.now(local_tzinfo)
return self.__get_events_in_range__(start_time, timespan)
def get_today_events(self):
return self.get_day_events(date.today())
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)))
local_tzinfo = datetime.now(timezone.utc).astimezone().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):
if month < 0:
month = datetime.now().month
if year < 0:
year = datetime.now().year
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]
return self.__get_events_in_range__(month_start, timedelta(month_days))
def __get_events_in_range__(self, start, duration):
if self.events is None:
return []
if start.tzinfo is None:
raise TypeError("start datetime needs to be timezone-aware")
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)
if event_occurrence:
events_in_range.extend(event_occurrence)
events_in_range = self.__sort_events__(events_in_range)
return self.__sort_event_types__(events_in_range)
def __get_if_event_in_range__(self, event, start, duration):
'''Returns list or None'''
if event is None:
return None
if event.rrule is None:
return self.__is_onetime_in_range__(event, start, duration)
else:
return self.__is_repeating_in_range__(event, start, duration)
def __is_onetime_in_range__(self, event, start, duration):
if event.begin_datetime > start:
first_start = start
first_duration = duration
second_start = event.begin_datetime
else:
first_start = event.begin_datetime
first_duration = event.duration
second_start = start
if (second_start - first_start) < first_duration:
return [event]
else:
return None
def __is_repeating_in_range__(self, event, start, duration):
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)
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
return merged_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_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"):
return rrule
if tz_index == len(rrule):
return rrule + timezone_str
else:
return rrule[:tz_index] + timezone_str + rrule[tz_index:]

View file

@ -1,10 +0,0 @@
class CryptoCoin(object):
def __init__(self):
self.name = None
self.symbol = None
self.price = None
self.day_change = None
self.currency = None
self.datetime = None
self.fetch_datetime = None

View file

@ -1,17 +0,0 @@
from DataSourceInterface import DataSourceInterface
class CryptoInterface(DataSourceInterface):
def __init__(self):
self.crypto_coins = []
def reload(self):
if self.is_available() == False:
return
self.crypto_coins = self.__get_coins__()
def __get_coins__(self):
raise NotImplementedError("Function needs to be implemented")
def get_coins(self):
return self.crypto_coins

View file

@ -1,39 +0,0 @@
from DesignEntity import DesignEntity
from TableDesign import TableDesign
from Assets import defaultfontsize
from GeckoCrypto import GeckoCrypto
from settings import crypto_coins
xpadding = 5
class CryptoListDesign (DesignEntity):
def __init__(self, size, crypto, text_size=defaultfontsize):
super(CryptoListDesign, self).__init__(size)
self.crypto = crypto
self.text_size = text_size
self.matrix = self.__get_matrix__()
def __finish_image__(self):
col_spacing = 10
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.pos = (xpadding, 0)
self.draw_design(table_design)
def __get_matrix__(self):
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)]
matrix.append(row)
return matrix
def get_estimated_height(self):
line_height = self.text_size * 1.25
height = line_height * len(self.matrix)
return height

View file

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

View file

@ -1,33 +0,0 @@
from DesignEntity import DesignEntity
from SingelDayEventListDesign import SingelDayEventListDesign
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
def add_calendar(self, calendar):
self.calendar = calendar
def __finish_image__(self):
self.__draw_header__()
self.__draw_events__()
def __draw_header__(self):
pass
def __draw_events__(self):
events = self.calendar.get_day_events(self.date)
pos = (0, self.size[1] * header_height)
size = (self.size[0], self.size[1] - pos[1])
event_list = SingelDayEventListDesign(size, events)
event_list.pos = pos
self.draw_design(event_list)

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

@ -1,154 +0,0 @@
from DesignEntity import DesignEntity
from PIL import ImageDraw
from TextDesign import TextDesign
from WeatherColumnDesign import WeatherColumnDesign
from datetime import date, timedelta, datetime, timezone
from SingelDayEventListDesign import SingelDayEventListDesign
from Assets import fonts, colors, defaultfontsize
from settings import general_settings
from BoxDesign import BoxDesign
numberbox_ypos = 0.15
numberbox_height = 1 - 2 * numberbox_ypos
number_height = numberbox_height * 0.83
number_boxypos = 0.17
month_height = numberbox_height / 4
monthbox_xpadding = 0.013
monthbox_ypadding = -0.05
monthbox_width = 1 - numberbox_ypos - monthbox_xpadding
weathercolumn_y_size = (0.4, 1)
weekday_height = numberbox_height * 0.19
weekdaybox_height = (weekday_height / numberbox_height) * 1.5
eventlist_static_fontsize = defaultfontsize
eventlist_xpadding = monthbox_xpadding
eventlist_ypadding = 0.01
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
self.date = date
def add_weather(self, weather):
if general_settings["weather-info"] == False:
return
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])
pos = (self.size[0] - size[0], 0)
design = WeatherColumnDesign(size, forecast)
design.pos = pos
self.draw_design(design)
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))
def add_events(self, events):
self.__draw_event_list__(events)
def add_rssfeed(self, rss):
pass
def add_crypto(self, crypto):
pass
def __finish_image__(self):
self.__draw_number_square__()
self.__draw_month__()
def __draw_event_list__(self, events):
box_ypos = numberbox_ypos * self.size[1]
box_xpos = numberbox_ypos * self.size[1]
box_height = numberbox_height * self.size[1]
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)
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.pos = pos
self.draw_design(event_list)
def __draw_month__(self):
font_size = int(month_height * self.size[1])
xpadding = int(monthbox_xpadding * self.size[0])
ypadding = int(monthbox_ypadding * self.size[1])
box_ypos = int(numberbox_ypos * self.size[1])
box_height = int(numberbox_height * self.size[1])
box_pos = (box_ypos + box_height + xpadding, box_ypos + ypadding)
box_size = (int(monthbox_width * self.size[0]), box_height)
month_name = self.date.strftime("%B")
month = TextDesign(box_size, text=month_name, fontsize=font_size)
month.pos = box_pos
self.draw_design(month)
def __draw_number_square__(self):
box_height = numberbox_height * self.size[1]
box_ypos = numberbox_ypos * self.size[1]
box_pos = (box_ypos, box_ypos)
box_size = (box_height, box_height)
box = BoxDesign(box_size, fill=numberbox_background_color)
box.pos = box_pos
self.draw_design(box)
self.__draw_today_number__()
self.__draw_weekday__()
def __draw_today_number__(self):
font_size = number_height * self.size[1]
box_height = numberbox_height * self.size[1]
box_ypos = numberbox_ypos * self.size[1]
ypadding = number_boxypos * box_height
size = (box_height, box_height - ypadding)
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.pos = pos
number.mask = False
self.draw_design(number)
def __draw_weekday__(self):
font_size = weekday_height * self.size[1]
box_height = numberbox_height * self.size[1]
size = (box_height, weekdaybox_height * box_height)
box_ypos = numberbox_ypos * self.size[1]
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.pos = pos
week_day.mask = False
self.draw_design(week_day)
def __abs_co__(self, coordinates):
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
def __get_day_text__(self):
return str(self.date.day)

View file

@ -1,140 +0,0 @@
from PanelDesign import PanelDesign
from Assets import colors
from settings import general_settings
import calendar as callib
from datetime import datetime, timedelta, date
from PIL import ImageDraw
from TextDesign import TextDesign
from DayHeaderDesign import DayHeaderDesign
from DayRowDesign import DayRowDesign
from RssPostListDesign import RssPostListDesign
from CryptoListDesign import CryptoListDesign
from settings import line_thickness
from math import ceil
todayheader_pos = (0, 0)
todayheader_size = (1, 0.25)
lines_thickness = line_thickness
infoarea_replacedrowscount = 3
dayrowsarea_ypos = todayheader_size[1]
dayrowsarea_height = 1 - todayheader_size[1]
dayrow_min_format = 50 / 384
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__ = []
self.__calc_dayrow_size__()
self.__first_render__()
def __first_render__(self):
self.__draw_today_header__()
self.__draw_day_rows__()
def add_weather(self, weather):
for row in self.__day_rows__:
row.add_weather(weather)
def add_calendar(self, calendar):
for row in self.__day_rows__:
row.add_calendar(calendar)
def add_rssfeed(self, rss):
for row in self.__day_rows__:
row.add_rssfeed(rss)
if general_settings["info-area"] is "rss":
self.__day_rows__ = self.__day_rows__[:-infoarea_replacedrowscount]
self.__draw_rss_infoarea__(rss)
def add_crypto(self, crypto):
if general_settings["info-area"] is "crypto":
self.__draw_crypto_infoarea__(crypto)
def add_tasks(self, tasks):
pass
def __draw_rss_infoarea__(self, rss):
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)
design = RssPostListDesign(size, rss)
design.pos = pos
self.draw_design(design)
def __draw_crypto_infoarea__(self, crypto):
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)
design = CryptoListDesign(size, crypto)
acutal_height = design.get_estimated_height()
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]))
self.__day_rows__ = self.__day_rows__[:-replaced_rows]
def __draw_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.__day_rows__.append(row)
def __get_day_row_pos__(self, i):
ypos = self.size[1] * dayrowsarea_ypos
down_shift = i * self.dayrow_size[1] * self.size[1]
return (0, int(ypos + down_shift))
def __calc_dayrow_size__(self):
max_area_height = dayrowsarea_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 __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 __draw_today_header__(self):
header = DayHeaderDesign(self.__abs_co__(
todayheader_size), date.today())
header.pos = self.__abs_co__(todayheader_pos)
self.__day_rows__.append(header)
def __draw_lines__(self):
positions = []
for i in range(len(self.__day_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=lines_thickness)
def __finish_panel__(self):
for design in self.__day_rows__:
self.draw_design(design)
self.__draw_lines__()
def __abs_co__(self, coordinates):
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))

View file

@ -1,123 +0,0 @@
from PIL import ImageDraw, Image
from TextDesign import TextDesign
from settings import week_starts_on, owm_paid_subscription, general_settings
from DesignEntity import DesignEntity
from datetime import datetime
from Assets import weathericons, wpath, fonts, colors, defaultfontsize
from SingelDayEventListDesign import SingelDayEventListDesign
daynumber_y_size = (1, 0.60)
weekday_y_size = (daynumber_y_size[0], 1 - daynumber_y_size[1])
weekday_ypos = daynumber_y_size[1]
daynumber_fontsize = daynumber_y_size[1] * 0.85
daynumber_ypadding = 0.1
weekday_fontsize = weekday_y_size[1] * 0.65
weathericon_ypos = 0.1
weathericon_height = 1 - 2 * weathericon_ypos
eventlist_xpadding = 5
eventlist_ypos = 0.02
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__()
self.date = date
def add_weather(self, weather):
if weather.is_available is False:
return
self.__draw_forecast__(weather)
def add_calendar(self, calendar):
self.__draw_event_list__(calendar)
def add_rssfeed(self, rss):
pass
def __draw_event_list__(self, calendar):
number_width = daynumber_y_size[0] * self.size[1]
ypos = eventlist_ypos * self.size[1]
weather_width = 0
if owm_paid_subscription and general_settings["weather-info"]:
weather_width = weathericon_height * self.size[1]
pos = (number_width + eventlist_xpadding, ypos)
size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1])
fontsize = eventlist_y_fontsize * self.size[1]
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.pos = pos
self.draw_design(event_list)
def __draw_forecast__(self, weather):
forecast = weather.get_forecast_in_days(
self.date.day - datetime.today().day)
if forecast is None:
return
height = int(weathericon_height * self.size[1])
size = (height, height)
ypos = weathericon_ypos * self.size[1]
pos = (self.size[0] - ypos - size[0], ypos)
icon = Image.open(wpath + weathericons[forecast.icon] + ".jpeg")
resized_icon = icon.resize(size, resample=Image.LANCZOS)
self.draw(resized_icon, pos)
def __finish_image__(self):
self.__draw_weekday__()
self.__draw_day_number__()
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])
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.pos = pos
week_day.mask = False
self.draw_design(week_day)
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])
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.pos = pos
self.draw_design(number)
def __abs_co__(self, coordinates):
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
def __get_day_text__(self):
return str(self.date.day)
def __get_day_color__(self):
"""Depending on week_starts_on"""
if week_starts_on == "Monday" and self.date.strftime("%w") == "0":
return colors["hl"]
elif week_starts_on == "Sunday" and self.date.strftime("%w") == "6":
return colors["hl"]
else:
return colors["fg"]

View file

@ -1,155 +0,0 @@
from PanelDesign import PanelDesign
from datetime import datetime, timedelta, date
from DayHeaderDesign import DayHeaderDesign
from HourListDesign import HourListDesign
from settings import general_settings
from RssPostListDesign import RssPostListDesign
from PIL import ImageDraw
from Assets import colors
from EventListDesign import EventListDesign
from CryptoListDesign import CryptoListDesign
header_size = (1, 0.2)
hourlist_size = (1, 1 - header_size[1])
default_shownhours_count = 12
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
if general_settings["info-area"] not in ["", "empty"]:
self.shownhours_count -= infoarea_replaced_hours
self.__first_render__()
def __first_render__(self):
self.__init_header__()
self.__init_hourlist__()
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)
if general_settings["info-area"] == "events":
self.__draw_event_list__(calendar)
self.__draw_infoarea_line__()
def add_rssfeed(self, rss):
if general_settings["info-area"] == "rss":
self.__draw_rss_feed__(rss)
self.__draw_infoarea_line__()
def add_crypto(self, crypto):
if general_settings["info-area"] == "crypto":
self.__draw_crypto_feed__(crypto)
self.__draw_infoarea_line__()
def __draw_infoarea_line__(self):
height = infoarea_replaced_hours * self.__hourlist__.row_size[1]
ypos = self.size[1] - height
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)
def __draw_rss_feed__(self, rss):
height = infoarea_replaced_hours * \
self.__hourlist__.row_size[1] - infoarea_padding
size = (self.size[0], height)
pos = (0, self.size[1] - size[1])
rss = RssPostListDesign(size, rss)
rss.pos = pos
self.draw_design(rss)
def __draw_crypto_feed__(self, crypto):
height = infoarea_replaced_hours * \
self.__hourlist__.row_size[1] - infoarea_padding
size = (self.size[0], height)
pos = (0, self.size[1] - size[1])
crypto = CryptoListDesign(size, crypto)
acutal_height = crypto.get_estimated_height()
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
size = (self.size[0], height)
pos = (0, self.size[1] - size[1])
events = EventListDesign(size, calendar.get_upcoming_events())
events.pos = pos
self.draw_design(events)
def add_tasks(self, tasks):
pass
def __finish_panel__(self):
self.draw_design(self.__header__)
self.draw_design(self.__hourlist__)
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)
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])
def __get_current_hour_range__(self):
start_hour = datetime.now().hour
additional_hours = self.shownhours_count - 1
if start_hour + additional_hours > 23:
start_hour = 23 - additional_hours
return start_hour, start_hour + additional_hours
def __abs_co__(self, coordinates):
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
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

View file

@ -1,65 +0,0 @@
from DebugInterface import DebugInterface
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("---------------------")
print('Begin datetime: ' + str(event.begin_datetime))
print('End datetime: ' + str(event.end_datetime))
print('Duration: ' + str(event.duration))
print('All day: ' + str(event.allday))
print('Multi-day: ' + str(event.multiday))
print('RRULE: ' + str(event.rrule))
print('Title: ' + str(event.title))
print('Description: ' + str(event.description))
print('Attendees: ' + str(event.attendees))
print('Highlight: ' + str(event.highlight))
print('Calendar name: ' + str(event.calendar_name))
print('Location: ' + str(event.location))
print('Fetch datetime: ' + str(event.fetch_datetime))
def print_forecast(self, forecast):
print("\nWeatherForecast:")
print("---------------------")
print('Air temperature: ' + str(forecast.air_temperature))
print('Air humidity: ' + str(forecast.air_humidity))
print('Air pressure: ' + str(forecast.air_pressure))
print('Rain probability: ' + str(forecast.rain_probability))
print('Rain amount: ' + str(forecast.rain_amount))
print('Snow amount: ' + str(forecast.snow_amount))
print('Sunrise-time: ' + str(forecast.sunrise))
print('Sunset time: ' + str(forecast.sunset))
print('Moon phase: ' + str(forecast.moon_phase))
print('Wind speed: ' + str(forecast.wind_speed))
print('Cloudiness: ' + str(forecast.clouds))
print('Icon code: ' + str(forecast.icon))
print('weather-icon name: ' + str(weathericons[forecast.icon]))
print('Short description: ' + str(forecast.short_description))
print('Detailed description: ' + str(forecast.detailed_description))
print('Units: ' + str(forecast.units))
print('Datetime: ' + str(forecast.datetime))
print('Location: ' + str(forecast.location))
print('Fetch datetime: ' + str(forecast.fetch_datetime))
def print_line(self, content):
if content is None:
return
print(str(content))
def print_err(self, exception, msg=""):
if exception is None:
return
content = "[ERR: "
content += datetime.now().strftime("")
content += "]\n" + str(exception)
content += "\n" + str(msg) + "\n"
traceback.print_exc()
self.print_line(str(content))

View file

@ -1,14 +0,0 @@
class DebugInterface (object):
"""Defines general interface for debugging operations"""
def print_event(self, event):
raise NotImplementedError("Functions needs to be implemented")
def print_forecast(self, forecast):
raise NotImplementedError("Functions needs to be implemented")
def print_line(self, content):
raise NotImplementedError("Functions needs to be implemented")
def print_err(self, exception, msg=""):
raise NotImplementedError("Functions needs to be implemented")

View file

@ -1,61 +0,0 @@
from PIL import Image, ImageOps, ImageDraw
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
self.__init_image__()
self.__finished_image__ = False
self.color_key = color_key
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)
def get_image(self):
if self.__finished_image__ is False:
self.__finish_image__()
self.__finished_image__ = True
return self.__image__
def draw(self, subimage, pos, mask=False, invert_mask=False, color_key=False):
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)
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)
def draw_image(self, path, pos):
self.draw(Image.open(path), pos)
def __finish_image__(self):
pass
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')
if invert_mask:
mask = ImageOps.invert(mask)
return ImageOps.invert(mask)

View file

@ -1,99 +0,0 @@
default_language = "en"
'''Characters following '*' are placeholders and will be replaced by some number/text/etc.'''
'''Events'''
more_events = {
'en': '+ *0 more',
'de': '+ *0 weitere'
}
multiday_events = {
'en': 'Multi-day',
'de': 'Mehrtägig'
}
allday_events = {
'en': 'All-day',
'de': 'Ganztägig'
}
'''Weather'''
rain_weather = {
'en': 'Rain',
'de': 'Regen'
}
clear_weather = {
'en': 'Clear',
'de': 'Klar'
}
clouds_weather = {
'en': 'Clouds',
'de': 'Wolken'
}
thunderstorm_weather = {
'en': 'Thunderstorm',
'de': 'Gewitter'
}
drizzle_weather = {
'en': 'Drizzle',
'de': 'Niesel'
}
snow_weather = {
'en': 'Snow',
'de': 'Schnee'
}
mist_weather = {
'en': 'Mist',
'de': 'Nebel'
}
smoke_weather = {
'en': 'Smoke',
'de': 'Rauch'
}
haze_weather = {
'en': 'Haze',
'de': 'Nebel'
}
dust_weather = {
'en': 'Dust',
'de': 'Staub'
}
fog_weather = {
'en': 'Fog',
'de': 'Nebel'
}
sand_weather = {
'en': 'Sand',
'de': 'Sand'
}
ash_weather = {
'en': 'Ash',
'de': 'Asche'
}
squall_weather = {
'en': 'Squall',
'de': 'Sturm'
}
tornado_weather = {
'en': 'Tornado',
'de': 'Tornado'
}
dictionary_collection = [
rain_weather,
clear_weather,
dust_weather,
squall_weather,
tornado_weather,
clouds_weather,
thunderstorm_weather,
smoke_weather,
ash_weather,
sand_weather,
fog_weather,
haze_weather,
mist_weather,
drizzle_weather,
snow_weather,
more_events,
allday_events,
multiday_events
]

View file

@ -1,29 +0,0 @@
from Dictionary import default_language
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():
text = dictionary[language]
return __insert_params__(text, params)
def __insert_params__(text, params):
index = 0
while '*%d' % index in text and index < len(params):
splitted = text.split('*%d' % index)
text = splitted[0] + str(params[index]) + splitted[1]
index += 1
while '*' in text:
splitted = text.split('*%d' % index)
if len(splitted) > 1:
text = splitted[0] + splitted[1].lstrip(' ')
else:
text = splitted[0].rsplit(' ')
index += 1
return text

View file

@ -1,12 +0,0 @@
class DisplayAdapter (object):
"""Interface for CalendarDesign output channels."""
def __init__(self, width, height):
self.width = width
self.height = height
def render(self, design):
raise NotImplementedError("Functions needs to be implemented")
def calibrate(self):
raise NotImplementedError("Functions needs to be implemented")

View file

@ -1,135 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
E-Paper Software (main script) for the 3-colour and 2-Colour E-Paper display
A full and detailed breakdown for this code can be found in the wiki.
If you have any questions, feel free to open an issue at Github.
Copyright by aceisace
"""
from datetime import datetime
from time import sleep
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 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
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))
debug = DebugConsole()
output_adapters = []
if render_to_file:
import ImageFileAdapter
epd = ImageFileAdapter.ImageFileAdapter(path)
output_adapters.append(epd)
if render_to_display:
if display_colours == "bwr":
import Epd7in5bAdapter
epd = Epd7in5bAdapter.Epd7in5bAdapter()
output_adapters.append(epd)
elif display_colours == "bw":
import Epd7in5Adapter
epd = Epd7in5Adapter.Epd7in5Adapter()
output_adapters.append(epd)
available_panels = {
"day-list": DayListPanel,
"month-overview": MonthOvPanel,
"day-view": DayViewPanel,
"day-focus-list": DayFocusListPanel,
"agenda-list": AgendaListPanel,
"month-view": MonthViewPanel,
"image-frame": ImageFramePanel,
}
loop_timer = LoopTimer(
update_interval, run_on_hour=run_on_hour, max_loop_count=max_loop_count)
"""Main loop starts from here"""
def main():
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)
while True:
loop_timer.begin_loop()
start_time = loop_timer.get_current()[0]
if start_time.hour in calibrate_hours and loop_timer.is_new_hour_loop():
debug.print_line("Calibrating outputs")
for output in output_adapters:
output.calibrate()
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 + ")")
debug.print_line("Fetching weather information from open weather map")
owm.reload()
design.add_weather(owm)
debug.print_line('Fetching events from your calendar')
events_cal.reload()
design.add_calendar(events_cal)
debug.print_line('Fetching posts from your rss-feeds')
rss.reload()
design.add_rssfeed(rss)
debug.print_line('Fetching crypto prices from coin gecko')
crypto.reload()
design.add_crypto(crypto)
debug.print_line("\nStarting to render")
for i, output in enumerate(output_adapters):
try:
output.render(design)
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_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("Sleeping " + str(sleep_time) + " until next loop.")
sleep(sleep_time.total_seconds())
if __name__ == '__main__':
main()

View file

@ -1,14 +0,0 @@
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)
def __finish_image__(self):
ImageDraw.Draw(self.__image__).ellipse(
self.corners, fill=self.fill, outline=self.outline, width=self.width)

View file

@ -1,65 +0,0 @@
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)
def display_frame(self, frame_buffer):
self.send_command(DATA_START_TRANSMISSION_1)
for i in range(0, 30720):
temp1 = frame_buffer[i]
j = 0
while (j < 8):
if(temp1 & 0x80):
temp2 = 0x03
else:
temp2 = 0x00
temp2 = (temp2 << 4) & 0xFF
temp1 = (temp1 << 1) & 0xFF
j += 1
if(temp1 & 0x80):
temp2 |= 0x03
else:
temp2 |= 0x00
temp1 = (temp1 << 1) & 0xFF
self.send_data(temp2)
j += 1
self.send_command(DISPLAY_REFRESH)
self.delay_ms(100)
self.wait_until_idle()
def get_frame_buffer(self, image):
buf = [0x00] * int(self.height * self.width / 8)
# Set buffer to value of Python Imaging Library image.
# Image must be in mode 1.
image_monocolor = image.convert('L') # with ot withour dithering?
imwidth, imheight = image_monocolor.size
if imwidth != self.height or imheight != self.width:
raise ValueError('Image must be same dimensions as display \
({0}x{1}).' .format(self.height, self.width))
pixels = image_monocolor.load()
for y in range(self.width):
for x in range(self.height):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] >= 240: # White
buf[int((x + y * self.height) / 8)] |= 0x80 >> (x % 8)
return buf
def calibrate(self):
for _ in range(2):
self.init_render()
black = Image.new('1', (self.height, self.width), 'black')
print('calibrating black...')
ImageDraw.Draw(black)
self.display_frame(self.get_frame_buffer(black))
white = Image.new('1', (self.height, self.width), 'white')
ImageDraw.Draw(white)
print('calibrating white...')
self.display_frame(self.get_frame_buffer(white))
self.sleep()
print('Calibration complete')

View file

@ -1,92 +0,0 @@
from EpdAdapter import EpdAdapter, DISPLAY_REFRESH, DATA_START_TRANSMISSION_1
from settings import display_colours
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)
def display_frame(self, frame_buffer):
self.send_command(DATA_START_TRANSMISSION_1)
for i in range(0, int(self.height / 4 * self.width)):
# the above line had to be modified due to python2 -> python3
# the issue lies in division, which returns integers in python2
# but floats in python3
temp1 = frame_buffer[i]
j = 0
while (j < 4):
if ((temp1 & 0xC0) == 0xC0):
temp2 = 0x03
elif ((temp1 & 0xC0) == 0x00):
temp2 = 0x00
else:
temp2 = 0x04
temp2 = (temp2 << 4) & 0xFF
temp1 = (temp1 << 2) & 0xFF
j += 1
if((temp1 & 0xC0) == 0xC0):
temp2 |= 0x03
elif ((temp1 & 0xC0) == 0x00):
temp2 |= 0x00
else:
temp2 |= 0x04
temp1 = (temp1 << 2) & 0xFF
self.send_data(temp2)
j += 1
self.send_command(DISPLAY_REFRESH)
self.delay_ms(100)
self.wait_until_idle()
def get_frame_buffer(self, image):
buf = [0x00] * int(self.height * self.width / 4)
imwidth, imheight = image.size
if imwidth != self.height or imheight != self.width:
raise ValueError('Image must be same dimensions as display \
({0}x{1}).' .format(self.height, self.width))
image_buf = self.__prepare_image__(image)
for x in range(self.width):
for y in range(self.height):
# Set the bits for the column of pixels at the current
# position.
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))
else: # Red
buf[int((y + x * self.height) / 4)
] &= ~(0xC0 >> (y % 4 * 2))
buf[int((y + x * self.height) / 4)] |= 0x40 >> (y % 4 * 2)
return buf
def __prepare_image__(self, image):
buffer = np.array(image)
r, g = buffer[:, :, 0], buffer[:, :, 1]
buffer[np.logical_and(r > 220, g > 220)] = [255, 255, 255]
buffer[r > g] = [255, 0, 0]
buffer[r != 255] = [0, 0, 0]
return buffer
def calibrate(self):
for _ in range(2):
self.init_render()
black = Image.new('RGB', (self.height, self.width), 'black')
print('calibrating black...')
ImageDraw.Draw(black)
self.display_frame(self.get_frame_buffer(black))
red = Image.new('RGB', (self.height, self.width), 'red')
ImageDraw.Draw(red)
print('calibrating red...')
self.display_frame(self.get_frame_buffer(red))
white = Image.new('RGB', (self.height, self.width), 'white')
ImageDraw.Draw(white)
print('calibrating white...')
self.display_frame(self.get_frame_buffer(white))
self.sleep()
print('Calibration complete')

View file

@ -1,187 +0,0 @@
from DisplayAdapter import DisplayAdapter
import spidev
import RPi.GPIO as GPIO
import time
from PIL import Image
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
# Commands
PANEL_SETTING = 0x00
POWER_SETTING = 0x01
POWER_OFF = 0x02
POWER_OFF_SEQUENCE_SETTING = 0x03
POWER_ON = 0x04
POWER_ON_MEASURE = 0x05
BOOSTER_SOFT_START = 0x06
DEEP_SLEEP = 0x07
DATA_START_TRANSMISSION_1 = 0x10
DATA_STOP = 0x11
DISPLAY_REFRESH = 0x12
IMAGE_PROCESS = 0x13
LUT_FOR_VCOM = 0x20
LUT_BLUE = 0x21
LUT_WHITE = 0x22
LUT_GRAY_1 = 0x23
LUT_GRAY_2 = 0x24
LUT_RED_0 = 0x25
LUT_RED_1 = 0x26
LUT_RED_2 = 0x27
LUT_RED_3 = 0x28
LUT_XON = 0x29
PLL_CONTROL = 0x30
TEMPERATURE_SENSOR_COMMAND = 0x40
TEMPERATURE_CALIBRATION = 0x41
TEMPERATURE_SENSOR_WRITE = 0x42
TEMPERATURE_SENSOR_READ = 0x43
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
LOW_POWER_DETECTION = 0x51
TCON_SETTING = 0x60
TCON_RESOLUTION = 0x61
SPI_FLASH_CONTROL = 0x65
REVISION = 0x70
GET_STATUS = 0x71
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)
self.reset_pin = RST_PIN
self.dc_pin = DC_PIN
self.busy_pin = BUSY_PIN
self.epd_init()
def display_frame(self, frame_buffer):
raise NotImplementedError("Functions needs to be implemented")
def get_frame_buffer(self, image):
raise NotImplementedError("Functions needs to be implemented")
def render(self, design):
self.init_render()
time.sleep(5)
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")
self.display_frame(self.get_frame_buffer(prepared_image))
# Powering off the E-Paper until the next loop
self.sleep()
print('Data sent successfully')
print('Powering off the E-Paper until the next loop' + '\n')
def init_render(self):
if (self.epd_init() != 0):
return -1
self.reset()
self.send_command(POWER_SETTING)
self.send_data(0x37)
self.send_data(0x00)
self.send_command(PANEL_SETTING)
self.send_data(0xCF)
self.send_data(0x08)
self.send_command(BOOSTER_SOFT_START)
self.send_data(0xc7)
self.send_data(0xcc)
self.send_data(0x28)
self.send_command(POWER_ON)
self.wait_until_idle()
self.send_command(PLL_CONTROL)
self.send_data(0x3c)
self.send_command(TEMPERATURE_CALIBRATION)
self.send_data(0x00)
self.send_command(VCOM_AND_DATA_INTERVAL_SETTING)
self.send_data(0x77)
self.send_command(TCON_SETTING)
self.send_data(0x22)
self.send_command(TCON_RESOLUTION)
self.send_data(0x02) # source 640
self.send_data(0x80)
self.send_data(0x01) # gate 384
self.send_data(0x80)
self.send_command(VCM_DC_SETTING)
self.send_data(0x1E) # decide by LUT file
self.send_command(0xe5) # FLASH MODE
self.send_data(0x03)
def digital_write(self, pin, value):
GPIO.output(pin, value)
def digital_read(self, pin):
return GPIO.input(pin)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_transfer(self, data):
self.SPI.writebytes(data)
def epd_init(self):
# SPI device, bus = 0, device = 0
self.SPI = spidev.SpiDev(0, 0)
#self.SPI.no_cs = True
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BUSY_PIN, GPIO.IN)
self.SPI.max_speed_hz = 2000000
self.SPI.mode = 0b00
return 0
def sleep(self):
self.send_command(POWER_OFF)
self.wait_until_idle()
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 reset(self):
self.digital_write(self.reset_pin, GPIO.LOW) # module reset
self.delay_ms(200)
self.digital_write(self.reset_pin, GPIO.HIGH)
self.delay_ms(200)
def send_command(self, command):
self.digital_write(self.dc_pin, GPIO.LOW)
# the parameter type is list but not int
# so use [command] instead of command
self.spi_transfer([command])
def send_data(self, data):
self.digital_write(self.dc_pin, GPIO.HIGH)
# the parameter type is list but not int
# so use [data] instead of data
self.spi_transfer([data])

View file

@ -1,74 +0,0 @@
from DesignEntity import DesignEntity
from TableDesign import TableDesign
from Assets import defaultfontsize, colors
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
self.__event_matrix__ = []
self.__props_matrix__ = []
self.show_more_info = show_more_info
self.text_size = text_size
self.line_spacing = line_spacing
self.col_spacing = col_spacing
self.font_family = font_family
self.general_color = general_color
self.background_color = background_color
self.highlight_color = highlight_color
self.event_prefix_func = event_prefix_func
self.event_prefix_rel_dates = event_prefix_rel_dates
if self.event_prefix_func is None:
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.__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__)
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]
prefix = self.event_prefix_func(event, rel_date)
return [prefix, event.title]
def __fill_event_matrix__(self):
visible_events = self.events
if self.show_more_info and len(visible_events) > self.visible_event_count:
visible_events = visible_events[:self.visible_event_count - 1]
for i, event in enumerate(visible_events):
row = self.__get_formatted_event__(event, i)
self.__event_matrix__.append(row)
self.__props_matrix__.append(self.__get_row_props__(event))
if self.show_more_info is False:
return
additional_events_count = len(self.events) - len(visible_events)
more_text = " " + get_text(more_events, additional_events_count)
if additional_events_count > 0:
self.__event_matrix__.append(["", more_text])
self.__props_matrix__.append(self.__get_row_props__())
def __get_row_props__(self, event=None):
color = self.general_color
bg_color = self.background_color
if event is not None and event.highlight:
color = self.highlight_color
cell = {
"color": color,
"background_color": bg_color
}
return [cell, cell]

View file

@ -1,68 +0,0 @@
from CryptoInterface import CryptoInterface
from datetime import datetime
from CryptoCoin import CryptoCoin
from urllib.request import urlopen
import json
import math
api_test_url = "https://api.coingecko.com/api/v3/ping"
api_url = "https://api.coingecko.com/api/v3/"
api_metadata_url = api_url + "coins/list"
api_price_url = api_url + "simple/price"
price_currency = "usd"
price_currency_sign = "$"
class GeckoCrypto(CryptoInterface):
def __init__(self, coins):
self.coin_ids = coins
self.metadata = None
super(GeckoCrypto, self).__init__()
def is_available(self):
try:
urlopen(api_test_url)
return True
except:
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()
dataJSON = json.loads(data.decode('utf-8'))
raw = dataJSON[id][price_currency]
price = math.ceil(raw*100) / 100
change = dataJSON[id]['usd_24h_change']
coins.append(self.__build_coin__(id, price, change))
except:
print("Gecko-Error [" + id + "]")
return coins
def __build_coin__(self, id, value, change):
coin = CryptoCoin()
coin.name = self.metadata[id]['name']
coin.day_change = round(change, 2)
coin.price = value
coin.datetime = datetime.now()
coin.fetch_datetime = datetime.now()
coin.currency = price_currency_sign
coin.symbol = self.metadata[id]['symbol']
return coin
def __prepare_metadata__(self):
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}

View file

@ -1,199 +0,0 @@
from DesignEntity import DesignEntity
from settings import hours, language, line_thickness
from TextDesign import TextDesign
from PIL import ImageDraw
from Assets import colors, defaultfontsize, fonts
from BoxDesign import BoxDesign
from datetime import timedelta, datetime
hourbox_y_width = 1
hour_box_fontsize = 0.85
hour_ypadding = 0.1
hoursubtext_fontsize = 0.7
hoursubtext_height = 0.45
event_title_fontsize = defaultfontsize
event_title_xpadding = 3
event_title_ypadding = 5
line_thickness = line_thickness
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
self.last_hour = last_hour
self.__calc_parameters__()
self.events = []
def add_events(self, events):
self.events.extend(events)
self.events.sort(key=lambda x: x.begin_datetime)
def __finish_image__(self):
self.number_columns = self.__get_max_num_simultaneous_events__()
self.__draw_lines__()
self.__draw_events__()
self.__draw_current_time_line__()
self.__draw_hour_rows__()
def __calc_parameters__(self):
self.hour_count = self.last_hour - self.first_hour + 1
self.row_size = (self.size[0], self.size[1] / self.hour_count)
def __get_hour_text__(self, hour):
if hour <= 12 or hours is "24":
return str(hour)
else:
short = hour - 12
return str(short) if short > 0 else "12"
def __get_ypos_for_time__(self, hour, minute=0):
return self.__get_height_for_duration__(hour, minute) - self.__get_height_for_duration__(self.first_hour)
def __get_height_for_duration__(self, hours, minutes=0):
row_height = self.row_size[1]
return row_height * (hours + minutes / 60)
def __draw_events__(self):
column_events = []
for _ in range(self.number_columns):
column_events.append(None)
for event in self.events:
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):
current_time = new_event.begin_datetime
new_event_added = False
for index in range(len(column_events)):
if column_events[index] != None and column_events[index].end_datetime <= current_time:
column_events[index] = None
if new_event_added == False and column_events[index] == None:
column_events[index] = new_event
new_event_added = True
return column_events
def __draw_hour_rows__(self):
for hour in range(self.first_hour, self.last_hour + 1):
self.__draw_row__(hour)
def __draw_row__(self, hour):
subtext_height = self.row_size[1] * hoursubtext_height
sub_fontsize = subtext_height * hoursubtext_fontsize
ypadding = hour_ypadding * self.row_size[1]
width = hourbox_y_width * self.row_size[1]
height = self.row_size[1] - subtext_height
size = (width, height)
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.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.pos = (0, height + self.__get_ypos_for_time__(hour))
self.draw_design(sub)
def __draw_lines__(self):
for i in range(self.hour_count):
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)
def __get_hour_sub_text__(self, hour):
if hours == "12":
return "AM" if hour < 12 else "PM"
elif language is "de":
return "Uhr"
elif language is "en":
return "o'c"
return ""
def __draw_event__(self, event, column=0):
xoffset = hourbox_y_width * self.row_size[1]
column_width = (self.size[0] - xoffset) / self.number_columns
begin = event.begin_datetime
time_ypos = self.__get_ypos_for_time__(begin.hour, begin.minute)
hours = event.duration.total_seconds() / 3600
time_height = self.__get_height_for_duration__(hours)
yoffset_correction = 0
if time_ypos < 0:
yoffset_correction = time_ypos
pos = (xoffset + column_width * column, time_ypos - yoffset_correction)
size = (column_width, time_height + yoffset_correction)
if size[1] < 0:
return # Event not in shown time range
self.__draw_event_block__(pos, size, event)
def __draw_event_block__(self, pos, size, event):
box_color = colors["hl"] if event.highlight else colors["fg"]
box = BoxDesign(size, fill=box_color)
box.mask = False
box.pos = pos
self.draw_design(box)
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)
txt.mask = False
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)
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]
for pre_event in preceding:
if self.__are_simultaneous__(pre_event, event):
current_parallelity += 1
if parallelity_count < current_parallelity:
parallelity_count = current_parallelity
return parallelity_count
def __are_simultaneous__(self, ev_a, ev_b):
if ev_a.begin_datetime > ev_b.begin_datetime:
ev_a, ev_b = ev_b, ev_a
mes_dur = ev_b.begin_datetime - ev_a.begin_datetime
return mes_dur < ev_a.duration
def __draw_current_time_line__(self):
now = datetime.now()
ypos = self.__get_ypos_for_time__(now.hour, now.minute)
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)

View file

@ -1,122 +0,0 @@
from CalendarInterface import CalendarInterface
from CalendarEvent import CalendarEvent
from ics import Calendar
from datetime import datetime, timedelta, timezone
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
super(IcalEvents, self).__init__()
def is_available(self):
for url in self.urls + self.highlighted_urls:
try:
urlopen(url)
return True
except:
pass
return False
def __get_events__(self):
events = self.__get_events_from_urls__(self.urls)
highlighted = self.__get_events_from_urls__(self.highlighted_urls)
highlighted = map(self.__highlight_event__, highlighted)
events.extend(highlighted)
return events
def __highlight_event__(self, event):
event.highlight = True
return event
def __get_events_from_urls__(self, urls):
loaded_events = []
if urls is None:
return loaded_events
for calendar in urls:
try:
decode = str(urlopen(calendar).read().decode())
decode = self.__remove_alarms__(decode)
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
cal_event.end_datetime = event.end.datetime
cal_event.duration = event.duration
cal_event.title = event.name
cal_event.description = event.description
cal_event.location = event.location
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)
if cal_event.allday:
cal_event = self.__fix_allday__(cal_event)
cal_event.multiday = self.__is_multiday__(cal_event)
loaded_events.append(cal_event)
except BaseException as ex:
print("ICal-Error [" + calendar + "]")
print(ex)
return loaded_events
def __fix_allday__(self, event):
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
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.duration = event.end_datetime - event.begin_datetime
return event
def __remove_alarms__(self, decode):
alarm_begin = 'BEGIN:VALARM'
alarm_end = 'END:VALARM'
lineseparation = '\r\n'
beginAlarmIndex = 0
while beginAlarmIndex >= 0:
beginAlarmIndex = decode.find(alarm_begin)
if beginAlarmIndex >= 0:
endAlarmIndex = decode.find(alarm_end, beginAlarmIndex)
decode = decode[:beginAlarmIndex] + \
decode[endAlarmIndex +
len(alarm_end) + len(lineseparation):]
return decode
def __extract_rrule__(self, event):
if re.search('RRULE', str(event)) is None:
return None
return re.search('RRULE:(.+?)\n', str(event)).group(1).rstrip()
def __is_multiday__(self, event):
if event.allday and event.duration == timedelta(1):
return False
return event.begin_datetime.day != event.end_datetime.day or \
event.begin_datetime.month != event.end_datetime.month or \
event.begin_datetime.year != event.end_datetime.year

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,18 +0,0 @@
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
if self.file_path == "":
self.file_path = path
def render(self, design):
design.get_image().save(self.file_path + 'design_exported.png')
def calibrate(self):
pass

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

@ -1,85 +0,0 @@
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):
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('Datetime: ' + str(begin_time) + '\n')
self.__add_beginning__(begin_time)
def __add_beginning__(self, time):
self.loop_history.append((time,))
if len(self.loop_history) > max_history_entries:
dif = len(self.loop_history) - max_history_entries
self.loop_history = self.loop_history[dif:]
def __add_ending__(self, time):
current = self.get_current()
self.loop_history[-1] = (current[0], time)
def end_loop(self):
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]
def time_until_next(self):
interval_duration = timedelta(minutes=self.interval)
loop_duration = self.get_last_duration()
sleep_time = interval_duration - loop_duration
if self.on_hour:
time_until_hour = self.get_time_to_next_hour()
if time_until_hour < sleep_time:
return time_until_hour
if sleep_time < timedelta(0):
sleep_time = timedelta(0, 0, 0, 0, min_sleep_minutes)
return sleep_time
def get_last_duration(self):
if len(self.loop_history) == 0:
return
begin, end = self.loop_history[-1]
return end - begin
def get_time_to_next_hour(self):
cur = datetime.now()
rounded = datetime(cur.year, cur.month, cur.day, cur.hour)
next_hour_time = rounded + timedelta(hours=1)
return next_hour_time - datetime.now()
def is_new_hour_loop(self):
if len(self.loop_history) < 2:
return False
previous_loop = self.loop_history[-2]
current_loop = self.get_current()
if previous_loop[0].hour != current_loop[0].hour:
return True
else:
return False

View file

@ -1,97 +0,0 @@
from DesignEntity import DesignEntity
import calendar as callib
from datetime import datetime, timedelta
from TextDesign import TextDesign
from Assets import colors
from settings import week_starts_on
from BoxDesign import BoxDesign
daynumberboxsize = (0.143, 0.2)
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
self.year = datetime_month.year
self.highlight_today = highlight_today
self.__week_days__ = self.__get_week_days_ordered__()
def __finish_image__(self):
self.__draw_month_overview__()
def __draw_month_overview__(self):
"""Using the built-in calendar function, draw icons for each
number of the month (1,2,3,...29,30,31)"""
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)))
if self.highlight_today:
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)
design.pos = pos
self.draw_design(design)
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.pos = (pos[0], pos[1] + day_number_ypadding * self.size[1])
self.draw_design(txt)
def get_day_pos(self, week_in_month, day_of_week, rel_pos=(0, 0)):
maxwidth, maxheight = self.size
partialwidth = maxwidth / 7
partialheight = maxheight / 5
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()))
return (x, int(y + (self.__abs_pos__(daynumberboxsize)[1] - self.__abs_pos__(dayhighlightboxsize)[1]) / 2))
def __get_week_of_month__(self, date):
for wof, week in enumerate(callib.monthcalendar(date.year, date.month)):
if date.day in week:
return wof
return 0
def __get_day_of_week__(self, date):
return self.__week_days__.index(date.strftime("%a"))
def __get_week_days_ordered__(self):
cur_weekday = datetime.now().weekday()
correction = -cur_weekday
if week_starts_on == "Sunday":
correction -= 1
weekdays = []
for i in range(7):
weekdays.append(
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
return weekdays
def __abs_pos__(self, pos, size=None):
if size is None:
size = self.size
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
def get_real_height(self):
weeks_in_month = callib.monthcalendar(self.year, self.month)
num_size = self.__abs_pos__(daynumberboxsize)
num_pos = self.get_day_pos(len(weeks_in_month) - 1, 6)
return num_size[1] + num_pos[1]

View file

@ -1,199 +0,0 @@
from PanelDesign import PanelDesign
from Assets import colors
from settings import general_settings, week_starts_on
import calendar as callib
from datetime import datetime, timedelta
from WeatherHeaderDesign import WeatherHeaderDesign
from PIL import ImageDraw
from TextDesign import TextDesign
from BoxDesign import BoxDesign
from EllipseDesign import EllipseDesign
from MonthBlockDesign import MonthBlockDesign, daynumberboxsize
from EventListDesign import EventListDesign
from RssPostListDesign import RssPostListDesign
from settings import general_settings
from CryptoListDesign import CryptoListDesign
weatherheadersize = (1, 0.113)
monthboxsize = (1, 0.085)
monthtextsize = monthboxsize[1] * 0.75
monthplace = (0, 0.11 - weatherheadersize[1])
monthovsize = (1, 0.48)
monthovposition = (0, 0.25 - weatherheadersize[1])
seperatorplace = (0, 0.113)
weekdayrowpos = (0, 0.209 - weatherheadersize[1])
weekrowboxsize = (1, 0.044)
weekdaytextsize = 0.7 * weekrowboxsize[1]
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
if general_settings["weather-info"]:
self.weather_header_height = self.size[1] * weatherheadersize[1]
self.__first_render__()
def __first_render__(self):
if week_starts_on == "Monday":
callib.setfirstweekday(callib.MONDAY)
elif week_starts_on == "Sunday":
callib.setfirstweekday(callib.SUNDAY)
self.__week_days__ = self.__get_week_days_ordered__()
self.__draw_month_name__()
self.__draw_week_row__()
if general_settings["weather-info"]:
self.__draw_seperator__()
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
self.draw_design(self.month_block)
def add_weather(self, weather):
if general_settings["weather-info"] == False:
return
self.draw_design(WeatherHeaderDesign(
self.__abs_pos__(weatherheadersize), weather))
def add_rssfeed(self, rss):
if general_settings["info-area"] is "rss":
self.__draw_rss_post_list_to_bottom__(rss)
def add_crypto(self, crypto):
if general_settings["info-area"] is "crypto":
self.__draw_crypto_post_list_to_bottom__(crypto)
def add_tasks(self, tasks):
pass
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()]))
for event in month_events:
self.__draw_highlight_event_day__(event)
if general_settings["info-area"] is "events":
self.__draw_event_list_to_bottom__(calendar)
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))
info_list = RssPostListDesign(size, rss)
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))
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))
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))
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))
self.draw_design(info_list)
def __draw_highlight_event_day__(self, date):
first_month_week = datetime(date[2], date[1], 1).isocalendar()[1]
cur_date = datetime(date[2], date[1], date[0])
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))
self.__draw_highlight_circle__(circle_size, pos, 'red', width=2)
def __abs_pos__(self, pos, size=None):
if size is None:
size = self.size
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
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)
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")
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")
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)
def __get_week_day_pos__(self, day_of_week):
maxwidth, _ = self.__abs_pos__(monthovsize)
partialwidth = maxwidth / 7
posx, posy = self.__abs_pos__(weekdayrowpos)
return (int(posx + day_of_week * partialwidth), int(posy + self.weather_header_height))
def __draw_highlight_box__(self, size, pos, color=colors["fg"], width=1):
design = BoxDesign(size, outline=color, width=width)
design.pos = pos
self.draw_design(design)
def __draw_highlight_circle__(self, size, pos, color=colors["fg"], width=1):
design = EllipseDesign(size, outline=color, width=width)
design.pos = pos
self.draw_design(design)
def __get_week_days_ordered__(self):
cur_weekday = datetime.now().weekday()
correction = -cur_weekday
if week_starts_on == "Sunday":
correction -= 1
weekdays = []
for i in range(7):
weekdays.append(
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
return weekdays
def __get_day_of_week__(self, date):
return self.__week_days__.index(date.strftime("%a"))

View file

@ -1,133 +0,0 @@
from PanelDesign import PanelDesign
from settings import general_settings, week_starts_on, line_thickness
from PIL import ImageDraw
from datetime import date
from Assets import colors
import calendar as callib
from TableDesign import TableDesign
from DayBoxDesign import DayBoxDesign
from RssPostListDesign import RssPostListDesign
from WeatherHeaderDesign import WeatherHeaderDesign
from CryptoListDesign import CryptoListDesign
weather_height = 0.113
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 = []
self.month = month
if self.month == None:
self.month = date.today().month
self.year = year
if self.year == None:
self.year = date.today().year
self.__init_sizes__()
self.__init_day_boxes__()
def __init_sizes__(self):
self.weather_height = 0
self.info_height = 0
if general_settings["info-area"] in ["events", "rss", "crypto"]:
self.info_height = info_height
if general_settings["weather-info"]:
self.weather_height = weather_height
self.day_area_height = 1 - self.weather_height - self.info_height
self.day_area_ypos = self.weather_height
self.week_count = self.__get_week_count__()
area_height = self.size[1] * self.day_area_height
area_width = self.size[0]
self.day_box_size = (area_width / 7, area_height / self.week_count)
def add_weather(self, weather):
if general_settings["weather-info"] == False:
return
size = (self.size[0], self.size[1] * self.weather_height)
header = WeatherHeaderDesign(size, weather)
self.draw_design(header)
self.__draw_seperator__(size[1], colors["hl"])
def add_calendar(self, calendar):
self.__add_calendar_to_days__(calendar)
def __add_calendar_to_days__(self, calendar):
for week in self.day_table:
for day in week:
if day != None:
day.add_calendar(calendar)
def add_rssfeed(self, rss):
if general_settings["info-area"] != "rss":
return
size = (self.size[0], self.size[1] * self.info_height)
pos = (0, self.size[1] - size[1] + info_padding)
rss = RssPostListDesign(size, rss)
rss.pos = pos
self.draw_design(rss)
def add_tasks(self, tasks):
pass
def add_crypto(self, crypto):
if general_settings["info-area"] == "crypto":
self.__draw_crypto__(crypto)
def __draw_crypto__(self, crypto):
size = (self.size[0], self.size[1] * self.info_height)
pos = (0, self.size[1] - size[1])
crypto = CryptoListDesign(size, crypto)
crypto.pos = (pos[0], pos[1] + (size[1] -
crypto.get_estimated_height()))
self.draw_design(crypto)
def __finish_panel__(self):
self.__draw_days__()
def __draw_days__(self):
size = (self.size[0], self.size[1] * self.day_area_height)
pos = (0, self.size[1] * self.day_area_ypos)
table = TableDesign(size, matrix=self.day_table)
table.pos = pos
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)
def __init_day_boxes__(self):
if week_starts_on == "Monday":
callib.setfirstweekday(callib.MONDAY)
elif week_starts_on == "Sunday":
callib.setfirstweekday(callib.SUNDAY)
weeks = callib.monthcalendar(self.year, self.month)
for i, week in enumerate(weeks):
self.day_table.append([])
for day in week:
self.day_table[i].append(self.__create_day__(day))
def __create_day__(self, day):
if day == None or day == 0:
return None
design = DayBoxDesign(self.day_box_size, date(
self.year, self.month, int(day)))
return design
def __get_week_count__(self):
return len(callib.monthcalendar(self.year, self.month))

BIN
Calendar/OpenSans-Bold.ttf Normal file

Binary file not shown.

View file

@ -1,101 +0,0 @@
from WeatherForecast import WeatherForecast
from WeatherInterface import WeatherInterface
import pyowm
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)
def is_available(self):
try:
return self.api.is_API_online()
except:
return False
def reload(self):
pass
def get_today_forecast(self, location=None):
if self.is_available() is False:
return None
try:
location = self.location if location is None else location
observation = self.api.weather_at_place(location)
weather = observation.get_weather()
return self.__get_forecast_from_weather__(weather, location=location)
except:
return None
def get_forecast_in_days(self, offset_by_days, location=None):
if offset_by_days is 0:
return self.get_today_forecast(location)
if self.is_available() is False:
return None
location = self.location if location is None else location
try:
forecast = self.api.daily_forecast(location, limit=offset_by_days)
target_weather = forecast.get_forecast().get_weathers()[-1]
return self.__get_forecast_from_weather__(target_weather, location=location)
except: # only allowed for paid membership
return None
def __get_forecast_from_weather__(self, weather, location):
forecast_object = WeatherForecast()
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.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.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
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
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.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

@ -1,39 +0,0 @@
from DesignEntity import DesignEntity
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
def add_calendar(self, calendar):
pass
def add_rssfeed(self, rss):
pass
def add_tasks(self, tasks):
pass
def add_crypto(self, crypto):
pass
def __finish_panel__(self):
pass
def __finish_image__(self):
self.__finish_panel__()
if print_technical_data:
td = TechnicalDataDesign(
self.size, self.start_timestamp, datetime.now())
td.mask = True
self.draw_design(td)

View file

@ -1,35 +0,0 @@
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 = []
def reload(self):
if self.is_available() == False:
return
self.loaded_posts = self.__get_posts__()
self.__sort_posts__()
def __get_posts__(self):
raise NotImplementedError("Functions needs to be implemented")
def get_latest_posts(self, count=10):
return self.loaded_posts[0:count]
def get_today_posts(self):
return self.get_day_posts(datetime.now())
def get_day_posts(self, day):
return self.__get_posts_to_filter__(lambda x: x.datetime.strftime('%d-%m-%y') == day.strftime('%d-%m-%y'))
def __get_posts_to_filter__(self, post_filter):
if self.loaded_posts is None:
return []
return [post for post in self.loaded_posts if post_filter(post)]
def __sort_posts__(self):
self.loaded_posts.sort(key=lambda x: x.datetime, reverse=True)

View file

@ -1,57 +0,0 @@
from RssInterface import RssInterface
from datetime import datetime, timedelta, date
import feedparser
import RssPost
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__()
def is_available(self):
try:
testurl = ""
if self.urls:
testurl = self.urls[0]
else:
return False
urlopen(testurl)
return True
except:
return False
def __get_posts__(self):
posts = []
today = date.today()
time_span = today - timedelta(days=max_range_days)
for feeds in self.urls:
parse = feedparser.parse(feeds)
for post in parse.entries:
parsed_post = self.__parse_post__(post)
if parsed_post.datetime.date() >= time_span:
posts.append(parsed_post)
return posts
def __parse_post__(self, post):
parsed_post = RssPost.RssPost()
parsed_post.fetch_datetime = datetime.now()
parsed_post.title = post.title
parsed_post.description = post.description
parsed_post.source = self.__get_webpage__(post.link)
parsed_post.datetime = datetime(*post.published_parsed[:6])
return parsed_post
def __get_webpage__(self, link):
start_index = link.find('://') + 3
end_index = link[start_index:].find('/') + start_index
return link[start_index: end_index]

View file

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

View file

@ -1,36 +0,0 @@
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
self.__post_matrix__ = []
self.text_size = text_size
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)
self.draw_design(table_design)
def __get_formatted_post__(self, post):
date = post.datetime.strftime('%d %b')
date = self.__remove_leading_zero__(date)
return ['', '', post.title]
def __remove_leading_zero__(self, text):
while text[0] is '0':
text = text[1:]
return text
def __fill_post_matrix__(self):
for post in self.rssfeed.get_latest_posts():
row = self.__get_formatted_post__(post)
self.__post_matrix__.append(row)

View file

@ -1,15 +0,0 @@
from EventListDesign import EventListDesign
from settings import hours
from Assets import fonts, defaultfontsize, colors
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)

View file

@ -1,192 +0,0 @@
from TextDesign import TextDesign
from TextWraper import wrap_text_with_font
from Assets import defaultfontsize, colors
from DesignEntity import DesignEntity
default_props = {
"color": colors["fg"],
"background_color": colors["bg"],
"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)
self.__init_image__(background_color)
self.matrix = matrix
self.max_col_size = max_col_size
self.max_row_size = max_row_size
self.line_spacing = line_spacing
self.col_spacing = col_spacing
self.truncate_rows = truncate_rows
self.truncate_cols = truncate_cols
self.max_row = None
self.max_col = None
self.column_horizontal_alignments = column_horizontal_alignments
self.wrap = wrap
self.truncate_text = truncate_text
self.truncate_suffix = truncate_suffix
self.cell_properties = cell_properties
default_props["font-size"] = fontsize
def __finish_image__(self):
if len(self.matrix) is 0:
return
self.__reform_col_size__()
self.__reform_row_size__()
self.cell_sizes = self.__get_cell_sizes__()
self.max_col, self.max_row = self.__get_truncated_counts__()
self.__print_table__(self.matrix)
def __reform_col_size__(self):
if self.max_col_size is not None:
return
col_sizes = []
for c in range(len(self.matrix[0])): # amout of columns
for r in range(len(self.matrix)):
row_col_size = self.__get_cell_size__(r, c)[0]
if len(col_sizes) - 1 < c:
col_sizes.append(row_col_size)
elif row_col_size > col_sizes[c]:
col_sizes[c] = row_col_size
for index, size in enumerate(col_sizes):
preceding_size = sum(col_sizes[:index]) + index * self.col_spacing
if preceding_size + size > self.size[0]:
col_sizes[index] = self.size[0] - preceding_size
break
self.max_col_size = col_sizes
def __reform_row_size__(self):
if self.max_row_size is not None:
return
row_sizes = []
for r in range(len(self.matrix)):
for c in range(len(self.matrix[0])): # amout of columns
col_row_size = self.__get_cell_size__(r, c)[1]
if len(row_sizes) - 1 < r:
row_sizes.append(col_row_size)
elif col_row_size > row_sizes[r]:
row_sizes[r] = col_row_size
self.max_row_size = row_sizes
def __get_cell_size__(self, r, c):
content = self.matrix[r][c]
size = (0, 0)
if content == None:
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]
if self.wrap and self.max_col_size != None:
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)
else: # DesignEntity
size = content.size
return size
def __get_truncated_counts__(self):
max_col = 0
if self.truncate_cols:
while max_col < len(self.matrix[0]) and self.__get_cell_pos__(0, max_col + 1)[0] - self.col_spacing <= self.size[0]:
max_col += 1
else:
max_col = len(self.matrix[0])
max_row = 0
if self.truncate_rows:
while max_row < len(self.matrix) and self.__get_cell_pos__(max_row + 1, 0)[1] - self.line_spacing <= self.size[1]:
max_row += 1
else:
max_row = len(self.matrix)
return (max_col, max_row)
def __print_table__(self, matrix):
for r in range(self.max_row):
for c in range(self.max_col):
self.__draw_cell__(r, c)
def __draw_text__(self, pos, size, row, col):
color = self.__get_cell_prop__(row, col, "color")
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.pos = pos
design.mask = False
self.draw_design(design)
def __draw_design__(self, pos, size, row, col):
bg_color = self.__get_cell_prop__(row, col, "background_color")
source_design = self.matrix[row][col]
source_design.mask = False
framed_design = DesignEntity(size, mask=False)
framed_design.__init_image__(color=bg_color)
framed_design.draw_design(source_design)
framed_design.pos = pos
self.draw_design(framed_design)
def __draw_cell__(self, row, col):
size = self.cell_sizes[row][col]
pos = self.__get_cell_pos__(row, col)
if self.matrix[row][col] == None:
return
elif type(self.matrix[row][col]) == str:
self.__draw_text__(pos, size, row, col)
else:
self.__draw_design__(pos, size, row, col)
def __get_cell_pos__(self, row, col):
xpos, ypos = (0, 0)
for c in range(col):
xpos += self.cell_sizes[row][c][0]
xpos += self.col_spacing
for r in range(row):
ypos += self.cell_sizes[r][col][1]
ypos += self.line_spacing
return (xpos, ypos)
def __get_cell_sizes__(self):
size_matrix = []
for r in range(len(self.matrix)):
size_matrix.append([])
for c in range(len(self.matrix[0])):
size = (self.max_col_size[c], int(self.max_row_size[r]))
size_matrix[r].append(size)
return size_matrix
def __get_col_hori_alignment__(self, c):
if len(self.column_horizontal_alignments) <= c:
return "left"
return self.column_horizontal_alignments[c]
def __get_cell_prop__(self, r, c, prop):
if self.cell_properties is None:
return default_props[prop]
if r < len(self.cell_properties) and c < len(self.cell_properties[r]) and prop in self.cell_properties[r][c].keys():
return self.cell_properties[r][c][prop]
else:
return default_props[prop]

View file

@ -1,42 +0,0 @@
from DesignEntity import DesignEntity
from TextDesign import TextDesign
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
self.stop = stop
def __finish_image__(self):
data = self.__get_data__()
txt = TextDesign(self.size, text=data, fontsize=font_size)
txt.color = colors["fg"]
txt.pos = (0, 0)
self.draw_design(txt)
txt = TextDesign(self.size, text=data, fontsize=font_size)
txt.color = colors["hl"]
txt.pos = (1, 1)
self.draw_design(txt)
txt = TextDesign(self.size, text=data, fontsize=font_size)
txt.color = colors["fg"]
txt.pos = (2, 2)
self.draw_design(txt)
def __get_data__(self):
data = "START: "
data += str(self.start)
data += "\nDURATION BEFORE RENDER: "
dur = self.stop - self.start
data += str(dur)
data += "\nSTOP: "
data += str(self.stop)
return data

View file

@ -1,80 +0,0 @@
from DesignEntity import DesignEntity
from PIL import ImageFont, ImageDraw, ImageOps
from Assets import path, defaultfont, colors, defaultfontsize
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:
font = defaultfont
self.font_family = font
self.font_size = fontsize
self.text = text
self.horizontal_alignment = horizontalalignment
self.vertical_alignment = verticalalignment
self.truncate = truncate
self.truncate_suffix = truncate_suffix
self.wrap = wrap
self.color = color
self.background_color = background_color
def __finish_image__(self):
if self.color is "white":
self.invert_mask = True
if self.background_color not in ["white", "black"] or self.color in ["red"]:
self.color_key = True
self.__init_image__(self.background_color)
self.__font__ = self.__get_font__()
if self.wrap is False and self.truncate:
self.__truncate_text__()
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__)
def __truncate_text__(self):
# does not need truncating
if self.__font__.getsize_multiline(self.text)[0] <= self.size[0]:
return
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(' ')
self.text += self.truncate_suffix
def __pos_from_alignment__(self):
width, height = self.__get_text_size__()
x, y = 0, 0
if self.vertical_alignment == "center":
y = int((self.size[1] / 2) - (height / 2))
elif self.vertical_alignment == "bottom":
y = int(self.size[1] - height)
if self.horizontal_alignment == "center":
x = int((self.size[0] / 2) - (width / 2))
elif self.horizontal_alignment == "right":
x = int(self.size[0] - width)
return (x, y)
def __get_text_size__(self):
widht = self.__font__.getsize_multiline(self.text)[0]
height = (self.text.count('\n') + 1) * self.__font__.font.height
return widht, height
def __wrap_text__(self):
self.text = wrap_text_with_font(self.text, self.size[0], self.__font__)
def __get_font__(self):
return ImageFont.truetype(path + self.font_family, int(self.font_size))

View file

@ -1,110 +0,0 @@
from settings import hours, language
from datetime import timedelta, datetime, timezone
from DictionaryMapper import get_text
from Dictionary import multiday_events, allday_events
first_occurrence_char = '['
middle_occurrence_char = '|'
last_occurrence_char = ']'
multiday_begin_character = ' >'
multiday_end_character = '< '
until_character = ' - '
allday_character = ""
multiday_character = allday_character + allday_character
def time_str(dt):
if hours is "12":
return dt.strftime("%I:%M%p")
elif hours is "24":
return dt.strftime("%H:%M")
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()
if event.multiday is False:
return event_time_summary(event)
# Relative to
# First day
elif __equal__(event.begin_datetime, relative_date):
return event_time_summary(event) + multiday_begin_character
# Last day
elif __equal__(event.end_datetime, relative_date) or \
(__day_duration__(event.end_datetime) == timedelta(0) and __equal__(relative_date + timedelta(1), event.end_datetime)):
return multiday_end_character + event_time_summary(event)
# Some day
else:
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()
if event.multiday:
return get_text(multiday_events)
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()
if event.multiday:
return multiday_character
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":
return dt.strftime('%a ' + day + '. %b')
elif language is "de":
return dt.strftime(day + '. %b., %a')
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,20 +0,0 @@
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:
until_current = (result + " " + word).strip()
txt_width, _ = font.getsize_multiline(until_current)
if txt_width > width:
result += '\n'
else:
result += ' '
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

@ -1,26 +0,0 @@
from Dictionary import default_language, dictionary_collection
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)
if dictionary == None:
return phrase
if target_lang in dictionary.keys():
return dictionary[target_lang]
elif '_' in target_lang and target_lang.split('_')[0] in dictionary.keys():
return dictionary[target_lang.split('_')[0]]
else:
return dictionary[default_language]
def find_dictionary(phrase, dictionary_collection=dictionary_collection):
for dictionary in dictionary_collection:
if phrase in dictionary.values():
return dictionary
return None

View file

@ -1,112 +0,0 @@
from DesignEntity import DesignEntity
from TextDesign import TextDesign
from TableDesign import TableDesign
from Assets import wpath, weathericons, tempicon, humicon, windicon, no_response, colors, defaultfontsize
from PIL import Image
from settings import hours
icon_xpos = 0.1
icon_x_ypos = 0
icon_width = 1 - 2 * icon_xpos
info_x_ypos = icon_x_ypos + icon_width
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
def __finish_image__(self):
if self.forecast is None:
self.__draw_no_response__()
return
self.__draw_icon__(self.forecast.icon)
self.__draw_infos__(self.forecast)
def __draw_infos__(self, forecast):
temperature = forecast.air_temperature + \
" " + self.__get_unit__(("°C", "°F"))
humidity = forecast.air_humidity + "%"
if forecast.units == "aviation":
if forecast.wind_deg == None:
forecast.wind_deg = ""
elif len(forecast.wind_deg) == 1:
forecast.wind_deg = "00" + forecast.wind_deg
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
else:
windspeed = forecast.wind_deg + "@" + forecast.wind_speed + \
self.__get_unit__(("", "")) # added degrees
else:
windspeed = forecast.wind_speed + " " + \
self.__get_unit__(("km/h", "mph"))
numbers_list = [[forecast.short_description],
[temperature],
[humidity],
[windspeed]]
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)
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)
def __draw_icon__(self, icon_id):
width = int(icon_width * self.size[0])
size = (width, width)
xpos = icon_xpos * self.size[0]
ypos = icon_x_ypos * self.size[0]
pos = (xpos, ypos)
self.__draw_resized_path_at__(
wpath + weathericons[icon_id] + ".jpeg", pos, size)
def __draw_no_response__(self):
width = int(icon_width * self.size[0])
size = (width, width)
xpos = icon_xpos * self.size[0]
ypos = icon_x_ypos * self.size[0]
pos = (xpos, ypos)
self.__draw_resized_image_at__(no_response, pos, size)
def __draw_resized_path_at__(self, path, pos, size):
img = Image.open(path)
self.__draw_resized_image_at__(img, pos, size)
def __draw_resized_image_at__(self, img, pos, size):
size = (int(size[0]), int(size[1]))
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]
else:
return tuple[1]
def __abs_co__(self, coordinates):
return (coordinates[0] * self.size[0], coordinates[1] * self.size[1])
def __get_time__(self, time):
if hours == "24":
return time.strftime('%H:%M')
else:
return time.strftime('%I:%M')

View file

@ -1,25 +0,0 @@
class WeatherForecast (object):
"""Defines a weather forecast, independent of any implementation"""
def __init__(self):
self.air_temperature = None
self.air_pressure = None
self.air_humidity = None
self.rain_probability = None
self.rain_amount = None
self.snow_amount = None
self.sunrise = None
self.sunset = None
self.moon_phase = None
self.wind_speed = None
self.wind_deg = None
self.clouds = None
self.icon = None
self.short_description = None
self.detailed_description = None
self.units = None
self.datetime = None
self.location = None
self.fetch_datetime = None

View file

@ -1,95 +0,0 @@
from DesignEntity import DesignEntity
from Assets import no_response, windicon, tempicon, sunseticon, sunriseicon, wpath, weathericons, humicon
from TextDesign import TextDesign
from settings import units, hours
wiconplace = (0, 0)
tempplace = (0.779, 0)
humplace = (0.779, 0.486)
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
self.__first_render__()
def __first_render__(self):
if self.__weather__.is_available() is False:
self.__render_missing_connection__()
return
cur_weather = self.__weather__.get_today_forecast()
if cur_weather == None:
self.__render_missing_connection__()
return
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 = ""
elif len(cur_weather.wind_deg) == 1: # if deg is 2, add two zeros for format
cur_weather.wind_deg = "00" + cur_weather.wind_deg
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
else:
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"))
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(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))
def __render_missing_connection__(self):
self.draw(no_response, self.__abs_pos__(wiconplace))
def __abs_pos__(self, pos):
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.pos = pos
self.draw_design(txt)
def __get_unit__(self, tuple):
if units == "metric" or units == "aviation":
return tuple[0]
else:
return tuple[1]
def __get_time__(self, time):
if hours == "24":
return time.strftime('%H:%M')
else:
return time.strftime('%I:%M')

View file

@ -1,11 +0,0 @@
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")
def get_today_forecast(self, location=None):
raise NotImplementedError("Functions needs to be implemented")

33
Calendar/calibration.py Normal file
View file

@ -0,0 +1,33 @@
import epd7in5b
from PIL import Image, ImageDraw, ImageFont
EPD_WIDTH = 640
EPD_HEIGHT = 384
epd = epd7in5b.EPD()
def calibration():
for i in range(2):
epd.init()
black = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 0)
print('calibrating black...')
ImageDraw.Draw(black)
epd.display_frame(epd.get_frame_buffer(black))
#print(epd.get_frame_buffer(black))
red = Image.new('L', (EPD_WIDTH, EPD_HEIGHT), 127)
ImageDraw.Draw(red)
print('calibrating red...')
epd.display_frame(epd.get_frame_buffer(red))
white = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 255)
ImageDraw.Draw(white)
print('calibrating white...')
epd.display_frame(epd.get_frame_buffer(white))
epd.sleep()
print('Cycle complete!')
def main():
for i in range(1):
calibration()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,31 @@
"""
Calibration module for the 2-Colour E-Paper Calendar display. Running this script
helps to 'flush' all the pixels and retain the colour on the display.
"""
import epd7in5
from PIL import Image, ImageDraw, ImageFont
EPD_WIDTH = 640
EPD_HEIGHT = 384
epd = epd7in5.EPD()
def calibration():
for i in range(2):
epd.init()
black = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 0)
print('calibrating black...')
ImageDraw.Draw(black)
epd.display_frame(epd.get_frame_buffer(black))
white = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 255)
ImageDraw.Draw(white)
print('calibrating white...')
epd.display_frame(epd.get_frame_buffer(white))
epd.sleep()
print('Cycle complete!')
def main():
for i in range(1):
calibration()
if __name__ == '__main__':
main()

BIN
Calendar/days/0.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
Calendar/days/1.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/10.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/11.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/12.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/13.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/14.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/15.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/16.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/17.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/18.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/19.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/2.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/20.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/21.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/22.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/23.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/24.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/25.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/26.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/27.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/28.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/29.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/3.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/30.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/31.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/4.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/5.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/6.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/7.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/8.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
Calendar/days/9.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

180
Calendar/epd7in5.py Normal file
View file

@ -0,0 +1,180 @@
import epdif
from PIL import Image
import RPi.GPIO as GPIO
# Display resolution
EPD_WIDTH = 640
EPD_HEIGHT = 384
# EPD7IN5 commands
PANEL_SETTING = 0x00
POWER_SETTING = 0x01
POWER_OFF = 0x02
POWER_OFF_SEQUENCE_SETTING = 0x03
POWER_ON = 0x04
POWER_ON_MEASURE = 0x05
BOOSTER_SOFT_START = 0x06
DEEP_SLEEP = 0x07
DATA_START_TRANSMISSION_1 = 0x10
DATA_STOP = 0x11
DISPLAY_REFRESH = 0x12
IMAGE_PROCESS = 0x13
LUT_FOR_VCOM = 0x20
LUT_BLUE = 0x21
LUT_WHITE = 0x22
LUT_GRAY_1 = 0x23
LUT_GRAY_2 = 0x24
LUT_RED_0 = 0x25
LUT_RED_1 = 0x26
LUT_RED_2 = 0x27
LUT_RED_3 = 0x28
LUT_XON = 0x29
PLL_CONTROL = 0x30
TEMPERATURE_SENSOR_COMMAND = 0x40
TEMPERATURE_CALIBRATION = 0x41
TEMPERATURE_SENSOR_WRITE = 0x42
TEMPERATURE_SENSOR_READ = 0x43
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
LOW_POWER_DETECTION = 0x51
TCON_SETTING = 0x60
TCON_RESOLUTION = 0x61
SPI_FLASH_CONTROL = 0x65
REVISION = 0x70
GET_STATUS = 0x71
AUTO_MEASUREMENT_VCOM = 0x80
READ_VCOM_VALUE = 0x81
VCM_DC_SETTING = 0x82
class EPD:
def __init__(self):
self.reset_pin = epdif.RST_PIN
self.dc_pin = epdif.DC_PIN
self.busy_pin = epdif.BUSY_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
def digital_write(self, pin, value):
epdif.epd_digital_write(pin, value)
def digital_read(self, pin):
return epdif.epd_digital_read(pin)
def delay_ms(self, delaytime):
epdif.epd_delay_ms(delaytime)
def send_command(self, command):
self.digital_write(self.dc_pin, GPIO.LOW)
# the parameter type is list but not int
# so use [command] instead of command
epdif.spi_transfer([command])
def send_data(self, data):
self.digital_write(self.dc_pin, GPIO.HIGH)
# the parameter type is list but not int
# so use [data] instead of data
epdif.spi_transfer([data])
def init(self):
if (epdif.epd_init() != 0):
return -1
self.reset()
self.send_command(POWER_SETTING)
self.send_data(0x37)
self.send_data(0x00)
self.send_command(PANEL_SETTING)
self.send_data(0xCF)
self.send_data(0x08)
self.send_command(BOOSTER_SOFT_START)
self.send_data(0xc7)
self.send_data(0xcc)
self.send_data(0x28)
self.send_command(POWER_ON)
self.wait_until_idle()
self.send_command(PLL_CONTROL)
self.send_data(0x3c)
self.send_command(TEMPERATURE_CALIBRATION)
self.send_data(0x00)
self.send_command(VCOM_AND_DATA_INTERVAL_SETTING)
self.send_data(0x77)
self.send_command(TCON_SETTING)
self.send_data(0x22)
self.send_command(TCON_RESOLUTION)
self.send_data(0x02) #source 640
self.send_data(0x80)
self.send_data(0x01) #gate 384
self.send_data(0x80)
self.send_command(VCM_DC_SETTING)
self.send_data(0x1E) #decide by LUT file
self.send_command(0xe5) #FLASH MODE
self.send_data(0x03)
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
self.delay_ms(200)
self.digital_write(self.reset_pin, GPIO.HIGH)
self.delay_ms(200)
def get_frame_buffer(self, image):
buf = [0x00] * int(self.width * self.height / 8)
# Set buffer to value of Python Imaging Library image.
# Image must be in mode 1.
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display \
({0}x{1}).' .format(self.width, self.height))
pixels = image_monocolor.load()
for y in range(self.height):
for x in range(self.width):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] != 0:
buf[int((x + y * self.width) / 8)] |= 0x80 >> (x % 8)
return buf
def display_frame(self, frame_buffer):
self.send_command(DATA_START_TRANSMISSION_1)
for i in range(0, 30720):
temp1 = frame_buffer[i]
j = 0
while (j < 8):
if(temp1 & 0x80):
temp2 = 0x03
else:
temp2 = 0x00
temp2 = (temp2 << 4) & 0xFF
temp1 = (temp1 << 1) & 0xFF
j += 1
if(temp1 & 0x80):
temp2 |= 0x03
else:
temp2 |= 0x00
temp1 = (temp1 << 1) & 0xFF
self.send_data(temp2)
j += 1
self.send_command(DISPLAY_REFRESH)
self.delay_ms(100)
self.wait_until_idle()
def sleep(self):
self.send_command(POWER_OFF)
self.wait_until_idle()
self.send_command(DEEP_SLEEP)
self.send_data(0xa5)
### END OF FILE ###

209
Calendar/epd7in5b.py Normal file
View file

@ -0,0 +1,209 @@
##
# @filename : epd7in5.py
# @brief : Implements for Dual-color e-paper library
# @author : Yehui from Waveshare
#
# Copyright (C) Waveshare July 10 2017
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import epdif
from PIL import Image
import RPi.GPIO as GPIO
# Display resolution
EPD_WIDTH = 640
EPD_HEIGHT = 384
# EPD7IN5 commands
PANEL_SETTING = 0x00
POWER_SETTING = 0x01
POWER_OFF = 0x02
POWER_OFF_SEQUENCE_SETTING = 0x03
POWER_ON = 0x04
POWER_ON_MEASURE = 0x05
BOOSTER_SOFT_START = 0x06
DEEP_SLEEP = 0x07
DATA_START_TRANSMISSION_1 = 0x10
DATA_STOP = 0x11
DISPLAY_REFRESH = 0x12
IMAGE_PROCESS = 0x13
LUT_FOR_VCOM = 0x20
LUT_BLUE = 0x21
LUT_WHITE = 0x22
LUT_GRAY_1 = 0x23
LUT_GRAY_2 = 0x24
LUT_RED_0 = 0x25
LUT_RED_1 = 0x26
LUT_RED_2 = 0x27
LUT_RED_3 = 0x28
LUT_XON = 0x29
PLL_CONTROL = 0x30 #was 0x30
TEMPERATURE_SENSOR_COMMAND = 0x40
TEMPERATURE_CALIBRATION = 0x41
TEMPERATURE_SENSOR_WRITE = 0x42
TEMPERATURE_SENSOR_READ = 0x43
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
LOW_POWER_DETECTION = 0x51
TCON_SETTING = 0x60
TCON_RESOLUTION = 0x61
SPI_FLASH_CONTROL = 0x65
REVISION = 0x70
GET_STATUS = 0x71
AUTO_MEASUREMENT_VCOM = 0x80
READ_VCOM_VALUE = 0x81
VCM_DC_SETTING = 0x82 #was 0x82
class EPD:
def __init__(self):
self.reset_pin = epdif.RST_PIN
self.dc_pin = epdif.DC_PIN
self.busy_pin = epdif.BUSY_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
def digital_write(self, pin, value):
epdif.epd_digital_write(pin, value)
def digital_read(self, pin):
return epdif.epd_digital_read(pin)
def delay_ms(self, delaytime):
epdif.epd_delay_ms(delaytime)
def send_command(self, command):
self.digital_write(self.dc_pin, GPIO.LOW)
# the parameter type is list but not int
# so use [command] instead of command
epdif.spi_transfer([command])
def send_data(self, data):
self.digital_write(self.dc_pin, GPIO.HIGH)
# the parameter type is list but not int
# so use [data] instead of data
epdif.spi_transfer([data])
def init(self):
if (epdif.epd_init() != 0):
return -1
self.reset()
self.send_command(POWER_SETTING)
self.send_data(0x37)
self.send_data(0x00)
self.send_command(PANEL_SETTING)
self.send_data(0xCF)
self.send_data(0x08)
self.send_command(BOOSTER_SOFT_START)
self.send_data(0xc7)
self.send_data(0xcc)
self.send_data(0x28)
self.send_command(POWER_ON)
self.wait_until_idle()
self.send_command(PLL_CONTROL)
self.send_data(0x3c)
self.send_command(TEMPERATURE_CALIBRATION)
self.send_data(0x00)
self.send_command(VCOM_AND_DATA_INTERVAL_SETTING)
self.send_data(0x77)
self.send_command(TCON_SETTING)
self.send_data(0x22)
self.send_command(TCON_RESOLUTION)
self.send_data(0x02) #source 640
self.send_data(0x80)
self.send_data(0x01) #gate 384
self.send_data(0x80)
self.send_command(VCM_DC_SETTING)
self.send_data(0x1E) #decide by LUT file
self.send_command(0xe5) #FLASH MODE
self.send_data(0x03)
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
self.delay_ms(200)
self.digital_write(self.reset_pin, GPIO.HIGH)
self.delay_ms(200)
def get_frame_buffer(self, image):
buf = [0x00] * int(self.width * self.height / 4)
# Set buffer to value of Python Imaging Library image.
# Image must be in mode L.
image_grayscale = image.convert('L')
imwidth, imheight = image_grayscale.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display \
({0}x{1}).' .format(self.width, self.height))
pixels = image_grayscale.load()
for y in range(self.height):
for x in range(self.width):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] < 64: # black
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
elif pixels[x, y] < 192: # convert gray to red
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2)
else: # white
buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2)
return buf #due to python2 -> python3, int had to be added in 'get_frame
#_buffer
def display_frame(self, frame_buffer):
self.send_command(DATA_START_TRANSMISSION_1)
for i in range(0, int(self.width / 4 * self.height)):
#the above line had to be modified due to python2 -> python3
#the issue lies in division, which returns integers in python2
#but floats in python3
temp1 = frame_buffer[i]
j = 0
while (j < 4):
if ((temp1 & 0xC0) == 0xC0):
temp2 = 0x03
elif ((temp1 & 0xC0) == 0x00):
temp2 = 0x00
else:
temp2 = 0x04
temp2 = (temp2 << 4) & 0xFF
temp1 = (temp1 << 2) & 0xFF
j += 1
if((temp1 & 0xC0) == 0xC0):
temp2 |= 0x03
elif ((temp1 & 0xC0) == 0x00):
temp2 |= 0x00
else:
temp2 |= 0x04
temp1 = (temp1 << 2) & 0xFF
self.send_data(temp2)
j += 1
self.send_command(DISPLAY_REFRESH)
self.delay_ms(100)
self.wait_until_idle()
def sleep(self):
self.send_command(POWER_OFF)
self.wait_until_idle()
self.send_command(DEEP_SLEEP)
self.send_data(0xa5)
### END OF FILE ###

36
Calendar/epdif.py Normal file
View file

@ -0,0 +1,36 @@
import spidev
import RPi.GPIO as GPIO
import time
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
# SPI device, bus = 0, device = 0
SPI = spidev.SpiDev(0, 0)
#SPI.no_cs = True
def epd_digital_write(pin, value):
GPIO.output(pin, value)
def epd_digital_read(pin):
return GPIO.input(BUSY_PIN)
def epd_delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_transfer(data):
SPI.writebytes(data)
def epd_init():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BUSY_PIN, GPIO.IN)
SPI.max_speed_hz = 2000000
SPI.mode = 0b00
return 0;

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more