New update with lots of improvements

Added RSS-feedparser. Added support for user-defined display-update intervals. Removed support for recurring events (temporarily).
Imporved processing time for the generated image form nearly 30 seconds to less than a second. Improved readability of docstrings.
This commit is contained in:
Ace 2019-03-11 18:12:15 +01:00 committed by GitHub
parent a4178f8e62
commit 15d37b64f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -14,6 +14,20 @@ from time import sleep
from dateutil.rrule import * from dateutil.rrule import *
from dateutil.parser import parse from dateutil.parser import parse
import re 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 python-numpy")
from settings import * from settings import *
from icon_positions_locations import * from icon_positions_locations import *
@ -41,42 +55,58 @@ EPD_HEIGHT = 384
font = ImageFont.truetype(path+'Assistant-Regular.ttf', 18) font = ImageFont.truetype(path+'Assistant-Regular.ttf', 18)
im_open = Image.open im_open = Image.open
possible_update_values = [10, 15, 20, 30, 60]
if int(update_interval) not in possible_update_values:
print('Selected update-interval: ',update_interval, 'minutes')
print('Please select an update interval from these values:', possible_update_values)
raise ValueError
"""Main loop starts from here""" """Main loop starts from here"""
def main(): def main():
calibration_countdown = 60//int(update_interval) - 60//int(datetime.now().strftime("%M"))
while True: while True:
time = datetime.now() time = datetime.now()
hour = int(time.strftime("%-H")) hour = int(time.strftime("%-H"))
month = int(time.now().strftime('%-m')) month = int(time.now().strftime('%-m'))
year = int(time.now().strftime('%Y')) year = int(time.now().strftime('%Y'))
mins = int(time.strftime("%M"))
seconds = int(time.strftime("%S"))
for i in range(1): for i in range(1):
print('_________Starting new loop___________'+'\n') print('_________Starting new loop___________'+'\n')
"""At the following hours (midnight, midday and 6 pm), perform """Start by printing the date and time for easier debugging"""
a calibration of the display's colours""" print('Date:', time.strftime('%a %-d %b %y'), 'Time: '+time.strftime('%H:%M')+'\n')
if hour is 0 or hour is 12 or hour is 18:
calibration()
print('Date:', time.strftime('%a %-d %b %y')+', Time: '+time.strftime('%H:%M')+'\n') """At the hours specified in the settings file,
calibrate the display to prevent ghosting"""
if hour in calibration_hours:
print('Current countdown:',calibration_countdown)
calibration_countdown -= 1
print('counts left until calibration:',calibration_countdown)
if calibration_countdown == 1:
calibration()
print('Resetting Countdown')
calibration_countdown = 60//int(update_interval)
print('Calibration countdown:',calibration_countdown)
"""Create a blank white page, for debugging, change mode to """Create a blank white page first"""
to 'RGB' and and save the image by uncommenting the image.save
line at the bottom"""
image = Image.new('RGB', (EPD_HEIGHT, EPD_WIDTH), 'white') image = Image.new('RGB', (EPD_HEIGHT, EPD_WIDTH), 'white')
"""Draw the icon with the current month's name""" """Add the icon with the current month's name"""
image.paste(im_open(mpath+str(time.strftime("%B")+'.jpeg')), monthplace) image.paste(im_open(mpath+str(time.strftime("%B")+'.jpeg')), monthplace)
"""Draw a line seperating the weather and Calendar section""" """Add the line seperating the weather and Calendar section"""
image.paste(seperator, seperatorplace) image.paste(seperator, seperatorplace)
"""Draw the icons with the weekday-names (Mon, Tue...) and """Add weekday-icons (Mon, Tue...) and draw a circle on the
draw a circle on the current weekday""" current weekday"""
if (week_starts_on == "Monday"): if (week_starts_on == "Monday"):
calendar.setfirstweekday(calendar.MONDAY) calendar.setfirstweekday(calendar.MONDAY)
image.paste(weekmon, weekplace) image.paste(weekmon, weekplace)
image.paste(weekday, weekdaysmon[(time.strftime("%a"))], weekday) image.paste(weekday, weekdaysmon[(time.strftime("%a"))], weekday)
"""For those whose week starts on Sunday, change accordingly"""
if (week_starts_on == "Sunday"): if (week_starts_on == "Sunday"):
calendar.setfirstweekday(calendar.SUNDAY) calendar.setfirstweekday(calendar.SUNDAY)
image.paste(weeksun, weekplace) image.paste(weeksun, weekplace)
@ -85,7 +115,6 @@ def main():
"""Using the built-in calendar function, draw icons for each """Using the built-in calendar function, draw icons for each
number of the month (1,2,3,...28,29,30)""" number of the month (1,2,3,...28,29,30)"""
cal = calendar.monthcalendar(time.year, time.month) cal = calendar.monthcalendar(time.year, time.month)
#print(cal) #-uncomment for debugging with incorrect dates
for numbers in cal[0]: for numbers in cal[0]:
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['a'+str(cal[0].index(numbers)+1)]) image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['a'+str(cal[0].index(numbers)+1)])
@ -115,7 +144,7 @@ def main():
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
image.paste(space, tuple) image.paste(space, tuple)
""" Handling Openweathermap API""" """Connect to Openweathermap API to fetch weather data"""
print("Connecting to Openweathermap API servers...") print("Connecting to Openweathermap API servers...")
owm = pyowm.OWM(api_key) owm = pyowm.OWM(api_key)
if owm.is_API_online() is True: if owm.is_API_online() is True:
@ -149,7 +178,7 @@ def main():
print('Temperature: '+Temperature+' °C') print('Temperature: '+Temperature+' °C')
print('Humidity: '+Humidity+'%') print('Humidity: '+Humidity+'%')
print('Icon code: '+weathericon) #print('Icon code: '+weathericon)
print('weather-icon name: '+weathericons[weathericon]) print('weather-icon name: '+weathericons[weathericon])
print('Wind speed: '+windspeed+'km/h') print('Wind speed: '+windspeed+'km/h')
print('Sunrise-time: '+sunrisetime) print('Sunrise-time: '+sunrisetime)
@ -157,47 +186,51 @@ def main():
print('Cloudiness: ' + cloudstatus+'%') print('Cloudiness: ' + cloudstatus+'%')
print('Weather description: '+weather_description+'\n') print('Weather description: '+weather_description+'\n')
"""Drawing the fetched weather icon""" """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)
"""Drawing the fetched temperature""" """Add the temperature icon at it's position"""
image.paste(tempicon, tempplace) image.paste(tempicon, tempplace)
"""Drawing the fetched humidity""" """Add the humidity icon and display the humidity"""
image.paste(humicon, humplace) image.paste(humicon, humplace)
write_text(50, 35, Humidity + " %", (334, 35)) write_text(50, 35, Humidity + " %", (334, 35))
"""Drawing the fetched sunrise time""" """Add the sunrise icon and display the sunrise time"""
image.paste(sunriseicon, sunriseplace) image.paste(sunriseicon, sunriseplace)
write_text(50, 35, sunrisetime, (249, 0)) write_text(50, 35, sunrisetime, (249, 0))
"""Drawing the fetched sunset time""" """Add the sunset icon and display the sunrise time"""
image.paste(sunseticon, sunsetplace) image.paste(sunseticon, sunsetplace)
write_text(50, 35, sunsettime, (249, 35)) write_text(50, 35, sunsettime, (249, 35))
"""Drawing the wind icon""" """Add the wind icon at it's position"""
image.paste(windicon, windiconspace) image.paste(windicon, windiconspace)
"""Write a short weather description""" """Add a short weather description"""
write_text(144, 35, weather_description, (70, 35)) write_text(144, 35, weather_description, (70, 35))
else: else:
"""If no response was received from the openweathermap
api server, add the cloud with question mark"""
image.paste(no_response, wiconplace) image.paste(no_response, wiconplace)
"""Filter upcoming events from your iCalendar/s""" """Algorithm for filtering and sorting events from your
iCalendar/s"""
print('Fetching events from your calendar'+'\n') print('Fetching events from your calendar'+'\n')
events_this_month = [] events_this_month = []
upcoming = [] upcoming = []
today = date.today() today = date.today()
"""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)) time_span = today + timedelta(days=int(events_max_range))
for icalendars in ical_urls: for icalendars in ical_urls:
decode = str(urlopen(icalendars).read().decode()) decode = str(urlopen(icalendars).read().decode())
fix_e_1 = decode.replace('BEGIN:VALARM\r\nACTION:NONE','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:') fix_e_1 = decode.replace('BEGIN:VALARM\r\nACTION:NONE','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:')
fix_e_2 = fix_e_1.replace('BEGIN:VALARM\r\nACTION:EMAIL','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:') fix_e_2 = fix_e_1.replace('BEGIN:VALARM\r\nACTION:EMAIL','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:')
# uncomment line below to display your calendar in ical format # print(fix_e_2) #print iCal as string
# print(fix_e_2)
ical = Calendar(fix_e_2) ical = Calendar(fix_e_2)
for events in ical.events: for events in ical.events:
if events.begin.date().month == today.month: if events.begin.date().month == today.month:
@ -205,37 +238,13 @@ def main():
events_this_month.append(int((events.begin).format('D'))) events_this_month.append(int((events.begin).format('D')))
if today <= events.begin.date() <= time_span: if today <= events.begin.date() <= time_span:
upcoming.append({'date':events.begin.format('YYYY MM DD'), 'event':events.name}) upcoming.append({'date':events.begin.format('YYYY MM DD'), 'event':events.name})
## 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 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})
def takeDate(elem): def takeDate(elem):
return elem['date'] return elem['date']
upcoming.sort(key=takeDate) upcoming.sort(key=takeDate)
# uncomment the following 2 lines to display the fetched events #print('Upcoming events:',upcoming) #Display fetched events
# from your iCalendar
# print('Upcoming events:')
# print(upcoming)
def write_text_left(box_width, box_height, text, tuple): def write_text_left(box_width, box_height, text, tuple):
text_width, text_height = font.getsize(text) text_width, text_height = font.getsize(text)
@ -248,23 +257,77 @@ def main():
image.paste(space, tuple) image.paste(space, tuple)
"""Write event dates and names on the E-Paper""" """Write event dates and names on the E-Paper"""
if len(cal) == 5: if additional_feature == "events":
del upcoming[6:] if len(cal) == 5:
del upcoming[6:]
for dates in range(len(upcoming)): for dates in range(len(upcoming)):
readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b') 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)]) write_text(70, 25, readable_date, date_positions['d'+str(dates+1)])
for events in range(len(upcoming)): for events in range(len(upcoming)):
write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+1)]) write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+1)])
if len(cal) == 6: if len(cal) == 6:
del upcoming[4:] del upcoming[4:]
for dates in range(len(upcoming)): for dates in range(len(upcoming)):
readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b') 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)]) write_text(70, 25, readable_date, date_positions['d'+str(dates+3)])
for events in range(len(upcoming)): for events in range(len(upcoming)):
write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+3)]) write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+3)])
"""Add rss-feeds at the bottom section of the Calendar"""
if additional_feature == "rss":
def multiline_text(text, max_width):
lines = []
if font.getsize(text)[0] <= max_width:
lines.append(text)
else:
words = text.split(' ')
i = 0
while i < len(words):
line = ''
while i < len(words) and font.getsize(line + words[i])[0] <= max_width:
line = line + words[i] + " "
i += 1
if not line:
line = words[i]
i += 1
lines.append(line)
return lines
rss_feed = []
for feeds in rss_feeds:
text = feedparser.parse(feeds)
for posts in text.entries:
rss_feed.append(posts.title)
random.shuffle(rss_feed)
news = []
if len(cal) == 5:
del rss_feed[:6]
if len(cal) == 6:
del rss_feed[:4]
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) == 5:
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)])
if len(cal) == 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)])
"""Draw smaller squares on days with events""" """Draw smaller squares on days with events"""
for numbers in events_this_month: for numbers in events_this_month:
@ -299,64 +362,53 @@ def main():
image.paste(dateicon, positions['f'+str(cal[5].index(today)+1)], dateicon) image.paste(dateicon, positions['f'+str(cal[5].index(today)+1)], dateicon)
""" """
The function below will take care of displaying the image correctly on E-Paper-Displays. It works by analysing Map all pixels of the generated image to red, white and black
2 bands of each pixel (for example, red and green) and re-writes the values in a way so the image only contains so that the image can be displayed 'correctly' on the E-Paper
3 colours, without anytyhing in between. As a result, the image looks much more better on E-Paper.
Currently in beta-phase.
""" """
buffer = np.array(image)
r,g,b = buffer[:,:,0], buffer[:,:,1], buffer[:,:,2]
if display_colours == "bwr":
buffer[np.logical_and(r > 240, g > 240)] = [255,255,255] #white
buffer[np.logical_and(r > 240, g < 240)] = [255,0,0] #red
buffer[np.logical_and(r != 255, r ==g )] = [0,0,0] #black
def display_corrected_image(image): if display_colours == "bw":
print('Improving the colours of the generated image.') buffer[np.logical_and(r > 240, g > 240)] = [255,255,255] #white
# Uncomment following line to save the unprocessed image buffer[r < 240] = [0,0,0] #black
# image.save(path+'before.bmp')
width, height = image.size improved_image = Image.fromarray(buffer).rotate(270, expand=True)
pixels = image.load() print('Initialising E-Paper Display')
if display_colours == "bwr": epd.init()
for x in range(width): sleep(5)
for y in range(height): print('Converting image to data and sending it to the display')
pixel = image.getpixel((x, y)) epd.display_frame(epd.get_frame_buffer(improved_image))
red = pixel[0] print('Data sent successfully')
green = pixel[1] print('______Powering off the E-Paper until the next loop______'+'\n')
if red > 240 and green > 240: #white epd.sleep()
pixels[x, y] = (255, 255, 255)
elif red > 250 and green < 180: #red
pixels[x, y] = (255, 0, 0)
else:
pixels[x, y] = (0, 0, 0)
if display_colours == "bw":
for x in range(width):
for y in range(height):
pixel = image.getpixel((x, y))
red = pixel[0]
green = pixel[1]
if red > 240 and green > 240: #white
pixels[x, y] = (255, 255, 255)
else:
pixels[x, y] = (0, 0, 0)
print('Conversion finished. Enjoy a crisp image on the E-Paper')
# Uncomment following line to save the processed image
# image.save(path+'after.bmp')
print('Initialising E-Paper Display')
epd.init()
sleep(5)
print('Converting image to data and sending it to the display')
print('This will take about a minute...'+'\n')
epd.display_frame(epd.get_frame_buffer(image.rotate(270, expand=1)))
print('Data sent successfully')
print('______Powering off the E-Paper until the next loop______'+'\n')
epd.sleep()
del image
display_corrected_image(image)
del events_this_month[:] del events_this_month[:]
del upcoming[:] del upcoming[:]
del rss_feed[:]
del news[:]
del buffer
del image
del improved_image
gc.collect()
for i in range(1): for i in range(1):
nexthour = ((60 - int(time.strftime("%-M")))*60) - (int(time.strftime("%-S"))) timings = []
sleep(nexthour) updates_per_hour = 60//int(update_interval)
for times in range(updates_per_hour):
interval = 60-(times*int(update_interval))
if interval >= mins:
time_left = (mins-interval)*(-1)
timings.append(time_left)
next_update = min(timings)*60 + (60-seconds)
print(min(timings),'Minutes and ', (60-seconds),'Seconds left until next loop')
del timings
print('sleeping for',next_update, 'seconds')
sleep(next_update)
if __name__ == '__main__': if __name__ == '__main__':
main() main()