im Januar 2024
Gibt es auch noch eine sinnvollere Anwendung für das E-Paper mit 3 Farben?
Könnte das den Familienkalender darstellen?
OK, der Familienkalender ist als iCal-File verfügbar. Das ganze soll auf einem Raspberry Pi 3 laufen und das E-Paper ansteuern, wie wäre es also mit Python?
Das Kalenderauslesen funktioniert gut mit recurring-ical-events (https://pypi.org/project/recurring-ical-events/). Die Bilder werden mit Pillow erzeugt und dann über die Waveshare-Bibliothek an das E-Paper übertragen.
# python -m venv .
# source ./bin/activate
# pip install recurring-ical-events
# see https://pypi.org/project/recurring-ical-events/
#
# Pillow
# pip install Pillow
#
# for epaper:
# pip install spidev
# pip install rpi.gpio # outdated, does not work on pi 5
# %%
import time
import logging
import re
# for calendar
import icalendar
import recurring_ical_events
import urllib.request
# for graphics
from PIL import Image, ImageDraw, ImageFont
from datetime import date, timedelta
logging.debug('Started')
#%%
# create blocks with dates for the calender. Blocks are tuples (str,int)
def create_blocks(calendar, start_date):
# create blocks
blocks : list[tuple[str, int]] = []
# create events in tuples (text, type)
for a_date in (start_date + timedelta(n) for n in range(20)): # max 14 days
events = recurring_ical_events.of(calendar).at(a_date)
# sort by start date
events = sorted(events, key=sortFun);
# create date block
datetext = a_date.strftime("%a %d.%m.%Y") # e.g. Tue, 02.01.2024
if (a_date != today):
blocks.append((datetext, 1)) # not-totday-block
else:
blocks.append((datetext, 2)) # today block
# iterate over day's events
for event in events:
# draw event text
start = event["DTSTART"].dt
duration = event["DTEND"].dt - event["DTSTART"].dt
title = event["SUMMARY"].title()
# remove non-printable chars
title = re.sub('[^A-Za-z0-9 .,:äöü?ÖÄÜ\\-_]+', '', title.strip()).strip()
if start.hour == 0 and start.minute == 0: # full-day events are at 0:00
text = title
else:
text = start.strftime("%H:%M") + " " + title
if "ATTENDEE" in event: # this is someone's event!
att_raw = event["ATTENDEE"];
if isinstance(att_raw, str): # handle single
if att_raw in attendees: # use translation map
text = text + " " + attendees[att_raw]
else: # handle list
for att in att_raw:
if att in attendees: # use translation map
text = text + " " + attendees[att]
blocks.append((text, 0))
return blocks
# %%
url = "https://app.klender.nl/b/ics/xyz.ics"
attendees = {
"MAILTO:m3@email.de" : "M3",
"MAILTO:m2@email.de" : "M2",
"MAILTO:m1@email.de" : "M1",
"MAILTO:t@email.de" : "T",
"MAILTO:a@email.de" : "A"
}
ical_string = urllib.request.urlopen(url).read()
calendar = icalendar.Calendar.from_ical(ical_string)
logging.debug('Fetched calendar')
# sort by start date
sortFun = lambda event : event["DTSTART"].dt
today = date.today()
start_date = today - timedelta(days=1) #timedelta(days=today.weekday())
end_date = start_date + timedelta(days=6)
# %%
# Image stuff
# image = b/w image for black
# imageRed = b/w image for red
# rotate images for painting in portrait mode
width = 480
height = 648
image = Image.new('1', (width, height), 255) # 648*480
image_red = Image.new('1', (width, height), 255) # 648*480 HRYimage: red or yellow image
logging.debug('Created images')
# Drawing and Font
img_draw = ImageDraw.Draw(image)
img_red_draw = ImageDraw.Draw(image_red)
# Use some nice fonts
font = ImageFont.truetype("AtariST8x16SystemFont.ttf", 16)
font_header = ImageFont.truetype("Inkfree.ttf", 48)
#%%
blocks = create_blocks(calendar, start_date)
# %%
# iterate over blocks and draw
img_draw.rectangle((0,0,480,648), outline='white', fill='white')
img_red_draw.rectangle((0,0,480,648), outline='white', fill='white')
(x,y,w,h) = font_header.getbbox("Tagesprophet")
img_draw.text(((width - w)/2, 0), "Tagesprophet", fill="black", font=font_header)
startx = 10
starty = h + 5
oy = starty# offset x
ox = startx # offset y
pady = 4
logging.debug('Started loop images')
for block in blocks: # max 14 days
# get data from block
text = block[0]
type = block[1]
if len(text) > 27:
text = text[0:27]
(x,y,w,h) = font.getbbox(text)
# draw red background box, but remove text
if type == 1 or type == 2:
if oy != starty: # add extra padding if not at top
oy += pady
if oy + h > 610: # make sure there is enough space below headers
oy = 621
if oy > 620:
if ox == startx : # first column is full, start new day in second column
oy = starty
ox = width / 2 + startx
else:
break; # reached end of second column
if type == 1: # header, not today
img_red_draw.rectangle((ox, oy+1, ox+220, oy +1 + h + pady + 3), fill="black")
img_red_draw.text((ox + 3, oy+3), text, fill="white", font=font)
img_draw.rectangle((ox, oy+1, ox+220, oy +1 + h + pady + 3), outline="black")
img_draw.text((ox + 3, oy+3), text, fill="black", font=font)
oy += h + pady + 6
elif type == 2: # header, today
img_draw.rectangle((ox, oy+1, ox+220, oy +1 + h + pady + 3), fill="black")
img_draw.text((ox + 3, oy+3), text, fill="white", font=font)
oy += h + pady + 6
elif type == 0: # event
img_draw.text((ox, oy), text, fill="black", font=font)
oy += h + pady
#%%
# for Debugging:
#image.show()
#image_red.show()
logging.debug('Writing to display')
# for display
from waveshare_epd import epd5in83b_V2
# de-rotate images for displaying
image = image.rotate(90, expand=True)
image_red = image_red.rotate(90, expand=True)
# copy to epaper
# create Display
epd = epd5in83b_V2.EPD()
epd.init()
epd.Clear()
time.sleep(1)
epd.display(epd.getbuffer(image), epd.getbuffer(image_red))
time.sleep(2)