Startseite bisherige Projekte Tools/Snippets Bücherempfehlungen Publikationen Impressum Datenschutzerklärung

Kalender auf dem E-Paper

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)



Impressum - Datenschutzerklärung