diff --git a/README.md b/README.md index d5cf749..e2789f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1 @@ # e-inkCal - -this small script is supposed to be run on a raspberry pi with an SPI e-ink display attached to it. -it will then display a weekcalendar on the e-ink display, which is fetched from a calDAV resource. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4fa1c23..0000000 --- a/TODO.md +++ /dev/null @@ -1,9 +0,0 @@ -ToDo: - -1. catching exceptions for caldav unauthorized -2. options: - -w show week calendar - -m show month calendar - -3. include birthdays from another calendar -4. make other views, e.g. month, day, year... diff --git a/calDAVconnector.py b/calDAVconnector.py index 9fc7841..070a7f3 100644 --- a/calDAVconnector.py +++ b/calDAVconnector.py @@ -1,189 +1,135 @@ -from datetime import datetime, date, time, timezone, timedelta +from datetime import datetime +from datetime import date +from datetime import timedelta from PIL import Image,ImageDraw,ImageFont import pickle as p -import numpy as np import requests -import sys, os +import sys +import getopt sys.path.insert(0, './caldav') -sys.path.insert(0, './e-Paper/RaspberryPi_JetsonNano/python/lib') import caldav from caldav.lib.error import AuthorizationError +#look for commandline args -caldav_url = '' -username = '' -password = '' -datafile = '' -selected_cals = [] -birthdaycal = '' -language="EN" -weekday_l_key = "FULL" -draw_date = False -has_color = False -#open config file, load configs +import getopt -if (os.path.isfile("./config")): - configfile = open("./config", "r") - conf = configfile.readlines() - print(conf) - for l in conf: - l = l.strip() - if(l.startswith("server")): - caldav_url = l[7:] - elif(l.startswith("user")): - username = l[5:] - elif(l.startswith("password")): - password = l[9:] - elif(l.startswith("birthdays")): - birthdaycal = l[10:] - elif(l.startswith("datafile")): - datafile = l[9:] - elif(l.startswith("calendars")): - selected_cals = l[10:].split(";") -#here the configs for drawing the calendar start - elif(l.startswith("language")): - language = l[9:] - elif(l.startswith("weekday_format")): - weekday_l_key = l[15:] - elif(l.startswith("draw_date")): - k = l[10:] - trueish = ["true","TRUE","1","True","T"] - draw_date = (k in trueish) - elif(l.startswith("colormode")): - if l[10:] == "2color": - has_color = True -on_e_paper = False -if(on_e_paper): - from waveshare_epd import epd7in5b_V2 +inputfile = '' +outputfile = '' +try: + opts, args = getopt.getopt(sys.argv[1:],"h:i:o",["ifile=","ofile="]) +except getopt.GetoptError: + print('test.py -i -o ') + sys.exit(2) +for opt, arg in opts: + if opt == '-h': + print ('test.py -i -o ') + sys.exit() + elif opt in ("-i", "--ifile"): + inputfile = arg + elif opt in ("-o", "--ofile"): + outputfile = arg +print ('Input file is "', inputfile) +print ('Output file is "', outputfile) + -#print(selected_cals) -#look if server and user are set -if(len(caldav_url) == 0): - print("Please provide a calDAV link") -if(len(username) == 0): - print("Please provide a username") #calDAV setup +caldav_url = 'https://www.kuhl-mann.de/nextcloud/remote.php/dav' +username = 'Justus' +password = 'Alphabeth1forB2;' +timezone = [2,0] timeout = 5 -if not (len(datafile) == 0): - f = open(datafile) +f = open("./calendarlib.p") server_reached = True client_established = True print("Looking for server...") try: - request = requests.get(caldav_url, timeout=timeout) + request = requests.get(caldav_url, timeout=timeout) except (requests.ConnectionError, requests.Timeout) as exception: print("didn't find server, showing data from last successful connection") server_reached = False else: print("found server") + try: client = caldav.DAVClient(url=caldav_url, username=username, password=password) except (Exception) as ex: client_established = False -#check in which time zone we are -tz = timedelta(minutes = round((datetime.now()-datetime.utcnow()).seconds/60)) - if(server_reached and client_established): - #if server is available, download new information from server print("Successfully connected to server, starting to download calendars...") my_principal = client.principal() - calendars_fetched = my_principal.calendars() - calendars = [] - if not (len(selected_cals) == 0 or len(selected_cals[0]) == 0): - for c in calendars_fetched: - if c.name in selected_cals: - calendars.append(c) - else: - calendars = calendars_fetched - print("selected calendars:") - for c in calendars: - print(c.name) - + calendars = my_principal.calendars() + time_events = [] day_events = [] birthdays = [] - #go through all calendars to look for events for c in calendars: current_calendar = my_principal.calendar(name=c.name) events_fetched = current_calendar.date_search( start=datetime.today()-timedelta(days = datetime.today().weekday()), end=datetime.today()+timedelta(days = 6-datetime.today().weekday()), expand=True) + if len(events_fetched)> 0: for event in events_fetched: event_start_str = str(event.vobject_instance.vevent.dtstart)[10:-1] event_end_str = str(event.vobject_instance.vevent.dtend)[8:-1] - #print(event.vobject_instance.vevent) if(event_start_str.startswith("VALUE")): - #if it is an event over a whole day, sort it into the day events - if(not birthdaycal == ''): - if(c.name == birthdaycal): - summary = str(event.vobject_instance.vevent.summary.value) - pieces = summary.split(" ") - age = date.today().year - int(pieces[2][2:-1]) - birthdays.append({ - "START":date(int(event_start_str.split("}")[1].split("-")[0]),int(event_start_str.split("}")[1].split("-")[1]),int(event_start_str.split("}")[1].split("-")[2])), - "END":date(int(event_end_str.split("}")[1].split("-")[0]),int(event_end_str.split("}")[1].split("-")[1]),int(event_end_str.split("}")[1].split("-")[2])), - "SUMMARY":pieces[1]+" "+pieces[0][:-1]+" ("+str(age)+")", - "CALENDAR":c.name - }) - else: - day_events.append({ - "START":date(int(event_start_str.split("}")[1].split("-")[0]),int(event_start_str.split("}")[1].split("-")[1]),int(event_start_str.split("}")[1].split("-")[2])), - "END":date(int(event_end_str.split("}")[1].split("-")[0]),int(event_end_str.split("}")[1].split("-")[1]),int(event_end_str.split("}")[1].split("-")[2])), - "SUMMARY":str(event.vobject_instance.vevent.summary.value), - "CALENDAR":c.name - }) - else: - #otherwise it has to be a time event - sd = event_start_str.split(" ")[0] - st = event_start_str.split(" ")[1] - sh = int(st.split(":")[0])+int(st.split(":")[2][2:4]) - sm = int(st.split(":")[1])+int(st.split(":")[3]) - ss = int(st.split(":")[2][0:1]) - ed = event_end_str.split(" ")[0] - et = event_end_str.split(" ")[1] - eh = int(et.split(":")[0])+int(st.split(":")[2][2:4]) - em = int(et.split(":")[1])+int(et.split(":")[3]) - es = int(et.split(":")[2][0:1]) - time_events.append({ - "START":datetime(int(sd.split("-")[0]),int(sd.split("-")[1]),int(sd.split("-")[2]), hour = sh, minute = sm, second = ss)+tz, - "END":datetime(int(ed.split("-")[0]),int(ed.split("-")[1]),int(ed.split("-")[2]), hour = eh, minute = em, second = es)+tz, + day_events.append({ + "DATE":date(int(event_start_str.split("}")[1].split("-")[0]),int(event_start_str.split("}")[1].split("-")[1]),int(event_start_str.split("}")[1].split("-")[2])), "SUMMARY":str(event.vobject_instance.vevent.summary.value), "CALENDAR":c.name }) - #if the user wants one calendar to be treated as a birthday calendar, these are sorted into an extra library + else: + sd = event_start_str.split(" ")[0] + st = event_start_str.split(" ")[1] + sh = int(st.split(":")[0])+timezone[0]+int(st.split(":")[2][2:4]) + sm = int(st.split(":")[1])+timezone[1]+int(st.split(":")[3]) + ss = int(st.split(":")[2][0:1]) + ed = event_end_str.split(" ")[0] + et = event_end_str.split(" ")[1] + eh = int(et.split(":")[0])+timezone[0]+int(st.split(":")[2][2:4]) + em = int(et.split(":")[1])+timezone[1]+int(et.split(":")[3]) + es = int(et.split(":")[2][0:1]) + time_events.append({ + "START":datetime(int(sd.split("-")[0]),int(sd.split("-")[1]),int(sd.split("-")[2]), hour = sh, minute = sm, second = ss), + "END":datetime(int(ed.split("-")[0]),int(ed.split("-")[1]),int(ed.split("-")[2]), hour = eh, minute = em, second = es), + "SUMMARY":str(event.vobject_instance.vevent.summary.value), + "CALENDAR":c.name + }) + for event in day_events: + if event["CALENDAR"] == "Geburtstage von Kontakten": + pieces = event["SUMMARY"].split(" ") + age = date.today().year - int(pieces[2][2:-1]) + event["SUMMARY"] = pieces[1]+" "+pieces[0][:-1]+" ("+str(age)+")" + birthdays.append(event) + day_events.remove(event) print("Download complete") - - #back up the data received to a local copy so that it can be displayed if needed - if(len(datafile)!= 0): - calendarlib = {"DAY_EVENTS":day_events,"TIME_EVENTS":time_events,"BIRTHDAYS":birthdays} - p.dump( calendarlib, open( "calendarlib.p", "wb" )) - f.close() + calendarlib = {"DAY_EVENTS":day_events,"TIME_EVENTS":time_events,"BIRTHDAYS":birthdays} + #f = open("./calendarlib.p") + p.dump( calendarlib, open( "calendarlib.p", "wb" )) + f.close() else: - #if the server is not available, instead load information from datafile - if(len(datafile)!= 0): - print("Loading caldata from last time...") - calendarlib = p.load(open(datafile,"rb")) - time_events = calendarlib["TIME_EVENTS"] - day_events = calendarlib["DAY_EVENTS"] - birthdays = calendarlib["BIRTHDAYS"] - else: - print("No data available!") - exit() + print("Loading caldata from last time...") + calendarlib = p.load(open("calendarlib.p","rb")) + time_events = calendarlib["TIME_EVENTS"] + day_events = calendarlib["DAY_EVENTS"] + birthdays = calendarlib["BIRTHDAYS"] + + +#get principal file print(birthdays) print(day_events) print(time_events) - #with this information now the week calendar can be painted on a b/w 800x480 bitmap. @@ -209,29 +155,19 @@ def draw_text_90_into (text: str, into, at): # Note that we don't need a mask into.paste (img, at) -#only for testing purposes -has_color = True - #define fonts to be used eventfont = ImageFont.truetype("./resource/bf_mnemonika_regular.ttf", 16) weekdayfont = ImageFont.truetype("./resource/bf_mnemonika_regular.ttf", 16) timefont = ImageFont.truetype("./resource/bf_mnemonika_regular.ttf", 12) -birthdayfont = weekdayfont -if(on_e_paper): - #initialise epd for the e-ink display - epd = epd7in5b_V2.EPD() - epd.init() - epd.Clear() #create image buffer Himage = Image.new('1', (800,480), 255) # 255: clear the frame draw = ImageDraw.Draw(Himage) -if(has_color): - HRimage = Image.new('1', (800,480), 255) # 255: clear the frame - draw_r = ImageDraw.Draw(HRimage) #define language, and if abbreviations for weekdays should be used - +language = "EN" +weekday_l_key = "FULL" +draw_date = True #define grid coordinates upper_border_grid = 0 lower_border_grid = 465 @@ -246,11 +182,6 @@ width_grid = right_border_grid-left_border_grid width_day = round(width_grid/7) height_grid = lower_border_grid-upper_border_grid weekday_height = weekdayfont.getsize("Monday")[1] -if(birthdaycal == ''): - upper_border_writable = upper_border_grid+weekday_height -else: - birthday_height = birthdayfont.getsize("ABCDEFGHIJKLMNOPQRSTUVWXYZÄÜÖabcdefghijklmnpqrstuvwxyzäüö")[1] - upper_border_writable = upper_border_grid+weekday_height+birthday_height+3 two_hour_space = round(2*(lower_border_grid-(upper_border_grid+weekday_height+2))/hours_in_day) last_hour_line = round((hours_in_day-(hours_in_day%2))*two_hour_space/2+upper_border_grid+weekday_height+2) #write down weekdays, makes next step easier @@ -271,79 +202,56 @@ weekdays = { #draw the weekdays monday = datetime.today()-timedelta(days = datetime.today().weekday()) -free_days = [5,6] for j in range(len(weekdays[language][weekday_l_key])): if draw_date: if(max([(weekdayfont.getsize(i+", "+str((monday+timedelta(days=j)).day)+"."+str((monday+timedelta(days=j)).month)+".")[0]>width_day-7) for i in weekdays[language]["FULL"]])): weekday_l_key = "SHORT" - w_str = (weekdays[language][weekday_l_key][j]+", "+str((monday+timedelta(days=j)).day)+"."+str((monday+timedelta(days=j)).month)+".") + draw.text((left_border_grid+j*width_day+5, upper_border_grid), (weekdays[language][weekday_l_key][j]+", "+str((monday+timedelta(days=j)).day)+"."+str((monday+timedelta(days=j)).month)+"."), font = weekdayfont, fill = 0) else: if(max([(weekdayfont.getsize(i)[0]>width_day-7) for i in weekdays[language]["FULL"]])): weekday_l_key = "SHORT" - w_str = (weekdays[language][weekday_l_key][j]) - if has_color and j in free_days: - draw_r.text((left_border_grid+j*width_day+5, upper_border_grid),w_str, font = weekdayfont, fill = 0) - else: - draw.text((left_border_grid+j*width_day+5, upper_border_grid),w_str, font = weekdayfont, fill = 0) + draw.text((left_border_grid+j*width_day+5, upper_border_grid), weekdays[language][weekday_l_key][j], font = weekdayfont, fill = 0) -# draw birthdays -for event in birthdays: - cropped_ev_str = (event["SUMMARY"]) - if (birthdayfont.getsize(cropped_ev_str)[0] > width_day-4): - p_list = cropped_ev_str.split(" ") - p_list[-2] = p_list[-2][0]+"." - cropped_ev_str = "" - for l in p_list: - cropped_ev_str += l+" " - print(cropped_ev_str) - while (birthdayfont.getsize(cropped_ev_str)[0] > width_day-4): - cropped_ev_str = cropped_ev_str.split("(")[0][:-1]+"("+cropped_ev_str.split("(")[1] - d = event["START"].weekday() - if has_color: - draw_r.text((left_border_grid+d*width_day+5, upper_border_grid+weekday_height+2),cropped_ev_str, font = weekdayfont, fill = 0) - else: - draw.text((left_border_grid+d*width_day+5, upper_border_grid+weekday_height+2),cropped_ev_str, font = weekdayfont, fill = 0) #draw a grid -draw.line([(left_border_grid, upper_border_writable+2),(right_border_grid, upper_border_writable+2)], fill=0, width=2) +draw.line([(left_border_grid, upper_border_grid+weekday_height+2),(right_border_grid, upper_border_grid+weekday_height+2)], fill=0, width=2) for y in range(width_day,width_grid,width_day): draw.line([(y+left_border_grid, upper_border_grid),(y+left_border_grid, lower_border_grid)], fill=0, width=2) -for y in range(upper_border_writable+2,lower_border_grid,two_hour_space): +for y in range(upper_border_grid+weekday_height+2,lower_border_grid,two_hour_space): for x in range(left_border_grid, right_border_grid, 6): - draw.line([(x, y), (x+2, y)], fill = 0, width = 1) + draw.line([(x, y), (x+2, y)], fill = 0, width = 1) #draw times for orientation i = 0 -for y in range(upper_border_writable+4,lower_border_grid,two_hour_space): +for y in range(upper_border_grid+weekday_height+4,lower_border_grid,two_hour_space): draw.text((left_border_grid-timefont.getsize(str(i*2+first_hour))[0], y), str(i*2+first_hour), font = timefont, fill = 0) i +=1 -already_an_event = np.zeros((7,lower_border_grid - upper_border_grid)) +events_on_weekday = [0,0,0,0,0,0,0] known_calendars = {"DLRG Kalendar" : "DLRG", "Uni Kalendar" : "UNI", "Persönlich" : "PER"} for event in day_events: - for d in range(event["START"].weekday(),event["END"].weekday()): - row = width_day*d+left_border_grid+4 - if event["CALENDAR"] in known_calendars: - cal = known_calendars[event["CALENDAR"]] - else: - cal = event["CALENDAR"] - if(np.amax(already_an_event[d,:])== 0): - draw.rectangle((row,upper_border_writable+5,row+width_day-6,lower_border_grid-1),fill = 255) - draw.line([(row,upper_border_writable+5),(row,lower_border_grid-1)], width = 4, fill = 0) - draw.line([(row,lower_border_grid-2),(row+width_day-6,lower_border_grid-2)], width = 2, fill = 0) - draw.line([(row,upper_border_writable+5),(row+width_day-6,upper_border_writable+5)], width = 2, fill = 0) - draw.line([(row+width_day-7,upper_border_writable+5),(row+width_day-7,lower_border_grid-1)], width = 2, fill = 0) - wi, hi = eventfont.getsize (event["SUMMARY"]) - draw_text_90_into("["+cal+"] "+event["SUMMARY"], Himage, (row+4,round(height_grid/2-(weekday_height+5)-wi/2))) - else: - wi, hi = eventfont.getsize(event["SUMMARY"]) - draw.line([(row+6+hi,upper_border_writable+9),(row+6+hi,lower_border_grid-4)], width = 2, fill = 0) - draw_text_90_into("["+cal+"] "+event["SUMMARY"], Himage, (row+10+hi,round(height_grid/2-(weekday_height+5)-wi/2))) - already_an_event[d,:] += 1 + row = width_day*event["DATE"].weekday()+left_border_grid+4 + if event["CALENDAR"] in known_calendars: + cal = known_calendars[event["CALENDAR"]] + else: + cal = event["CALENDAR"] + if(events_on_weekday[event["DATE"].weekday()]== 0): + draw.rectangle((row,upper_border_grid+weekday_height+5,row+width_day-6,lower_border_grid-1),fill = 255) + draw.line([(row,upper_border_grid+weekday_height+5),(row,lower_border_grid-1)], width = 4, fill = 0) + draw.line([(row,lower_border_grid-2),(row+width_day-6,lower_border_grid-2)], width = 2, fill = 0) + draw.line([(row,upper_border_grid+weekday_height+5),(row+width_day-6,upper_border_grid+weekday_height+5)], width = 2, fill = 0) + draw.line([(row+width_day-7,upper_border_grid+weekday_height+5),(row+width_day-7,lower_border_grid-1)], width = 2, fill = 0) + wi, hi = eventfont.getsize (event["SUMMARY"]) + draw_text_90_into("["+cal+"] "+event["SUMMARY"], Himage, (row+4,height_grid-(weekday_height+5)-round(wi/2))) + else: + wi, hi = eventfont.getsize(event["SUMMARY"]) + draw.line([(row+6+hi,upper_border_grid+weekday_height+9),(row+6+hi,lower_border_grid-4)], width = 2, fill = 0) + draw_text_90_into("["+cal+"] "+event["SUMMARY"], Himage, (row+10+hi,-round(wi/2))) + events_on_weekday[event["DATE"].weekday()] += 1 for event in time_events: #draw rectangle @@ -351,34 +259,26 @@ for event in time_events: row_start = width_day*event["START"].weekday()+left_border_grid+4 row_end = width_day*event["END"].weekday()+left_border_grid+4 - right_border_event = row_start+width_day-6 - upper_border_event = round(upper_border_writable+5+((lower_border_grid-(upper_border_writable+5))/hours_in_day)*(event["START"].hour-first_hour+(event["START"].minute/60))) - lower_border_event = round(upper_border_writable+5+((lower_border_grid-(upper_border_writable+5))/hours_in_day)*(event["END"].hour-first_hour+(event["END"].minute/60))) - left_border_event = row_start+((np.amax(already_an_event[event["START"].weekday(),upper_border_event:lower_border_event]))*6) - already_an_event[event["START"].weekday(),upper_border_event:lower_border_event] +=1 + left_border_event = row_start+(events_on_weekday[event["START"].weekday()]*(6+(eventfont.getsize(event["SUMMARY"])[1]))) + right_border_event = row_start+width_day-6-((events_on_weekday[event["START"].weekday()])>0)*3 + upper_border_event = round(upper_border_grid+weekday_height+5+((lower_border_grid-(upper_border_grid+weekday_height+5))/hours_in_day)*(event["START"].hour-first_hour+(event["START"].minute/60))) + lower_border_event = round(upper_border_grid+weekday_height+5+((lower_border_grid-(upper_border_grid+weekday_height+5))/hours_in_day)*(event["END"].hour-first_hour+(event["END"].minute/60))) + #blank out everything if (row_start == row_end): draw.rectangle((left_border_event,upper_border_event,right_border_event,lower_border_event),fill = 255) - if(lower_border_event first_hour): - draw.rectangle((left_border_event+((event["END"].weekday()-event["START"].weekday())*width_day),upper_border_writable+5,right_border_event+((event["END"].weekday()-event["START"].weekday())*width_day),lower_border_event),fill = 255) - draw.line([(left_border_event+((event["END"].weekday()-event["START"].weekday())*width_day),upper_border_writable+5),(left_border_event+((event["END"].weekday()-event["START"].weekday())*width_day),lower_border_event)], width = 4, fill = 0) - draw.line([(right_border_event-1+((event["END"].weekday()-event["START"].weekday())*width_day),upper_border_writable+5),(right_border_event-1+((event["END"].weekday()-event["START"].weekday())*width_day),lower_border_event)], width = 2, fill = 0) - if(lower_border_event right_border_event-left_border_event-4): cropped_ev_str = cropped_ev_str[:-1] draw.text((left_border_event+6, lower_border_event+4),cropped_ev_str, font = eventfont, fill = 0) - elif(event["START"].day == event["END"].day and lower_border_event-upper_border_event-2 < hi*2) or lower_border_grid-upper_border_event-2 < hi*2: + elif(lower_border_event-upper_border_event-2 < hi*2): cropped_ev_str = ('{:2d}'.format(event["START"].hour)+":"+'{:02d}'.format(event["START"].minute)+ " " +event["SUMMARY"]) while (eventfont.getsize(cropped_ev_str)[0] > right_border_event-left_border_event-4): cropped_ev_str = cropped_ev_str[:-1] draw.text((left_border_event+6, upper_border_event+4),cropped_ev_str, font = eventfont, fill = 0) - + else: cropped_ev_str = (event["SUMMARY"]) while (eventfont.getsize(cropped_ev_str)[0] > right_border_event-left_border_event-4): @@ -411,14 +311,10 @@ for event in time_events: # draw a line for current date and time now_row = width_day*datetime.now().weekday()+left_border_grid -now_time = round(upper_border_writable+5+(((last_hour_line-(upper_border_writable+5))/(hours_in_day-(hours_in_day%2)))*(datetime.now().hour-first_hour+(datetime.now().minute/60)))) -if(now_time < lower_border_grid and now_time > upper_border_writable+2): - if has_color: - draw_r.line([(now_row,now_time),(now_row+width_day-2,now_time)], width = 2, fill = 0) - draw_r.ellipse((now_row, now_time, now_row+10, now_time+4), fill = 0) - else: - draw.line([(now_row,now_time),(now_row+width_day-2,now_time)], width = 2, fill = 0) - draw.ellipse((now_row, now_time, now_row+10, now_time+4), fill = 0) +now_time = round(upper_border_grid+weekday_height+5+(((last_hour_line-(upper_border_grid+weekday_height+5))/(hours_in_day-(hours_in_day%2)))*(datetime.now().hour-first_hour+(datetime.now().minute/60)))) +if(now_time < lower_border_grid and now_time > upper_border_grid+weekday_height+2): + draw.line([(now_row,now_time),(now_row+width_day-2,now_time)], width = 2, fill = 0) + draw.ellipse((now_row, now_time, now_row+10, now_time+4), fill = 0) #draw warnings, if something went wrong with the connection if(not server_reached): @@ -426,7 +322,4 @@ if(not server_reached): if(not client_established): Himage.paste(Image.open("./resource/unauthorized.png"), (right_border_grid-40,lower_border_grid-40)) Himage.save("./canvas.bmp") -HRimage.save("./r_canvas.bmp") -if(on_e_paper): - epd.display(epd.getbuffer(Himage), epd.getbuffer(HRimage)) \ No newline at end of file