Calendar set temperature from something in the event

Hi,

I am deploying HA into a church building, I have replaced all the old dumb thermostats with tasmota based sensors and relays, leaving the actual wiring system for the UFH alone as much as possible.

What I am trying to do now is to use our Bookings calendar integration (currently through Google, but can be a direct iCal as well I think) to set the target temperature for each room.

I have got the GCal integration working - it’s currently showing that there is an event going on, but I can’t see what details are available for that event.

I’d like to configure an automation to say, for instance:
30 minutes before the event, read the temperature from the event and set the relevant areas to that target temperature.

Is there somewhere I can see what’s available for me to read from the entry so that I can work out where I should inject some useful information about the desired temperature.

Cheers,

Have you looked at the “Offset” function for calendars?

With this tutorial you will get a good overview of how to use calendar events in Home Assistant. A search for “Home Assistant+calendar+offset” will present you plenty of instructions and examples.

The data that is available in the trigger variable for Calendar triggers can be found in the docs.

I did this in calandar

- id: 0258131f-831b-426b-b55e-35e9c61c222a
  alias: Google Calendar gas off
  trigger:
  - platform: state
    entity_id: calendar.gas_heater
    to: 'on'
  condition:
  - condition: template
    value_template: '{{ is_state_attr(''calendar.gas_heater'',''message'',''#Gas-off'')
      }}'
  action:
  - data:
      entity_id: input_boolean.livingroom_climate
    service: input_boolean.turn_off

@Tamsy
Thanks - the offset isn’t the issue here, I wasn’t clear which issue I was trying to solve - it’s trying to make the temperature depend on something about the triggering event. For instance a coffee morning attended by more elderly guests needs the room warmer than a very active dance class.

@myle
That looks like a fairly simple “heater on/off” setup, which I can now confirm I have working (since the heating got turned on this morning automagically, hurrah). The issue I have is that I want to control the temperature based on “some information” in the calendar event - that means that when a booking is taken, we can specify a specific temperature (and have a default if nothing is explicitly set).
Hopefully that makes sense…

Slightly longer description now that I’ve found the calendar triggering event details (thanks @Didgeridrew), and have automations running:

I currently have three automations which are all of the form listed below.

The trigger is either an event starting (on a calendar pulled from our bookings system), or it stopping. The “starting” has an offset of an hour to give time for the building to heat up. The action then sets the relevant rooms to 19 degrees (and this is the number I want to be able to change based on the booking) an hour before the booking starts, and returns them to a setback temperature (which I’ve defined as a helper so that I don’t need to keep adjusting the automations) at the end of the booking.

Looking at the calendar entity under the GCal integration when there is a triggering event active I can see the event listed as:

Attributes

Message: [B] Noah's Ark
All day: No
Start time: 16 September 2024 at 09:30:00
End time: 16 September 2024 at 12:00:00
Location: Auditorium | Hall
Description: 
Offset reached: false

Description is the only thing in there I can reasonably control, so it’s going to have to be another template - probably extracting T:18 or similar from a potentially much longer string?
A test event showed that the carriage returns I put into the description on the booking system get thrown away by the time the event is listed on HA, so it’s going to have to be a regex extraction of the form T:(\d+) I think…
It’s a shame the visual editor can’t cope with templates for the climate temperature setting.

Looking around I found Help with Regex template sensor

So maybe I want…

{% set caldesc = "This is a long description with a T:0 temperature request" %}
{{ float(caldesc | regex_findall_index('T:(\d+\.?\d?)'), states('input_number.default_heating_temperature')) }}

That works, but only if there is a temp defined… the findall_index throws a fit if it isn’t.
Multi line template, if I can just shove that in as is???:

{% set caldesc = "This is a long description with a T:17.8 temperature request" %}

{% if caldesc | regex_search('T:(\d+\.?\d?)') %}
{{ float(caldesc | regex_findall_index('T:(\d+\.?\d?)'), states('input_number.default_heating_temperature')) }}
{% else %}
{{ states('input_number.default_heating_temperature') }}
{% endif %}

Current Automation:

alias: Hall Heating
description: |-
  Triggered by bookings which use the hall
trigger:
  - alias: Hour before calendar event starts
    platform: calendar
    entity_id: calendar.webcal_...
    event: start
    offset: "-1:0:0"
  - alias: Calendar event ends
    platform: calendar
    entity_id: calendar.webcal_...
    event: end
    offset: "0:0:0"

condition: []

action:
  - if:
      - condition: template
        value_template: "{{ trigger.event == 'start' }}"
        alias: If calendar event is starting
    then:
      - action: climate.set_temperature
        metadata: {}
        data:
          temperature: 19
        target:
          entity_id:
            - climate.foyer
            - climate.hall
            - climate.front_room
            - climate.changing_place
            - climate.ladies
            - climate.gents
            - climate.office
            - climate.kitchen
            - climate.quiet_room
    else:
      - action: climate.set_temperature
        alias: Return to setback temperature
        metadata: {}
        data:
          temperature: "{{ states('input_number.setback_temperature') }}"
        target:
          entity_id:
            - climate.foyer
            - climate.hall
            - climate.front_room
            - climate.changing_place
            - climate.ladies
            - climate.gents
            - climate.office
            - climate.kitchen
            - climate.quiet_room
mode: single

Is it likely that there will be events in the calendar that overlap or abutt (including all-day events and considering the -1 hour offset)? If so, your sequences need to check the calendar for future/concurrent events.

I hope there won’t be many, but that hour does have the potential to mess things up.
For turning off I really need to check two things:

  • Check the other areas aren’t in use, because various shared areas (think loos and foyer) are needed for multiple "booking area"s.

  • I also, I now realise, potentially need to deal with an upcoming event, i.e. a booking which takes place shortly after another. Ignoring any temperature conflict possibilities for the moment…

I know I can check that another calendar has an event (because it’s calendar status would be on), and I can just wait until a minute after the event before turning off the heating to cope with any potential race conditions there…
But detecting whether a calendar has an upcoming event is probably harder…
We won’t have all day bookings (or at least they won’t be calendared as all day, they’ll be e.g. 8-6 on several consecutive days).
I also possible need to deal with a temperature change between events… that might be much harder.

Is this going to need a counter of “events currently active” to get incremented by the start automation and decremented by the end automation - with an overnight system resetting that counter to zero in case anything goes belly up diring the day?

Nope, there is an action (calendar.get_events) that queries the calendar to get the data of events within a given time period. However, you may not even to use that, depending on how you structure the automation and the calendar(s). Is the plan to use the location values from the event to determine the area that will be targeted, or are there separate calendars for each bookable area? Do the area names used in the calendar match those used in Home Assistant?

I would suggest you assign a label to all the common area climate entities to make your life easier.

Thanks - a quick read of the linked get_events documentation suggests it can be specified with a duration (of an hour in my case) to give me a list of events coming up soon - then I would parse through the limited list (hopefully just one) of events for my logic.
I’ll need to read up a bit more on where all those snippets end up, and how they interact, and how the list of events is sorted - I only need to look at the first one from each calendar if they’re sorted by time.

There are (currently) three calendars, one for each of the main “resources” (that’s how they’re described in the booking system) that get booked, automatically exported from the booking system. Effectively one “resource” is upstairs, one is the auditorium and the final one is the hall. I’d like to keep things as simple as I can for the people managing the bookings (hence a default temperature so most bookings don’t need to specify anything) so limiting the resources to that short list is good.

Bookings that include multiple areas (as our toddler group does in the above example) therefore have multiple entries in the location value, but appear also on both calendars. Since I set up both systems the room names / resources do match, though the shared spaces don’t have calendars.
The only detailed event information in use at the moment is the search for a T:20 style string in the description - the calendar on which the entry exists is, I think, a simple way to determine which areas need to be heated.

Having labels for those shared areas is definitely a good idea - the Foyer is the only zone shared three ways, so that will end with three additional labels if I’m understanding the suggestion correctly.

.

The logic on this is going to be substantially more complex than my own “reduce boiler cycling” logic at home… Just thinking aloud from here on…

When a booking ends (plus one minute).

  • Check if there’s another booking in the same place in the next hour, and update the temperature if there is.
  • Else set back heating in this area.
  • If there are any bookings, current or soon, in the other calendars then leave shared spaces heated.
  • Else set back heating for those spaces.

