For anyone that comes across this in the future, here is my solution which was created based on the code provided here: Google calendar get more than one event - #11 by der-optimist
This will create and populate a new sensor, the attributes will contain all the upcoming events within X number of days and the state will be the next event date.
-
Install the AppDaemon addon and add the packages requests
, datetime
and humanize
required in the python section of the configuration
-
Create a config/appdaemon/apps/calendar.py file with the following code
import appdaemon.plugins.hass.hassapi as hass
from requests import get
import json
import datetime
import humanize
class calendar(hass.Hass):
def initialize(self):
# --- define variables ---
self.ha_url = self.args["url"]
self.token = self.args["token"]
self.calendar_name = self.args["calendar"]
self.days_to_display = self.args["days_to_display"]
self.sensor_name = self.args["sensor_name"]
self.run_hourly(self.check_calendar_events, datetime.time(hour=0, minute=1, second=0))
# --- do all the stuff at restarts ---
self.listen_event(self.startup, "plugin_started")
self.listen_event(self.startup, "appd_started")
# --- initialize ---
self.check_calendar_events(None)
def startup(self, event_name, data, kwargs):
self.log("Startup detected")
self.check_calendar_events(None)
def check_calendar_events(self, kwargs):
utc_offset = self.utc_offset(None)
start_dt = (datetime.datetime.now() - utc_offset).strftime("%Y-%m-%dT%H:%M:%S") # results in UTC time => "Z" in url
end_dt = (datetime.datetime.now() + datetime.timedelta(days=self.days_to_display) - utc_offset).strftime("%Y-%m-%dT%H:%M:%S") # results in UTC time => "Z" in url
_list = self.load_calendar(self.calendar_name,start_dt,end_dt)
# self.log(_list)
if _list == "error":
self.log("received http error - will retry later")
self.run_in(self.check_calendar_events, 600)
else:
_nextEvent:datetime = None
_events = []
for element in _list:
_summary = ""
_start_date = ""
_end_date = ""
_all_day_event = "dateTime" not in element["start"]
if "summary" not in element:
self.log("No summary in event, ignore")
continue
_summary = element["summary"]
if(_all_day_event):
_start = datetime.datetime.strptime(element["start"]["date"], "%Y-%m-%d")
_end = datetime.datetime.strptime(element["end"]["date"], "%Y-%m-%d")
_start_date = datetime.datetime.combine(_start, datetime.datetime.min.time()).astimezone()
_end_date = datetime.datetime.combine(_end, datetime.datetime.min.time()).astimezone()
else:
_start_date = datetime.datetime.strptime(element["start"]["dateTime"],"%Y-%m-%dT%H:%M:%S%z")
_end_date = datetime.datetime.strptime(element["end"]["dateTime"], "%Y-%m-%dT%H:%M:%S%z")
_diff = _end_date - _start_date
_duration = humanize.precisedelta(_diff)
friendly_start = humanize.naturalday(_start_date)
_events.append({
"summary": _summary,
"start_date": _start_date,
"end_date": _end_date,
"all_day_event": _all_day_event,
"duration": _duration,
"friendly_start": friendly_start,
})
if _nextEvent is None:
_nextEvent = _start_date
else:
if _start_date < _nextEvent:
_nextEvent = _start_date
# self.log(_events)
self.set_state(self.sensor_name,state=_nextEvent,attributes= {"events" : _events})
def load_calendar(self,calendar,start_dt,end_dt):
headers = {'Authorization': "Bearer {}".format(self.token)}
# self.log("Try to load calendar events")
apiurl = "{}/api/calendars/{}?start={}Z&end={}Z".format(self.ha_url,calendar,start_dt,end_dt)
# self.log("ha_config: url is {}".format(apiurl))
try:
r = get(apiurl, headers=headers, verify=False, timeout=10)
except:
self.log("Error while loading calendar {}. Maybe connection problem".format(calendar))
return "error"
# self.log(r)
# self.log(r.text)
if r.status_code == 200:
if "summary" in r.text:
resp = json.loads(r.text) # List
else:
resp = []
else:
self.log("loading calendar {} failed. http error {}".format(calendar,r.status_code))
resp = "error"
return resp
def utc_offset(self, kwargs):
now_utc_naive = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
now_loc_naive = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
utc_offset_dt = datetime.datetime.strptime(now_loc_naive, "%Y-%m-%dT%H:%M:%S") - datetime.datetime.strptime(now_utc_naive, "%Y-%m-%dT%H:%M:%S")
#self.log("utc offset: {}d {}sec".format(utc_offset_dt.days, utc_offset_dt.seconds))
return utc_offset_dt
- Update config/appdaemon/apps/apps.yaml and add the following code
calendar:
module: calendar
class: calendar
token: !secret calendar_token
url: http://192.168.1.3:8123
sensor_name: 'sensor.calendar_events'
calendar: 'calendar.family'
days_to_display: 60