calendar scripts working
This commit is contained in:
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 |
90
cal/cal.py
90
cal/cal.py
|
@ -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)
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue