Excuse me, in which folder should I put your script?
This is just a snippet of code that can be used to retrieve the list of events in a calendar, it is not a fully functioning script.
To get going you would need to install the Pyscript integration, turn the snippet into a full script that does what you want and copy it into /config/pyscript
.
@Mathieu - thanks for the pointer. I wrote a pyscript that
- for every calendar included/excluded (see code) adds a “calendar.name_ext” entity with 4 attributes
current_event_brief:
upcoming_event_brief: 9:00 Justin and Alex
start_time: 2023-02-27T09:00:00-05:00
till: 12hr
- adds calendar.all entity that aggegates included calendars
current_event_brief:
upcoming_event_brief: 9:00 (Steve / alex)
start_time: 2023-02-27T09:00:00-05:00
till: 12hr
import aiohttp
import datetime
from datetime import timezone
import json
from homeassistant.const import EVENT_STATE_CHANGED
ha_base_url = "http://192.168.XX.YY:8123"
access_token = "YOUR_TOKEN"
now = datetime.datetime.now()
start = now.isoformat()
end = now + datetime.timedelta(days=7)
events_filter = f"start={start}&end={end}"
headers = {
'Authorization': f"Bearer {access_token}",
'Content-Type': 'application/json'
}
include_calendars = []
exclude_calendars = ['calendar.birthdays', 'calendar.holidays_in_united_state']
if include_calendars and len(include_calendars) == 0:
calendar_url = f"{ha_base_url}/api/calendars"
async with aiohttp.ClientSession(headers=headers, connector=aiohttp.TCPConnector(ssl=False)) as session:
async with session.get(calendar_url) as response:
try:
calendars = json.loads(response.text())
include_calendars = [calendar['entity_id'] for calendar in calendars]
finally:
pass
all_calendar_events = []
def add_briefs(include_calendars, exclude_calendars):
for calendar in include_calendars:
if calendar in exclude_calendars:
continue
calendar_url = f"{ha_base_url}/api/calendars/{calendar}?{events_filter}"
async with aiohttp.ClientSession(headers=headers, connector=aiohttp.TCPConnector(ssl=False)) as session:
async with session.get(calendar_url) as response:
calendar_events = json.loads(response.text())
# add calendar name to each event
for event in calendar_events:
event['calendar.entity_id'] = calendar
if 'dateTime' not in event['start']:
event['start']['dateTime'] = event['start']['date'] + 'T00:00:00'
event['all_day'] = True
else:
event['all_day'] = False
start_time = datetime.datetime.fromisoformat(event['start']['dateTime']).astimezone()
dow = start_time.strftime('%a')
hm = start_time.strftime('%-I:%M')
duration = start_time - datetime.datetime.now().astimezone()
hours = duration.seconds // 3600 + (duration.days * 24)
minutes = (duration.seconds // 60) % 60
if hours > 0:
duration_str = f"{hours}hr"
else:
duration_str = f"{minutes}m"
event['till'] = f"{duration_str}"
# remove common words from summary
summary = event['summary'].replace('Meeting with ', '')
summary = summary.replace('Meeting ', '')
summary = summary.replace('Call with ', '')
summary = summary.replace('Discussion', '')
summary = summary.replace('Introduction Call ', '')
summary = summary.replace('Call ', '')
summary = summary.replace(' with ', ' ')
summary = summary.replace(' - ', ' ')
summary = summary.replace(' @ ', ' ')
summary = summary.strip(' -@:')
if (hours > 24):
event['brief'] = f"{dow} {hm} {summary}"
else:
event['brief'] = f"{hm} {summary}"
filtered_calendar_events = calendar_events
filtered_calendar_events = [event for event in filtered_calendar_events if event['start']['dateTime'] > now.isoformat()]
filtered_calendar_events = [event for event in filtered_calendar_events if not event['all_day']]
filtered_calendar_events = [event for event in filtered_calendar_events if 'Personal Commitment' not in event['summary']]
filtered_calendar_events = [event for event in filtered_calendar_events if 'summary' in event and len(event['summary'])>0]
all_calendar_events.extend(filtered_calendar_events)
# sort events by ascending start time
sorted_calendar_events = sorted(filtered_calendar_events, key=lambda event: event['start']['dateTime'])
current_events = [event for event in sorted_calendar_events if event['end']['dateTime'] < now.isoformat()]
upcoming_events = [event for event in sorted_calendar_events if event['end']['dateTime'] > now.isoformat()]
if len(current_events) > 0:
next_event = current_events[0]
state.set(f"{calendar}_ext",
current_event_brief=next_event['brief'],
end_time=next_event['end']['dateTime'])
else:
state.set(f"{calendar}_ext", current_event_brief='')
if len(upcoming_events) > 0:
next_event = upcoming_events[0]
state.set(f"{calendar}_ext",
upcoming_event_brief=next_event['brief'],
start_time=next_event['start']['dateTime'],
till=next_event['till'])
else:
state.set(f"{calendar}_ext", upcoming_event_brief='')
sorted_calendar_events = sorted(all_calendar_events, key=lambda event: event['start']['dateTime'])
current_events = [event for event in sorted_calendar_events if event['end']['dateTime'] < now.isoformat()]
upcoming_events = [event for event in sorted_calendar_events if event['end']['dateTime'] > now.isoformat()]
if len(current_events) > 0:
next_event = current_events[0]
state.set("calendar.all", current_event_brief=next_event['brief'], end_time=next_event['end']['dateTime'])
else:
state.set("calendar.all", current_event_brief='')
if len(upcoming_events) > 0:
next_event = upcoming_events[0]
state.set("calendar.all", next_event['till'],
upcoming_event_brief=next_event['brief'],
start_time=next_event['start']['dateTime'],
till=next_event['till'])
else:
state.set("calendar.all", upcoming_event_brief='')
@event_trigger(EVENT_STATE_CHANGED, "entity_id.startswith('calendar.') and not entity_id.endswith('_ext') and 'calendar.all' not in entity_id")
def monitor_calendar_change(entity_id=None, new_state=None, old_state=None):
add_briefs(include_calendars, exclude_calendars)
add_briefs(include_calendars, exclude_calendars)
Thanks for the great explanation @Alex_Pupkin and @Mathieu. I adapted to create a list of upcoming events to show them on an epaper display.
Only thing I can’t get to work: The @event_trigger
to work when the calendar is updated.
I use the Google Calendar integration which does not to trigger a state_changed event (I checked manually the by listening to the event bus). I believe a state_changed event is triggered only if the very next event has changed, not if upcoming events have changed.
Question: Has anyone an idea if and which event the Google Calendar integration triggers when it updates (which it does every 15 minutes)?
Here is my “trigger code”.
@event_trigger(EVENT_STATE_CHANGED, "entity_id=='calendar.mabjo'")
@time_trigger("cron(0 0 * * *)", "startup")
def monitor_calendar_change(entity_id=None, new_state=None, old_state=None):
add_briefs(include_calendars, exclude_calendars)
What would really be amazing is being able to select event, relative to now, by a consistently numbered selector, such as:
states.calendar['my_calendar_name'].event-2 = two events previous to now
states.calendar['my_calendar_name'].event-1 = most recent previous event
states.calendar['my_calendar_name'].event_0 = next event
states.calendar['my_calendar_name'].event_1 = second furthest out states.calendar['my_calendar_name'].event_2 = third furthest out event
And then have the attribute properties again selectable below that, so that:
states.calendar['my_calendar_name'].event_0.start_time was retrievable
Please feel free to adjust my model to be valid python, this is definitely not my “native programming language”.
Thanks.
I was playing around with using the home assistant rest API and a rest template sensor and came up with a hacky way to do this:
- platform: rest
resource: https://<your home assistant url>/api/calendars/calendar.personal
name: Agenda
method: GET
scan_interval: 900
headers:
authorization: !secret rest_events_token
params:
start: >
{{ utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z') }}
end: >
{{ (utcnow() + timedelta(days=5)).strftime('%Y-%m-%dT%H:%M:%S.000Z') }}
value_template: >
{% set events = value_json %}
{% for event in events %}
{% if event.start.dateTime %}
{{ (event.start.dateTime | as_datetime).strftime('%Y-%m-%d %H:%M') }}: {{ event.summary }}
{% else %}
{{ event.start.date }}: {{ event.summary }}
{% endif %}
{% endfor %}
See RESTful Sensor - Home Assistant for more details. You’ll need to get a home assistant bearer token for this to work and enter as a secret as shown in the rest docs.
i dont know if there an event but you can create one easily if you have pyscript. add an attribute to calendar entity on timer - when google calendar integration refreshes data, it removes that attribute thus generating an event.
I also like to announce my appointments of the day with TTS in my morning routine
Hello everyone,
I have installed “HASS Calendar Addon”, but I can’t see a sensor anywhere, what am I doing wrong.
Can someone help me with this?
Thanks for every response
What does the addon’s log say?
Thanks for you reaction
s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
cont-init: info: running /etc/cont-init.d/00-banner.sh
-----------------------------------------------------------
Add-on: Hass Calendar Addon
Addon to consume caldav and google calendars and make calendar events available as sensordata in Home Assistant.
-----------------------------------------------------------
Add-on version: 0.301
You are running the latest version of this add-on.
System: Home Assistant OS 10.0 (amd64 / qemux86-64)
Home Assistant Core: 2023.4.6
Home Assistant Supervisor: 2023.04.1
-----------------------------------------------------------
Please, share the above information when looking for help
or support in, e.g., GitHub, forums or the Discord chat.
-----------------------------------------------------------
cont-init: info: /etc/cont-init.d/00-banner.sh exited 0
cont-init: info: running /etc/cont-init.d/01-log-level.sh
cont-init: info: /etc/cont-init.d/01-log-level.sh exited 0
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
added 80 packages, and audited 81 packages in 14s
23 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 9.1.2 -> 9.6.5
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v9.6.5>
npm notice Run `npm install -g [email protected]` to update!
npm notice
System locale: en-US
Luxon locale set to: nl-BE
System timeZone: Europe/Brussels
Luxon timezone set to: Europe/Brussels
Previously stored events posted to sensor(s) at: Sun Apr 23 2023 17:50:11 GMT+0200 (Central European Summer Time)
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
There was an issue about that log error a couple months ago… related to an intermittent problem with empty calendars. I don’t know if it was fixed yet.
Make sure there is at least one event in your calendar(s) within the time frame of fetchDaysPast - fetchDays, restart the addon. Remember that the sensor only sends a fetch request based on your fetchCron time configuration variable. If you use the default, that means it will fetch on the hour and at half-past. Sometimes the first fetch after restart fails, so let it run uninterrupted through at least 2 fetch cycles.
Hi,
Thank you for your response.
In attachment you see my calendar to test and also my settings.
If I understand you correctly, the add-on will retrieve the data from the calendar every 30 minutes? Is that correct?
After waiting 30 minutes I get this in the log:
s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
cont-init: info: running /etc/cont-init.d/00-banner.sh
-----------------------------------------------------------
Add-on: Hass Calendar Addon
Addon to consume caldav and google calendars and make calendar events available as sensordata in Home Assistant.
-----------------------------------------------------------
Add-on version: 0.301
You are running the latest version of this add-on.
System: Home Assistant OS 10.0 (amd64 / qemux86-64)
Home Assistant Core: 2023.4.6
Home Assistant Supervisor: 2023.04.1
-----------------------------------------------------------
Please, share the above information when looking for help
or support in, e.g., GitHub, forums or the Discord chat.
-----------------------------------------------------------
cont-init: info: /etc/cont-init.d/00-banner.sh exited 0
cont-init: info: running /etc/cont-init.d/01-log-level.sh
cont-init: info: /etc/cont-init.d/01-log-level.sh exited 0
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
added 80 packages, and audited 81 packages in 14s
23 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 9.1.2 -> 9.6.5
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v9.6.5>
npm notice Run `npm install -g [email protected]` to update!
npm notice
System locale: en-US
Luxon locale set to: nl-BE
System timeZone: Europe/Brussels
Luxon timezone set to: Europe/Brussels
Previously stored events posted to sensor(s) at: Mon Apr 24 2023 10:54:18 GMT+0200 (Central European Summer Time)
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
Calendar(s) queried at: Mon Apr 24 2023 11:30:01 GMT+0200 (Central European Summer Time)
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
postEventsAllCalendars error: TypeError: Cannot read properties of undefined (reading 'length')
Events posted to sensor(s) at: Mon Apr 24 2023 11:30:01 GMT+0200 (Central European Summer Time)
axios error: [object Object]Request failed with status code 401
axios error: [object Object]Request failed with status code 401
axios error: [object Object]Request failed with status code 401
axios error: [object Object]Request failed with status code 401
Hello everyone,
For me it works now.
But the “caldavUrl:” setting should not be
“https://pXX-caldav.icloud.com/{DSid}/calendars/{pGUID}” or
“https://pXX-caldav.icloud.com/{DSid}/calendars/{calendarName}”
but
“https://pXX-caldav.icloud.com/{DSid}/calendars/{GUID}”.
This way I receive all data very well.
However, if I
https://pXX-caldav.icloud.com/{DSid}/calendars/{pGUID} or
“https://pXX-caldav.icloud.com/{DSid}/calendars/{calendarName}”
I keep getting
´´´´
axios error: [object Object]Request failed with status code 404
´´´´
error message.
At least this way it works for me!!
Thank you to all the people who gave me a little feedback!!
I am trying to use your code and I am struggling to have the sensor populate with anything. On restart, I get the agenda sensor but its empty. Following the RESTful documentation, I used the same details and used cURL and that returned calendar events. Here is what I I have in my config
- platform: rest
resource: https://myurl.com/api/calendars/calendar.calendar_me
name: Agenda
unique_id: calendar_agenda
method: GET
scan_interval: 900
headers:
authorization: !secret rest_events_token
params:
start: >
{{ utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z') }}
end: >
{{ (utcnow() + timedelta(days=5)).strftime('%Y-%m-%dT%H:%M:%S.000Z') }}
value_template: >
{% set events = value_json %}
{% for event in events %}
{% if event.start.dateTime %}
{{ (event.start.dateTime | as_datetime).strftime('%Y-%m-%d %H:%M') }}: {{ event.summary }}
{% else %}
{{ event.start.date }}: {{ event.summary }}
{% endif %}
{% endfor %}
I have a calendar and it has events in the last 5 days and next 5 days. I’ve set up a long-term token in my secret file, I’ve checked the logs and there is nothing in there on this.
I used this command from the docs (modifying for my instance)
curl \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
"https://myurl.com/api/calendars/calendar.calendar_me?start=2022-05-01T07:00:00.000Z&end=2022-06-12T07:00:00.000Z"
and it returns events.
Have I missed a vital step? I’d really appreciate a steer.
Thanks in advance.
This looks very similar. I’m not sure what the problem is from spot checking, but maybe I can suggest a couple things.
- turn up debug logging for the rest component
homeassistant.components.rest
and see if there are any details in the logs about failed requests - make the template return “foo” to confirm its running
- set the return value to " {{ value_json }} " and you can dump the whole thing and make sure that is working
Perfect - thanks for the pointer
Seems we finally got something to ask for data using in automation. In our case calendar data. See 2023.7: Beta release notes - Home Assistant
Yep! This is meant to address this problem, but in the works for awhile now and happy to get it out!
Good job Allen. Thanks!