Problems reading local calendar trigger variables

I am trying to use the new local calendar feature as a means to schedule radiator thermostats, but am having trouble picking up the trigger variables. It is not much documented or discussed yet so I do not know whether I am doing something wrong or have found bugs?

I define a local calendar for each radiator. Calendar events are for a period of time for which the radiator is to be set to a given temperature, which I write in the description field to one decimal place.
The automation needs the name of the event, the start time and the temperature (from the description). I am not sure about the required format for the start time or the casting of the description to a number, so for test purposes I defined input_text variables for these as well the and input_datetime and input_number variables that I would normally use.

Calendar event attributes cannot be read at any arbitrary time, as far as I can tell, so I am picking them up from the trigger variables in an automation that is triggered by the start of an event in the calendar. As I am writing to input variables I can read them anywhere and see them on a dashboard too.

I use the visual editor to create the automation, but the trigger variables have to be read using YAML.
:black_small_square: The event name is read as value: "{{ trigger.calendar_event.summary }}". I put this is an input_text variable.
:black_small_square: The start time is read as value: "{{ trigger.calendar_event.start }}". I put this in both an input_text variable and an input_datetime variable. I have also tried "{{ states(' trigger.calendar_event.start') }}" but that did not help.
:black_small_square: The description is read as value: "{{ trigger.calendar_event.description }}". I put this is an input_text variable.
:black_small_square: The required temperature is extracted with value: "{{ states ('trigger.calendar_event.description') | float(1) }}". I put this in an input_number variable.

Results:
The event name (summary) is read with no problem.
The start time is not read either as text or as date-time.
The description is read as text but the value cast to a number is not written (the input_number retains its default minimum value).

Can anyone advise how I should code these?

