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:
		| @@ -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: | ||||
|             """Start by printing the date and time for easier debugging""" | ||||
|             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) | ||||
|  | ||||
|             print('Date:', time.strftime('%a %-d %b %y')+', Time: '+time.strftime('%H:%M')+'\n') | ||||
|  | ||||
|             """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,6 +257,7 @@ def main(): | ||||
|                 image.paste(space, tuple) | ||||
|  | ||||
|             """Write event dates and names on the E-Paper""" | ||||
|             if additional_feature == "events": | ||||
|                 if len(cal) == 5: | ||||
|                     del upcoming[6:] | ||||
|  | ||||
| @@ -266,6 +276,59 @@ def main(): | ||||
|                     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: | ||||
|                 if numbers in cal[0]: | ||||
| @@ -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 | ||||
|             """ | ||||
|  | ||||
|             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() | ||||
|             buffer = np.array(image) | ||||
|             r,g,b = buffer[:,:,0], buffer[:,:,1], buffer[:,:,2] | ||||
|             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) | ||||
|                 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 | ||||
|  | ||||
|             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') | ||||
|                 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') | ||||
|                 print('This will take about a minute...'+'\n') | ||||
|                 epd.display_frame(epd.get_frame_buffer(image.rotate(270, expand=1))) | ||||
|             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() | ||||
|                 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() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user