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.parser import parse
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 icon_positions_locations import *
@ -41,42 +55,58 @@ EPD_HEIGHT = 384
font = ImageFont.truetype(path+'Assistant-Regular.ttf', 18)
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"""
def main():
calibration_countdown = 60//int(update_interval) - 60//int(datetime.now().strftime("%M"))
while True:
time = datetime.now()
hour = int(time.strftime("%-H"))
month = int(time.now().strftime('%-m'))
year = int(time.now().strftime('%Y'))
mins = int(time.strftime("%M"))
seconds = int(time.strftime("%S"))
for i in range(1):
print('_________Starting new loop___________'+'\n')
"""At the following hours (midnight, midday and 6 pm), perform
a calibration of the display's colours"""
if hour is 0 or hour is 12 or hour is 18:
calibration()
"""Start by printing the date and time for easier debugging"""
print('Date:', time.strftime('%a %-d %b %y'), 'Time: '+time.strftime('%H:%M')+'\n')
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
to 'RGB' and and save the image by uncommenting the image.save
line at the bottom"""
"""Create a blank white page first"""
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)
"""Draw a line seperating the weather and Calendar section"""
"""Add the line seperating the weather and Calendar section"""
image.paste(seperator, seperatorplace)
"""Draw the icons with the weekday-names (Mon, Tue...) and
draw a circle on the current weekday"""
"""Add weekday-icons (Mon, Tue...) and draw a circle on the
current weekday"""
if (week_starts_on == "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 == "Sunday"):
calendar.setfirstweekday(calendar.SUNDAY)
image.paste(weeksun, weekplace)
@ -85,7 +115,6 @@ def main():
"""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)
#print(cal) #-uncomment for debugging with incorrect dates
for numbers in cal[0]:
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)
image.paste(space, tuple)
""" Handling Openweathermap API"""
"""Connect to Openweathermap API to fetch weather data"""
print("Connecting to Openweathermap API servers...")
owm = pyowm.OWM(api_key)
if owm.is_API_online() is True:
@ -149,7 +178,7 @@ def main():
print('Temperature: '+Temperature+' °C')
print('Humidity: '+Humidity+'%')
print('Icon code: '+weathericon)
#print('Icon code: '+weathericon)
print('weather-icon name: '+weathericons[weathericon])
print('Wind speed: '+windspeed+'km/h')
print('Sunrise-time: '+sunrisetime)
@ -157,47 +186,51 @@ def main():
print('Cloudiness: ' + cloudstatus+'%')
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)
"""Drawing the fetched temperature"""
"""Add the temperature icon at it's position"""
image.paste(tempicon, tempplace)
"""Drawing the fetched humidity"""
"""Add the humidity icon and display the humidity"""
image.paste(humicon, humplace)
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)
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)
write_text(50, 35, sunsettime, (249, 35))
"""Drawing the wind icon"""
"""Add the wind icon at it's position"""
image.paste(windicon, windiconspace)
"""Write a short weather description"""
"""Add a short weather description"""
write_text(144, 35, weather_description, (70, 35))
else:
"""If no response was received from the openweathermap
api server, add the cloud with question mark"""
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')
events_this_month = []
upcoming = []
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))
for icalendars in ical_urls:
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_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(fix_e_2) #print iCal as string
ical = Calendar(fix_e_2)
for events in ical.events:
if events.begin.date().month == today.month:
@ -205,37 +238,13 @@ def main():
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})
## 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):
return elem['date']
upcoming.sort(key=takeDate)
# uncomment the following 2 lines to display the fetched events
# from your iCalendar
# print('Upcoming events:')
# print(upcoming)
#print('Upcoming events:',upcoming) #Display fetched events
def write_text_left(box_width, box_height, text, tuple):
text_width, text_height = font.getsize(text)
@ -248,23 +257,77 @@ def main():
image.paste(space, tuple)
"""Write event dates and names on the E-Paper"""
if len(cal) == 5:
del upcoming[6:]
if additional_feature == "events":
if len(cal) == 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)])
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)])
if len(cal) == 6:
del upcoming[4:]
if len(cal) == 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_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+3)])
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)])
"""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"""
for numbers in events_this_month:
@ -299,64 +362,53 @@ def main():
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
2 bands of each pixel (for example, red and green) and re-writes the values in a way so the image only contains
3 colours, without anytyhing in between. As a result, the image looks much more better on E-Paper.
Currently in beta-phase.
Map all pixels of the generated image to red, white and black
so that the image can be displayed 'correctly' on the E-Paper
"""
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):
print('Improving the colours of the generated image.')
# Uncomment following line to save the unprocessed image
# image.save(path+'before.bmp')
width, height = image.size
pixels = image.load()
if display_colours == "bwr":
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)
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')
if display_colours == "bw":
buffer[np.logical_and(r > 240, g > 240)] = [255,255,255] #white
buffer[r < 240] = [0,0,0] #black
improved_image = Image.fromarray(buffer).rotate(270, expand=True)
print('Initialising E-Paper Display')
epd.init()
sleep(5)
print('Converting image to data and sending it to the display')
epd.display_frame(epd.get_frame_buffer(improved_image))
print('Data sent successfully')
print('______Powering off the E-Paper until the next loop______'+'\n')
epd.sleep()
# 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 upcoming[:]
del rss_feed[:]
del news[:]
del buffer
del image
del improved_image
gc.collect()
for i in range(1):
nexthour = ((60 - int(time.strftime("%-M")))*60) - (int(time.strftime("%-S")))
sleep(nexthour)
timings = []
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__':
main()