It would be easier to help is you provided the whole automation. From the visual editor click the 3-dots/kebab menu button on the top right and select “Edit in YAML”. Copy the contents and paste them here, starting and ending with 3 backticks (```).

The input_datetime.set_datetime service is particular about the format in which the data is passed to it. The trigger.calendar_event.start variable returns a time-zone aware datetime string that requires filtering to work with this service. What type of Input datetime are you using… time-only, date-only, time-and-date?

This is a little test automation, just to solve this problem

alias: Radiator 2 event capture
description: code test
trigger:
  - platform: calendar
    event: start
    offset: "0:0:0"
    entity_id: calendar.radiator_2
condition: []
action:
  - service: input_text.set_value
    data:
      value: "{{ trigger.calendar_event.summary }}"
    target:
      entity_id: input_text.radiator_2_event_name
  - service: input_text.set_value
    data:
      value: "{{ trigger.calendar_event.start }}"
    target:
      entity_id: input_text.rad_2_start_time_as_text
  - service: input_datetime.set_datetime
    data:
      datetime: "{{ trigger.calendar_event.start }}"
    target:
      entity_id: input_datetime.radiator_2_event_start_time
  - service: input_text.set_value
    data:
      value: "{{ trigger.calendar_event.description }}"
    target:
      entity_id: input_text.rad_2_description_as_text
  - service: input_number.set_value
    data:
      value: "{{ states ('trigger.calendar_event.description') | float(1) }}"
    target:
      entity_id: input_number.radiator_2_event_temperature
mode: single

It will be part of a larger automation that so far looks like this:

alias: Radiator X control
description: "model for a blueprint"
trigger:
  - platform: calendar
    event: start
    offset: "0:0:0"
    entity_id: calendar.radiator_1
    id: event_start
  - platform: calendar
    event: end
    offset: "0:2:0"
    entity_id: calendar.radiator_1
    id: event_end
  - platform: state
    entity_id:
      - sensor.radiator_1_set_temperature
    for:
      hours: 0
      minutes: 0
      seconds: 5
    id: manual_override
  - type: occupied
    platform: device
    device_id: 737e8f81fcac51fcdcdce5629ff6cb1a
    entity_id: binary_sensor.ep1_mmwave
    domain: binary_sensor
    id: room_occupied
  - type: not_occupied
    platform: device
    device_id: 737e8f81fcac51fcdcdce5629ff6cb1a
    entity_id: binary_sensor.ep1_mmwave
    domain: binary_sensor
    for:
      hours: 0
      minutes: 0
      seconds: 10
    id: room_unoccupied
  - type: opened
    platform: device
    device_id: 4b75acd529cf91315e5fe62a43a1a2b3
    entity_id: binary_sensor.lumi_lumi_sensor_magnet_aq2_opening
    domain: binary_sensor
    for:
      hours: 0
      minutes: 0
      seconds: 10
    id: door_or_window_opened
  - type: not_opened
    platform: device
    device_id: 4b75acd529cf91315e5fe62a43a1a2b3
    entity_id: binary_sensor.lumi_lumi_sensor_magnet_aq2_opening
    domain: binary_sensor
    id: doors_and_windows_closed
  - platform: state
    entity_id:
      - timer.radiator_1_manual_override_timer
    from: active
    to: idle
condition: []
action:
  - choose:
      - conditions:
          - condition: trigger
            id: event_start
        sequence:
          - service: input_text.set_value
            data:
              value: "{{ trigger.calendar_event.summary }}"
            target:
              entity_id: input_text.radiator_1_current_event_name
          - service: input_number.set_value
            data:
              value: "{{ states ('trigger.calendar_event.description') | float(1) }}"
            target:
              entity_id: input_number.radiator_1_scheduled_temperature
          - service: input_datetime.set_datetime
            data:
              datetime: "{{ trigger.calendar_event.start }}"
            target:
              entity_id: input_datetime.radiator_1_current_event_start_time
          - service: timer.start
            data:
              duration: "00:02:00"
            target:
              entity_id: timer.radiator_x_new_event_grace_period
      - conditions:
          - condition: trigger
            id: event_end
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: >-
                      {{ states('trigger.calendar_event.summary') }} == {{
                      states('input_text.radiator_1_current_event_name') }}
                sequence:
                  - service: input_number.set_value
                    data:
                      value: 5
                    target:
                      entity_id: input_number.radiator_1_scheduled_temperature
                  - service: input_datetime.set_datetime
                    data:
                      datetime: "{{ states('trigger.calendar_event.end') }}"
                    target:
                      entity_id: input_datetime.radiator_1_current_event_start_time
                  - service: input_text.set_value
                    data:
                      value: "\"(none)\""
                    target:
                      entity_id: input_text.radiator_1_current_event_name
                  - service: climate.set_temperature
                    data:
                      temperature: 5
                    target:
                      entity_id: climate.radiator_1_thermostat_moesthermostat_2
                  - service: input_text.set_value
                    data:
                      value: Turned off because nothing is scheduled
                    target:
                      entity_id: input_text.radiator_x_setting_reason
                  - service: timer.cancel
                    data: {}
                    target:
                      entity_id: timer.radiator_x_new_event_grace_period
      - conditions:
          - condition: trigger
            id: manual_override
          - condition: state
            entity_id: timer.radiator_x_last_change_grace_period
            state: idle
        sequence:
          - service: input_number.set_value
            data:
              value: "{{ states('sensor.radiator_1_set_temperature') | float(1) }}"
            target:
              entity_id: input_number.radiator_1_manual_override_temp
          - service: timer.start
            data:
              duration: "00:05:00"
            target:
              entity_id: timer.radiator_1_manual_override_timer
  - choose:
      - conditions:
          - condition: state
            entity_id: binary_sensor.lumi_lumi_sensor_magnet_aq2_opening
            state: "on"
            for:
              hours: 0
              minutes: 0
              seconds: 0
        sequence:
          - service: climate.set_temperature
            data:
              temperature: 5
            target:
              entity_id: climate.radiator_1_thermostat_moesthermostat_2
          - service: input_text.set_value
            data:
              value: Turned off because a door or window is open
            target:
              entity_id: input_text.radiator_x_setting_reason
      - conditions:
          - condition: state
            entity_id: binary_sensor.ep1_occupancy
            state: "off"
            for:
              hours: 0
              minutes: 0
              seconds: 0
          - condition: state
            entity_id: timer.radiator_x_new_event_grace_period
            state: idle
        sequence:
          - service: climate.set_temperature
            data:
              temperature: 5
            target:
              entity_id: climate.radiator_1_thermostat_moesthermostat_2
          - service: input_text.set_value
            data:
              value: Turned off because the room is unoccupied
            target:
              entity_id: input_text.radiator_x_setting_reason
      - conditions:
          - condition: state
            entity_id: timer.radiator_1_manual_override_timer
            state: active
        sequence:
          - service: climate.set_temperature
            data:
              temperature: "{{ states('input_number.radiator_1_manual_override_temp')}}"
            target:
              entity_id: climate.radiator_1_thermostat_moesthermostat_2
          - service: input_text.set_value
            data:
              value: Set manually
            target:
              entity_id: input_text.radiator_x_setting_reason
      - conditions:
          - condition: state
            entity_id: calendar.radiator_1
            state: "on"
        sequence:
          - service: climate.set_temperature
            data:
              temperature: "{{ states('input_number.radiator_1_scheduled_temperature')}}"
            target:
              entity_id: climate.radiator_1_thermostat_moesthermostat_2
          - service: input_text.set_value
            data:
              value: Set according to a calendar event
            target:
              entity_id: input_text.radiator_x_setting_reason
    default:
      - service: climate.set_temperature
        data:
          temperature: 5
        target:
          entity_id: climate.radiator_1_thermostat_moesthermostat_2
      - service: input_text.set_value
        data:
          value: Turned off because nothing is scheduled
        target:
          entity_id: input_text.radiator_x_setting_reason
  - service: timer.start
    data:
      duration: "00:00:10"
    target:
      entity_id: timer.radiator_x_last_change_grace_period
mode: single

It uses these custom sensors

### radiator target temperature ###
- platform: template
  sensors: 
    radiator_1_set_temperature:
      friendly_name: Radiator 1 set temperature
      unique_id: "3011e180-08a0-4cef-9793-e2c0a13e57e2"
      unit_of_measurement: 'C'
      icon_template: mdi:thermometer
      value_template: "{{ state_attr('climate.radiator_1_thermostat_moesthermostat_2', 'temperature') }}"

### radiator measured temperature ###
    radiator_1_ambient_temperature:
      friendly_name: Radiator 1 ambient temperature
      unique_id: "7b5b54c2-277e-486a-be67-3f0c93c17894"
      unit_of_measurement: 'C'
      icon_template: mdi:thermometer
      value_template: "{{ state_attr('climate.radiator_1_thermostat_moesthermostat_2', 'current_temperature') }}"

Tests are ongoing but most of it works except the calendar event scheduling. When testing is complete I want to make it into a Blueprint where everything starting radiator_1 or radiator _x will be parameterised. I welcome any comments on how the code could be made more robust, simplified or streamlined.

I am using time and date so that I can calculate elapsed time since the start of the event even if it runs overnight.

It seems I changed something because since writing the first post the ‘radiator 2’ test version is working for date and time (though not for temperature), but the radiator X version is not, while I cannot spot the difference in the code!?!

What changed in ‘radiator 2’ is that you cleaned up most (but not all) of the templates that look like the following:

value_template: >-
  {{ states('trigger.calendar_event.summary') }} == {{
  states('input_text.radiator_1_current_event_name') }}

When you wrap it in quotes, you turn the trigger variable into a string. In that process it’s data is stripped and thrown away. So {{ states('trigger.calendar_event.summary') }} is trying to get the state of a nonsense string and will just returns a None value.

Then you have an == comparison outside the template. When thing are not within the bounds of the curly braces, they do not have the function they have inside the braces. They just become part of a string.

The template above should look more like:

value_template: >-
 {{ trigger.calendar_event.summary == 
 states('input_text.radiator_1_current_event_name') }}

Fix those issue them let us know how it’s going.

Thanks.

  1. I understand the bit about the comparison; thanks for spotting that. (I had not tested this section yet so did not see that it failed).

  2. I am not clear what you think I ‘tidied up’ in the radiator 2 example? Examining the code where input_text, input_number or input_datetime are set they both look the same to me, so is it a matter of context?

  3. When setting a value to an input_ variable, I was confused by when to add the quotes. By experiment (Radiator 2) I found that while {{ ... }} works in the template editor, value: {{ ... }} does not work in the automation; I have to write value: "{{ ... }}" (with the quotes). Is that correct?

  4. I often have trouble getting the automation editor to save changes. If I use edit YAML locally to set the value within a call service … input_number set it sometimes does not give me the “save” button, and sometimes appears to save the value but when I go back it has replaced the value with “null”. What is going on here?

  5. Now I want to tidy up the extraction of the temperature from the description. I decided to put the temperature between two # symbols, then I can write other stuff in the description as well. I could not find any more general way of extracting a number from string. It is annoying to have to wait 15 minutes for each calendar test, so I wrote an even simpler test automation that just triggers when an input_text variable changes and writes an input_number. The expression

     {{ '%0.1f' | format(float(states('input_text.test_text').split("#")[1])) }} 

… works nicely in the template editor but I cannot get it into the automaton! From the test text “Set temperature to #24.987# C” it correctly returns “24.9” in the template editor. The automation editor says “Message malformed: template value should be a string for dictionary value @ data[‘action’][0][‘data’]”. If I edit the YAML overall it appears to accept it but replaces the value with ‘null’ after I save.

Solved point 5 myself, though the template editor is no help here! The problem is in the quotation mark types. The template content must avoid the double quotes used to enclose the whole thing – so this works:

service: input_number.set_value
data:
  value: "{{ '%0.1f' | format(float(states('input_text.test_text').split('#')[1])) }} "
target:
  entity_id: input_number.test_number

#2
In your previous post you said:

Temperature is not working because the template contained in that service has the states('trigger.....') issue I outlined in my previous post, as seen below.

  - service: input_number.set_value
    data:
      value: "{{ states ('trigger.calendar_event.description') | float(1) }}"
    target:
      entity_id: input_number.radiator_2_event_temperature

This same type of template mis-construction is also present throughout the larger automation.


#3

When using templates in YAML configuration the templates must be wrapped in quotes OR prefaced by a multiline quote key. The following are equivalent:

value: "{{  states('sensor.example') }}"

value: >
  {{  states('sensor.example') }}

Placing quotes around YAML configuration variable keys does not work. The following is invalid because there is essentially no key.

'value: {{ states("sensor.example") }}'

#4
I have had that happen before as well. For me it is most often due to an invalid template construction. Commonly it’s when I do something silly like forget to use single and double quote marks properly. When you fail to alternate single and double quotes as shown here:

value: "{{ states("sensor.example") }}"

The interpreter sees something like value: "{{states(" which throws an error because your template isn’t closed out. In the UI editor it just gets overwritten with null.

#2

Sorry I did not fully understand what you meant by the states('trigger.....') issue. I think you are saying I cannot read a trigger with the states method but can just access it as a variable in the outer part of the template??

So should it be …

"{{ trigger.calendar_event.description | float(1) }}"

…?

For my more advanced formula to extract the temperature from between hashes in a longer text description, should I store the description first in input_text.radiator_x_description and then read that with states?

service: input_number.set_value
data:
  value: "{{ '%0.1f' | format(float(states('input_text.radiator_x_description').split('#')[1])) }}"
target:
  entity_id: input_number.test_number

Or can I write …

service: input_number.set_value
data:
  value: "{{ '%0.1f' | format(float(trigger.calendar_event.description).split('#')[1])) }}"
target:
  entity_id: input_number.test_number

(Does that count as “being inside” the template?)

Sorry to keep coming back to you but I have been on this for three days solid now and trial-and-error is becoming a bit tedious! :frowning:

#3 thanks for clarifying (it’s far from obvious!)

#4
That’s probably it. My problem was that the template editor does not show up erroneous alternation of quote marks in the same way so it took me ages to spot it. The formulation I noted above …


value: "{{ '%0.1f' | format(float(states('input_text.test_text').split('#')[1])) }}"

…is accepted; i.e. my smallest test automation is working. I am now working my way back to the ‘Radiator 2’ test and then to the full ‘Radiator X’ automation.

I am closing this thread now. Thanks to @Didgeridrew for the help.

To summarise for anyone else following this:

  1. Having a template working in the template editor does not mean it will work in an automation!
  2. When using a template to provide a value is has to be quoted '{{ ... }}', so there should be none of the same type of quotes inside the template; use a different kind of quotes
         value: '{{ ''%0.1f'' | format(float(states(''input_text.radiator_x_event_description'').split(''#'')[1])) }}'
  1. The error “Message malformed: template value should be a string …" often mean that there is a mismatching of quote signs
  2. Trigger variables cannot be read using states(' ... '), but they can be read with e.g.
        value: '{{trigger.calendar_event.start}}'
  1. To perform operations on trigger variables the value must therefore first be stored in a local variable or a global ‘helper’ variable. I use the latter because I need the value to persist and to be available on a dashboard. this example extracts a number enclosed in hash characters from the description field in a local calendar – I use it for the scheduled set temperature or a radiator:
      - service: input_text.set_value
        data:
          value: '{{ trigger.calendar_event.description }}'
        target:
          entity_id: input_text.radiator_x_event_description
      - service: input_number.set_value
        data:
          value: '{{ ''%0.1f'' | format(float(states(''input_text.radiator_x_event_description'').split(''#'')[1]))
            }} '
        target:
          entity_id: input_number.radiator_x_event_temperature