calendar scripts working

This commit is contained in:
pepev-nrt 2025-02-01 22:47:52 +01:00
parent a9c7381977
commit 24b1b939c6
4 changed files with 189 additions and 25 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,90 @@
#!/usr/bin python3
# -*- coding: utf-8 -*-
"""
# TODO: Comment this
"""
import caldav
from datetime import date, datetime, timedelta
import pytz
import vobject
class CalendarHelper:
def __init__(self, caldavURL: str, caldavUser: str, caldavPassword: str, timezone="Europe/Madrid", caldavBlacklist=[""]):
self.caldavURL = caldavURL
self.caldavUser = caldavUser
self.caldavPassword = caldavPassword
self.caldavBlacklist = caldavBlacklist
self.timezone = timezone
def fetch_cals(self):
with caldav.DAVClient(
url=self.caldavURL,
username=self.caldavUser,
password=self.caldavPassword
) as client:
principal = client.principal()
# Get user's calendars
calendars = principal.calendars()
# Upcoming events on all calendars
tz = pytz.timezone(self.timezone)
now = datetime.now(tz=tz)
end = now + timedelta(days=30)
all_events = []
for calendar in calendars:
#print(str(calendar))
if str(calendar) not in self.caldavBlacklist:
events = calendar.search(start=now, end=end, expand=True)
all_events.extend(events)
# Parse events into parsed_vents list
parsed_events = []
for event in all_events:
parsed_events.append(self.parse_event(event.data))
for event in parsed_events:
# If datetime.datetime, do nothing, if datetime.time, convert it to datetime.datetime
if not isinstance(event["DTSTART"], datetime):
event["DTSTART"] = date.strftime(event["DTSTART"], '%Y-%m-%d, %H:%M:%S')
event["DTSTART"] = datetime.strptime(event["DTSTART"], '%Y-%m-%d, %H:%M:%S')
else:
event["DTSTART"] = datetime.strftime(event["DTSTART"], '%Y-%m-%d, %H:%M:%S')
event["DTSTART"] = datetime.strptime(event["DTSTART"], '%Y-%m-%d, %H:%M:%S')
#event["DTSTART"] = datetime.combine(event["DTSTART"], datetime.min.time())
#print(parsed_events)
# Sort the list of dictionaries by DTSTART
sorted_events = sorted(parsed_events, key=lambda x: x['DTSTART'])
#return sorted_events
# Print the sorted list of events
#for event in sorted_events:
#print(f'{event["DTSTART"]} --- {event["SUMMARY"]}')
return sorted_events
def parse_event(self, event_str: str):
calendar = vobject.readOne(event_str)
event = calendar.vevent
event_dict = {}
for component in event.getChildren():
try:
event_dict[component.name] = component.value
except:
continue
return(event_dict)

View file

@ -12,5 +12,10 @@
"latitude": 40.4084, "latitude": 40.4084,
"longitude": -3.6876, "longitude": -3.6876,
"timezone": "Europe/Madrid", "timezone": "Europe/Madrid",
"locales": "es_ES.UTF-8" "locales": "es_ES.UTF-8",
"caldavURL": "",
"caldavUser": "",
"caldavPassword": "",
"caldavBlacklist": "blocked cal 1, blocked cal 2"
} }

View file

