diff --git a/Calendar/E-Paper.py b/Calendar/E-Paper.py index 2b95c0e..96846bd 100644 --- a/Calendar/E-Paper.py +++ b/Calendar/E-Paper.py @@ -13,21 +13,12 @@ from datetime import datetime, date, timedelta from time import sleep from dateutil.rrule import * from dateutil.parser import parse +import arrow import re import random import gc - -try: - import feedparser -except ImportError: - print("Please install feedparser with: sudo pip3 install feedparser") - print("and") - print("pip3 install feedparser") - -try: - import numpy as np -except ImportError: - print("Please install numpy with: sudo apt-get install python3-numpy") +import feedparser +import numpy as np from settings import * from icon_positions_locations import * @@ -52,7 +43,9 @@ from calibration import calibration EPD_WIDTH = 640 EPD_HEIGHT = 384 -font = ImageFont.truetype(path+'Assistant-Regular.ttf', 18) +default = ImageFont.truetype(path+'Assistant-Regular.ttf', 18) +semi = ImageFont.truetype(path+'Assistant-SemiBold.ttf', 18) +bold = ImageFont.truetype(path+'Assistant-Bold.ttf', 18) im_open = Image.open owm = pyowm.OWM(api_key) @@ -94,60 +87,24 @@ def main(): """Create a blank white page first""" image = Image.new('RGB', (EPD_HEIGHT, EPD_WIDTH), 'white') - """Add the icon with the current month's name""" - image.paste(im_open(mpath+str(time.strftime("%B")+'.jpeg')), monthplace) - - """Add the line seperating the weather and Calendar section""" - image.paste(seperator, seperatorplace) - - """Add weekday-icons (Mon, Tue...) and draw a circle on the - current weekday""" - if (week_starts_on is "Monday"): - calendar.setfirstweekday(calendar.MONDAY) - image.paste(weekmon, weekplace) - image.paste(weekday, weekdaysmon[(time.strftime("%a"))], weekday) - - """For those whose week starts on Sunday, change accordingly""" - if (week_starts_on is "Sunday"): - calendar.setfirstweekday(calendar.SUNDAY) - image.paste(weeksun, weekplace) - image.paste(weekday, weekdayssun[(time.strftime("%a"))], weekday) - - """Using the built-in calendar function, draw icons for each - number of the month (1,2,3,...28,29,30)""" - cal = calendar.monthcalendar(time.year, time.month) - - for numbers in cal[0]: - image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['a'+str(cal[0].index(numbers)+1)]) - for numbers in cal[1]: - image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['b'+str(cal[1].index(numbers)+1)]) - for numbers in cal[2]: - image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['c'+str(cal[2].index(numbers)+1)]) - for numbers in cal[3]: - image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['d'+str(cal[3].index(numbers)+1)]) - for numbers in cal[4]: - image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['e'+str(cal[4].index(numbers)+1)]) - if len(cal) is 6: - for numbers in cal[5]: - image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['f'+str(cal[5].index(numbers)+1)]) - - """Custom function to display text on the E-Paper. - Tuple refers to the x and y coordinates of the E-Paper display, - with (0, 0) being the top left corner of the display.""" - def write_text(box_width, box_height, text, tuple): + """Custom function to display text on the E-Paper""" + def write_text(box_width, box_height, text, tuple, font=default, alignment='middle'): text_width, text_height = font.getsize(text) - if (text_width, text_height) > (box_width, box_height): - raise ValueError('Sorry, your text is too big for the box') - else: + while (text_width, text_height) > (box_width, box_height): + text=text[0:-1] + text_width, text_height = font.getsize(text) + if alignment is "" or "middle" or None: x = int((box_width / 2) - (text_width / 2)) - y = int((box_height / 2) - (text_height / 2)) - space = Image.new('RGB', (box_width, box_height), color='white') - ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) - image.paste(space, tuple) + if alignment is 'left': + x = 0 + y = int((box_height / 2) - (text_height / 2)) + space = Image.new('RGB', (box_width, box_height), color='white') + ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) + image.paste(space, tuple) - """Connect to Openweathermap API to fetch weather data""" - print("Connecting to Openweathermap API servers...") - if owm.is_API_online() is True: + """Connect to Openweathermap API and fetch weather data""" + if top_section is "Weather" and api_key != "" and owm.is_API_online() is True: + print("Connecting to Openweathermap API servers...") observation = owm.weather_at_place(location) print("weather data:") weather = observation.get_weather() @@ -176,18 +133,18 @@ def main(): sunrisetime = str(datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix'))).strftime('%-I:%M')) sunsettime = str(datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix'))).strftime('%-I:%M')) - print('Temperature: '+Temperature+' °C') - print('Humidity: '+Humidity+'%') - #print('Icon code: '+weathericon) + """Show the fetched weather data""" + print('Temperature: '+ Temperature+' °C') + print('Humidity: '+ Humidity+'%') print('weather-icon name: '+weathericons[weathericon]) - print('Wind speed: '+windspeed+'km/h') - print('Sunrise-time: '+sunrisetime) - print('Sunset time: '+sunsettime) + print('Wind speed: '+ windspeed+'km/h') + print('Sunrise-time: '+ sunrisetime) + print('Sunset time: '+ sunsettime) print('Cloudiness: ' + cloudstatus+'%') - print('Weather description: '+weather_description+'\n') + print('Weather description: '+ weather_description+'\n') """Add the weather icon at the top left corner""" - image.paste(im_open(wpath+weathericons[weathericon]+'.jpeg'), wiconplace) + image.paste(im_open(wpath + weathericons[weathericon] +'.jpeg'), wiconplace) """Add the temperature icon at it's position""" image.paste(tempicon, tempplace) @@ -215,92 +172,63 @@ def main(): api server, add the cloud with question mark""" image.paste(no_response, wiconplace) - """Algorithm for filtering and sorting events from your - iCalendar/s""" - print('Fetching events from your calendar'+'\n') - events_this_month = [] - upcoming = [] - today = date.today() + """Using the built-in calendar to generate the monthly Calendar + template""" + cal = calendar.monthcalendar(time.year, time.month) + + if middle_section is "Calendar": + """Add the icon with the current month's name""" + image.paste(im_open(mpath+str(time.strftime("%B")+'.jpeg')), monthplace) - """Create a time span using the events_max_range value (in days) - to filter events in that range""" - time_span = today + timedelta(days=int(events_max_range)) + """Add the line seperating the weather and Calendar section""" + image.paste(seperator, seperatorplace) - for icalendars in ical_urls: - decode = str(urlopen(icalendars).read().decode()) - beginAlarmIndex = 0 - while beginAlarmIndex >= 0: - beginAlarmIndex = decode.find('BEGIN:VALARM') - if beginAlarmIndex >= 0: - endAlarmIndex = decode.find('END:VALARM') - decode = decode[:beginAlarmIndex] + decode[endAlarmIndex+12:] - ical = Calendar(decode) - for events in ical.events: - if re.search('RRULE',str(events)) is not None: - r = re.search('RRULE:(.+?)\n',str(events)) - r_start = re.search('DTSTART:(.+?)\n',str(events)) - if r_start is not None: # if r_start is None the format of DTSTART is not recognized - if time.now().month == 12: - r_string=(r.group(1).rstrip()+';UNTIL='+'%04d%02d%02d'+'T000000Z') % (time.now().year+1, 1, 1) - else: - r_string=(r.group(1).rstrip()+';UNTIL='+'%04d%02d%02d'+'T000000Z') % (time.now().year, time.now().month+1, 1) - rule=rrulestr(r_string,dtstart=parse(r_start.group(1))) - for i in rule: - if i.year == time.now().year and i.month == time.now().month and i.day >= time.now().day: - upcoming.append({'date':str(time.now().year) + " " + time.now().strftime('%m')+ " " + str(i.day).zfill(2), 'event':events.name}) - if i.day not in events_this_month: - events_this_month.append(i.day) - # uncomment this line to see fetched recurring events - #print ("Appended recurring event: " + events.name + " on " + str(time.now().year) + " " + time.now().strftime('%m')+ " " + str(i.day).zfill(2)) - else: - if events.begin.date().month == today.month: - if int((events.begin).format('D')) not in events_this_month: - events_this_month.append(int((events.begin).format('D'))) - if today <= events.begin.date() <= time_span: - upcoming.append({'date':events.begin.format('YYYY MM DD'), 'event':events.name}) + """Add weekday-icons (Mon, Tue...) and draw a circle on the + current weekday""" + if (week_starts_on is "Monday"): + calendar.setfirstweekday(calendar.MONDAY) + image.paste(weekmon, weekplace) + image.paste(weekday, weekdaysmon[(time.strftime("%a"))], weekday) + """For those whose week starts on Sunday, change accordingly""" + if (week_starts_on is "Sunday"): + calendar.setfirstweekday(calendar.SUNDAY) + image.paste(weeksun, weekplace) + image.paste(weekday, weekdayssun[(time.strftime("%a"))], weekday) - def takeDate(elem): - return elem['date'] - - upcoming.sort(key=takeDate) - - #print('Upcoming events:',upcoming) #Display fetched events - - def write_text_left(box_width, box_height, text, tuple): - text_width, text_height = font.getsize(text) - while (text_width, text_height) > (box_width, box_height): - text=text[0:-1] - text_width, text_height = font.getsize(text) - y = int((box_height / 2) - (text_height / 2)) - space = Image.new('RGB', (box_width, box_height), color='white') - ImageDraw.Draw(space).text((0, y), text, fill='black', font=font) - image.paste(space, tuple) - - """Write event dates and names on the E-Paper""" - if additional_feature is "events": - if len(cal) is 5: - del upcoming[6:] - - for dates in range(len(upcoming)): - readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b') - write_text(70, 25, readable_date, date_positions['d'+str(dates+1)]) - for events in range(len(upcoming)): - write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+1)]) - + """Create the calendar template of the current month""" + for numbers in cal[0]: + image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['a'+str(cal[0].index(numbers)+1)]) + for numbers in cal[1]: + image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['b'+str(cal[1].index(numbers)+1)]) + for numbers in cal[2]: + image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['c'+str(cal[2].index(numbers)+1)]) + for numbers in cal[3]: + image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['d'+str(cal[3].index(numbers)+1)]) + for numbers in cal[4]: + image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['e'+str(cal[4].index(numbers)+1)]) if len(cal) is 6: - del upcoming[4:] + for numbers in cal[5]: + image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['f'+str(cal[5].index(numbers)+1)]) - for dates in range(len(upcoming)): - readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b') - write_text(70, 25, readable_date, date_positions['d'+str(dates+3)]) - for events in range(len(upcoming)): - write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+3)]) + """Draw a larger square on today's date""" + today = time.day + if today in cal[0]: + image.paste(dateicon, positions['a'+str(cal[0].index(today)+1)], dateicon) + if today in cal[1]: + image.paste(dateicon, positions['b'+str(cal[1].index(today)+1)], dateicon) + if today in cal[2]: + image.paste(dateicon, positions['c'+str(cal[2].index(today)+1)], dateicon) + if today in cal[3]: + image.paste(dateicon, positions['d'+str(cal[3].index(today)+1)], dateicon) + if today in cal[4]: + image.paste(dateicon, positions['e'+str(cal[4].index(today)+1)], dateicon) + if len(cal) is 6 and today in cal[5]: + image.paste(dateicon, positions['f'+str(cal[5].index(today)+1)], dateicon) """Add rss-feeds at the bottom section of the Calendar""" - if additional_feature is "rss": - - def multiline_text(text, max_width): + if bottom_section is "RSS" and rss_feeds != []: + def multiline_text(text, max_width, font=default): lines = [] if font.getsize(text)[0] <= max_width: lines.append(text) @@ -318,70 +246,165 @@ def main(): lines.append(line) return lines + """Parse the RSS-feed titles and save them to a list""" rss_feed = [] for feeds in rss_feeds: text = feedparser.parse(feeds) for posts in text.entries: - rss_feed.append(posts.title) + rss_feed.append(posts.summary)#title + """Shuffle the list to prevent displaying the same titles over and over""" random.shuffle(rss_feed) news = [] - if len(cal) is 5: + """Remove all titles except the first 4 or 6, + depenfing on how much space is available on the """ + if middle_section is 'Calendar' and len(cal) is 5 or middle_section is 'Agenda': del rss_feed[6:] if len(cal) is 6: del rss_feed[4:] + """Split titles of the rss feeds into lines that can fit + on the Calendar and add them to a list""" for title in range(len(rss_feeds)): news.append(multiline_text(rss_feed[title], 384)) news = [j for i in news for j in i] - if len(cal) is 5: + """Display the split lines of the titles""" + if middle_section is 'Calendar' and len(cal) is 5 or middle_section is 'Agenda': if len(news) > 6: del news[6:] for lines in range(len(news)): - write_text_left(384, 25, news[lines], rss_places['line_'+str(lines+1)]) + write_text(384, 25, news[lines], rss_places['line_'+str(lines+1)], alignment = 'left') if len(cal) is 6: if len(news) > 4: del news[4:] for lines in range(len(news)): - write_text_left(384, 25, news[lines], rss_places['line_'+str(lines+3)]) + write_text(384, 25, news[lines], rss_places['line_'+str(lines+3)], alignment = 'left') - """Draw smaller squares on days with events""" - for numbers in events_this_month: - if numbers in cal[0]: - image.paste(eventicon, positions['a'+str(cal[0].index(numbers)+1)], eventicon) - if numbers in cal[1]: - image.paste(eventicon, positions['b'+str(cal[1].index(numbers)+1)], eventicon) - if numbers in cal[2]: - image.paste(eventicon, positions['c'+str(cal[2].index(numbers)+1)], eventicon) - if numbers in cal[3]: - image.paste(eventicon, positions['d'+str(cal[3].index(numbers)+1)], eventicon) - if numbers in cal[4]: - image.paste(eventicon, positions['e'+str(cal[4].index(numbers)+1)], eventicon) - if len(cal) is 6: - if numbers in cal[5]: + + if middle_section is "Calendar" or "Agenda": + """Algorithm for filtering and sorting events from your + iCalendar/s""" + print('Fetching events from your calendar'+'\n') + events_this_month = [] + upcoming = [] + now = arrow.now() + today = time.today() + + """Create a time span using the events_max_range value (in days) + to filter events in that range""" + agenda_max_days = arrow.now().replace(days=+22) + calendar_max_days = arrow.now().replace(days=+int(events_max_range)) + for icalendars in ical_urls: + decode = str(urlopen(icalendars).read().decode()) + beginAlarmIndex = 0 + while beginAlarmIndex >= 0: + beginAlarmIndex = decode.find('BEGIN:VALARM') + if beginAlarmIndex >= 0: + endAlarmIndex = decode.find('END:VALARM') + decode = decode[:beginAlarmIndex] + decode[endAlarmIndex+12:] + ical = Calendar(decode) + for events in ical.events: + if re.search('RRULE',str(events)) is not None: + r = re.search('RRULE:(.+?)\n',str(events)) + r_start = re.search('DTSTART:(.+?)\n',str(events)) + if r_start is not None: # if r_start is None the format of DTSTART is not recognized + if time.now().month == 12: + r_string=(r.group(1).rstrip()+';UNTIL='+'%04d%02d%02d'+'T000000Z') % (time.now().year+1, 1, 1) + else: + r_string=(r.group(1).rstrip()+';UNTIL='+'%04d%02d%02d'+'T000000Z') % (time.now().year, time.now().month+1, 1) + rule=rrulestr(r_string,dtstart=parse(r_start.group(1))) + for i in rule: + if i.year == time.now().year and i.month == time.now().month and i.day >= time.now().day: + upcoming.append(events) + if i.day not in events_this_month: + events_this_month.append(i.day) + # uncomment this line to see fetched recurring events + #print ("Appended recurring event: " + events.name + " on " + str(time.now().year) + " " + time.now().strftime('%m')+ " " + str(i.day).zfill(2)) + else: + if events.begin.date().year == today.year and events.begin.date().month is today.month and int((events.begin).format('D')) not in events_this_month: + events_this_month.append(int((events.begin).format('D'))) + if middle_section is 'Agenda' and events in ical.timeline.included(now, agenda_max_days): + upcoming.append(events) + if middle_section is 'Calendar' and events in ical.timeline.included(now, calendar_max_days): + upcoming.append(events) + + def event_begins(elem): + return elem.begin + + upcoming.sort(key=event_begins) + + if middle_section is 'Agenda': + """For the agenda view, create a list containing dates and events of the next 22 days""" + if len(upcoming) is not 0: + while (upcoming[-1].begin.date().day - now.day) + len(upcoming) >= 22: + del upcoming[-1] + agenda_list = [] + for i in range(22): + date = now.replace(days=+i) + agenda_list.append({'value':date.format('ddd D MMM YY'),'type':'date'}) + for events in upcoming: + if events.begin.date().day == date.day: + if not events.all_day: + agenda_list.append({'value':events.begin.format('HH:mm')+ ' '+ str(events.name), 'type':'timed_event'}) + else: + agenda_list.append({'value':events.name, 'type':'full_day_event'}) + + if rss_feeds != []: + del agenda_list[16:] + image.paste(seperator2, agenda_view_lines['line17']) + else: + del agenda_list[22:] + image.paste(seperator2, agenda_view_lines['line22']) + + for lines in range(len(agenda_list)): + if agenda_list[lines]['type'] is 'date': + write_text(384, 25, agenda_list[lines]['value'], agenda_view_lines['line'+str(lines+1)], font=bold, alignment='left') + image.paste(seperator2, agenda_view_lines['line'+str(lines+1)]) + elif agenda_list[lines]['type'] is 'timed_event': + write_text(384, 25, agenda_list[lines]['value'], agenda_view_lines['line'+str(lines+1)], alignment='left') + else: + write_text(384, 25, agenda_list[lines]['value'], agenda_view_lines['line'+str(lines+1)]) + + if middle_section is 'Calendar': + """Draw smaller squares on days with events""" + for numbers in events_this_month: + if numbers in cal[0]: + image.paste(eventicon, positions['a'+str(cal[0].index(numbers)+1)], eventicon) + if numbers in cal[1]: + image.paste(eventicon, positions['b'+str(cal[1].index(numbers)+1)], eventicon) + if numbers in cal[2]: + image.paste(eventicon, positions['c'+str(cal[2].index(numbers)+1)], eventicon) + if numbers in cal[3]: + image.paste(eventicon, positions['d'+str(cal[3].index(numbers)+1)], eventicon) + if numbers in cal[4]: + image.paste(eventicon, positions['e'+str(cal[4].index(numbers)+1)], eventicon) + if len(cal) is 6 and numbers in cal[5]: image.paste(eventicon, positions['f'+str(cal[5].index(numbers)+1)], eventicon) - """Draw a larger square on today's date""" - today = time.day - if today in cal[0]: - image.paste(dateicon, positions['a'+str(cal[0].index(today)+1)], dateicon) - if today in cal[1]: - image.paste(dateicon, positions['b'+str(cal[1].index(today)+1)], dateicon) - if today in cal[2]: - image.paste(dateicon, positions['c'+str(cal[2].index(today)+1)], dateicon) - if today in cal[3]: - image.paste(dateicon, positions['d'+str(cal[3].index(today)+1)], dateicon) - if today in cal[4]: - image.paste(dateicon, positions['e'+str(cal[4].index(today)+1)], dateicon) - if len(cal) is 6: - if today in cal[5]: - image.paste(dateicon, positions['f'+str(cal[5].index(today)+1)], dateicon) + """Write event dates and names on the E-Paper""" + if bottom_section is "Events": + if len(cal) is 5: + del upcoming[6:] + for dates in range(len(upcoming)): + readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b') + write_text(70, 25, readable_date, date_positions['d'+str(dates+1)]) + for events in range(len(upcoming)): + write_text(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+1)], alignment = 'left') + if len(cal) is 6: + del upcoming[4:] + for dates in range(len(upcoming)): + readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b') + write_text(70, 25, readable_date, date_positions['d'+str(dates+3)]) + for events in range(len(upcoming)): + write_text(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+3)], alignment = 'left') + + """ Map all pixels of the generated image to red, white and black so that the image can be displayed 'correctly' on the E-Paper @@ -407,12 +430,16 @@ def main(): print('______Powering off the E-Paper until the next loop______'+'\n') epd.sleep() + #if middle_section is 'Calendar': del events_this_month del upcoming - if additional_feature is "rss": + if bottom_section is 'RSS': del rss_feed del news + + if middle_section is 'Agenda': + del agenda_list del buffer del image