When a booking starts (minus one hour).

  • Check if the space is already being heated (else start)
  • If the temperature should be different then apply that change 15 minutes before the first booking ends?
    Maybe these could be done very simply: Have the at start automation check that the resource isn’t currently active (i.e. don’t do anything if the resource is already in use). Have a second automation at 15 minutes before which updates the temperature (which would be a no-op when there weren’t multiple events)

I’m wondering whether a few scripts might make this easier to parse in the future, so that the automations call a script for their “resource” room (with a custom temp), then a script for the shared rooms (which would use the default temp).
Then those scripts could do the checking for other events - leaving the automations themselves relatively simple to deal with.

Reading a bit more into the calendar entity…

“Calendar triggers are the best way to automate based on calendar events. A calendar entity can also be used to automate based on its state, but these are limited and attributes only represent the next event.”
(Calendar - Home Assistant)

Whilst that is a limitation… it’s fine for my purposes - I only ever need the next event for a given calendar for heating purposes.

A quick play in the template editor gives me the start time, so I can just subtract the current time to see how far away the next entry is (EDIT: added some handling for “no next event”):

{% set cal = state_attr('calendar.webcal_...','start_time') %}
{% if cal is none %}{% set cal=now() + timedelta(days=1) %}{% endif %}
{{ as_timestamp(cal) - as_timestamp(now()) }}

Technically this doesn’t always give the next entry - it gives the current one if the calendar is active - so I get negative “time to next event” if the calendar is active.

So we have a few distinct scenarios:

A - If an event (-1hr) triggers and there isn’t an event active, then we’re all good - set the temp.
B - If an event (-1hr) triggers whilst there is already an event active (i.e. the calendar is active) then wait until much later (say, a 45 minute wait) before adjusting the target temperature.
I might do 59 minutes, rather than an hour, to avoid edge cases where the time taken to run the template means the logic catches one event, but not the other.

C - If an event stops (+1 min) and there isn’t an event for more than 3600 seconds, then we’re all good - set back.
D - If an event stops (+1 min) and there is an event within 3600 seconds (or has been running for just 60)… then we’re probably already waiting, or recently had the temp set, (both from scenario B). We can set the new temperature right away, it’s probably going to end up being set twice, but that’s not a problem, it’s redundancy :stuck_out_tongue_winking_eye: .

At this point the automation needs to allow parallel running to deal with the potential waits etc…

I think that’s the “main” spaces logic in a relatively short list (and therefore simple automation)

Calling a script for the shared spaces then makes sense. I’m thinking that the shared spaces probably always want to have the “default” temperature - it’s not like the dancers will be dancing in the loos…

That script would loop through each calendar and check whether there is an event running, or one starting in the next hour - and add the relevant tags/labels/spaces to a list of areas to heat.
After all the calendars are checked the areas on the list are set to heat, and the rest of the area are set_back (again, it’s ok to set a temperature on a thermostat that’s already at that temperature). That would be a script which can be run when an event starts or stops, or indeed at any random time.

For completeness… I think I have a working solution.
EDIT: It is indeed working.

Thanks to those who pointed me in correct directions - particularly @Didgeridrew

I have

  • Three calendar entities contain the bookings for the three main spaces.
  • Four automations are defined, one for each calendar and one for the shared spaces.

The three main automations are triggered by an event starting (-59 minutes) and by an event ending (+1 minutes).

  • If it’s an event starting check whether there is already an event active*:

    • If there is then wait 45 minutes, else go straight to…
    • Set the temperature for that event.
  • If it’s an event ending then check for event starting in the next hour and:

    • Set the temperature to that future event’s target OR
    • Set to the setback temperature.
  • Then call the shared heating space automation:

    • Take the start time of each calendar (i.e. current or next event only) and if it’s under 60 minutes away (includes events which have already started) then it considers that space “booked”.
    • Four conditional statements (pertaining to anywhere, downstairs, hall, upstairs) then set the heating on that group of shared spaces based on which spaces are booked to either the default temperature or the setback temperature.

Cheers,

.* If I was being particularly fussy I suppose I could check whether the triggering event had the same start time as the calendar entity - i.e. it was the “next” event. I don’t anticipate consecutive short events though.