@ -18,6 +18,7 @@ import json
import logging import logging
from PIL import Image,ImageDraw,ImageFont from PIL import Image,ImageDraw,ImageFont
from cal.cal import CalendarHelper
from weather.weather import WeatherHelper from weather.weather import WeatherHelper
@ -29,19 +30,29 @@ def main():
isDisplayConected = config['isDisplayConected'] # set to true when debugging rendering without displaying to screen isDisplayConected = config['isDisplayConected'] # set to true when debugging rendering without displaying to screen
screenWidth = config['screenWidth'] # Width of E-Ink display. Default is landscape. Need to rotate image to fit. screenWidth = config['screenWidth'] # Width of E-Ink display. Default is landscape. Need to rotate image to fit.
screenHeight = config['screenHeight'] # Height of E-Ink display. Default is landscape. Need to rotate image to fit. screenHeight = config['screenHeight'] # Height of E-Ink display. Default is landscape. Need to rotate image to fit.
imageWidth = config['imageWidth'] # Width of image to be generated for display. imageWidth = config['imageWidth'] # Width of image to be generated for display.
imageHeight = config['imageHeight'] # Height of image to be generated for display. imageHeight = config['imageHeight'] # Height of image to be generated for display.
rotateAngle = config['rotateAngle'] # If image is rendered in portrait orientation, angle to rotate to fit screen rotateAngle = config['rotateAngle'] # If image is rendered in portrait orientation, angle to rotate to fit screen
hourFormat = config['hourFormat'] # The format the hour will be displayed. eg. 13:02 or 01:02 PM hourFormat = config['hourFormat'] # The format the hour will be displayed. eg. 13:02 or 01:02 PM
latitude = config['latitude'] # A float. The latitude for the Weather API. latitude = config['latitude'] # A float. The latitude for the Weather API.
longitude = config['longitude'] # A float. The longitude for the Weather API. longitude = config['longitude'] # A float. The longitude for the Weather API.
timezone = config['timezone'] # The timezone is necesary for the Weather API timezone = config['timezone'] # The timezone is necesary for the Weather API
locales = config['locales'] # Set the locales for the month name locales = config['locales'] # Set the locales for the month name
weatherService = WeatherHelper(latitude, longitude, timezone) caldavURL = config['caldavURL'] # URL to Caldav calendar. eg. 'https://nextcloud.example/remote.php/dav'
weather_data = weatherService.fetch_open_meteo_data() caldavUser = config['caldavUser'] # Caldav username
caldavPassword = config['caldavPassword'] # Caldav password
caldavBlacklist = config ['caldavBlacklist'] # Caldav list of calendars to be ommited
# Convert json "str1, str2" to a python list
caldavBlacklist = caldavBlacklist.split(',')
for i in range(len(caldavBlacklist)):
caldavBlacklist[i-1] = caldavBlacklist[i-1].strip()
locale.setlocale(locale.LC_TIME, locales) locale.setlocale(locale.LC_TIME, locales)
@ -52,6 +63,8 @@ def main():
time = dt.datetime.now().strftime("%H:%M") time = dt.datetime.now().strftime("%H:%M")
day = dt.datetime.now().strftime("%A, %d %b") day = dt.datetime.now().strftime("%A, %d %b")
tomorrow = dt.datetime.now() + dt.timedelta(days=1)
tomorrow = dt.datetime.strftime(tomorrow, "%A, %d %b")
assets = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'assets') assets = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'assets')
@ -67,6 +80,7 @@ def main():
image = Image.new('1', (imageWidth, imageHeight), 255) # 255: clear the frame image = Image.new('1', (imageWidth, imageHeight), 255) # 255: clear the frame
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
# Draw todays date
draw.text((0, 0), f"{day}. {time}", font = font16_bold, fill = 0) # draw the current time in the top left corner draw.text((0, 0), f"{day}. {time}", font = font16_bold, fill = 0) # draw the current time in the top left corner
# Frames # Frames
@ -77,14 +91,17 @@ def main():
# Calendar icon # Calendar icon
cal_coordinates_x = 207 cal_coordinates_x = 207
cal_coordinates_y = 2 cal_coordinates_y = 2
cal_icon = Image.open(os.path.join(assets, "cal-icon3.bmp")) # calendar icon cal_icon = Image.open(os.path.join(assets, "cal-icon3.bmp")) # calendar icon
# This seems complicates, but it just draw a white rectangle bellow the calendar # This seems complicates, but it just draw a white rectangle bellow the calendar
draw.rectangle([(cal_coordinates_x-2, cal_coordinates_y),(cal_coordinates_x + 28, cal_coordinates_y + 30)], fill = 255) draw.rectangle([(cal_coordinates_x-2, cal_coordinates_y),(cal_coordinates_x + 28, cal_coordinates_y + 30)], fill = 255)
image.paste(cal_icon, (cal_coordinates_x, cal_coordinates_y)) image.paste(cal_icon, (cal_coordinates_x, cal_coordinates_y))
# LEFT SEGMENT (WEATHER) # LEFT SEGMENT (WEATHER)
try:
# Todays information # Todays information
weatherService = WeatherHelper(latitude, longitude, timezone)
weather_data = weatherService.fetch_open_meteo_data()
today_icon = weatherService.iconize_weather(weather_data["current_weather_code"]) today_icon = weatherService.iconize_weather(weather_data["current_weather_code"])
today_icon = Image.open(os.path.join(weather_icons, today_icon)) today_icon = Image.open(os.path.join(weather_icons, today_icon))
image.paste(today_icon, (29,20)) image.paste(today_icon, (29,20))
@ -110,9 +127,61 @@ def main():
day_after_temperature = f"{str(weather_data['day_after_temperature_2m_max'])}/{str(weather_data['day_after_temperature_2m_min'])}°" day_after_temperature = f"{str(weather_data['day_after_temperature_2m_max'])}/{str(weather_data['day_after_temperature_2m_min'])}°"
draw.text((52,106), day_after_temperature, font = font16_bold, fill = 0) draw.text((52,106), day_after_temperature, font = font16_bold, fill = 0)
except:
pass
# TODO: desing an error icon and
# RIGHT SEGMENT (CALENDAR/AGENDA) # RIGHT SEGMENT (CALENDAR/AGENDA)
# TODO: writing this part and also the logic part try:
calService = CalendarHelper(caldavURL, caldavUser, caldavPassword, timezone=timezone, caldavBlacklist=caldavBlacklist)
calendar_data = calService.fetch_cals()
#print(calendar_data)
last_date: str = None
max_agenda_lines = 7
agenda_lines = 0
agenda_x = 96
agenda_y = 16
font_size = 13
for event in calendar_data:
text: str = ''
bold: bool = False
event_date = dt.datetime.strftime(event["DTSTART"], "%A, %d %b")
if event_date != last_date:
last_date = event_date
# check if date is today or tomorrow
if last_date == day:
text = "Hoy"
bold = True
elif last_date == tomorrow:
text = "Mañana"
bold = True
else:
text = last_date
bold = True
coords_y = font_size * agenda_lines + agenda_y
if coords_y >= screenHeight - font_size*2:
break
draw.text((agenda_x, coords_y), text, font = font16_bold, fill = 0)
agenda_lines += 1
last_date = event_date
text = f'-{event["SUMMARY"]}'
coords_y = font_size * agenda_lines + agenda_y
if coords_y >= screenHeight - font_size:
break
draw.text((agenda_x, coords_y), text, font = font16, fill = 0)
agenda_lines += 1
except:
pass
# TODO: clean the code and comment
# draw.line([(0,50),(50,0)], fill = 0,width = 1) # draw.line([(0,50),(50,0)], fill = 0,width = 1)
# draw.chord((10, 60, 50, 100), 0, 360, fill = 0) # draw.chord((10, 60, 50, 100), 0, 360, fill = 0)