Request for Generic Thermostat "heater" switch to act as a thermostat physical switch

The Generic Thermostat integration toggles a heater switch to control the temperature. In many cases this switch is a physical device with a button. It would be useful if the button could turn the thermostat on/off in addition to the heater. This should only work in cases when it wasn’t the thermostat itself which toggled the heater of course, otherwise both the heater and thermostat would turn off once the target temperature is reached.

Here are use cases to help explain:
User uses heater button

  • User wishes to turn the thermostat on/off from within the room with the heater
  • User pushes the heater switch button, for instance a Sonoff mains plug to a space heater
  • The heater turns on, since the button works locally
  • HA detects the unexpected switch state change and toggles the generic thermostat state

User uses thermostat

  • User wishes to turn the thermostat on/off from away from the room
  • User uses Lovelace or other interface to toggle the generic thermostat state

Thermostat toggles heater switch

  • Generic thermostat toggles the heater switch state to reach the target temp
  • Heater switch state change does not toggle the generic thermostat as it was the thermostat which initiated the heater state change

This FR contradicts the basic operation of a thermostat.

A thermostat is used to govern the operation of the heating (or cooling) device. It’s the temperature differential, between ambient and target, that determines when the device should be turned on/off.

If ambient is equal to target, the thermostat ensures the heating/cooling device is off. If you manually turn on the heater, the thermostat will turn it off to prevent ambient rising above target. That’s the core responsibility of a thermostat, to ensure ambient is equal to target.


You propose to modify that behavior by turning off the thermostat when the heater is manually turned on by a user.

How do you propose to detect when the heater switch was turned on by a user? It can be detected if the heater’s entity is turned on in the UI (via context.user_id) but how will it be informed if the user presses the physical heater switch?

That was not my intention. My explanation may have been unclear.

The request is to have an option to treat the heater switch button as thermostat switch instead. The thermostat should exert full control of the heater to maintain target temperature or turn the heater off when the thermostat is turned off, just like a traditional thermostat on/off button.

Yes, that is the intended behavior in this case. The user who enabled this option should be aware that the button is no longer a heater switch but a thermostat switch, meaning the thermostat may turn the heater off.

I propose turning off the thermostat by pressing the heater button while the thermostat is on (button toggles thermostat). This may or may not be while the heater is on, depending on the room temperature. Pressing the button to toggle the thermostat from on to off while the heater is on would turn everything off. Doing so while the heater is off would briefly turn the heater on before everything turns off.

The major drawback is that the thermostat state user feedback is identical for on and off while the room is at the target temperature. The user should know the prior state before pressing the button.

I initially hoped the devs would find a way, which is why it’s a feature request and not an automation; however, after digging deeper I found this thread which explains a way to distinguish button presses from other triggers. If both parent_id and user_id of the event are null, the state_changed event was triggered by the device button. Given this new information, I can achieve the desired result using an automation as opposed to this feature request.

This should help you get started:

alias: Override Generic Thermostat
id: override_generic_thermostat
trigger:
  - platform: state
    entity_id: switch.your_heater
condition:
  - "{{ trigger.to_state.context.id != none }}"
  - "{{ trigger.to_state.context.parent_id == none }}"
  - "{{ trigger.to_state.context.user_id == none }}"
action:
  - service: "climate.turn_{{ 'off' if trigger.to_state.state == 'on' else 'on' }}"
    target:
      entity_id: climate.your_thermostat

When the heater switch changes state and the trigger variable’s context.id contains a value but context.parent_id and context.user_id contain null values, then the switch was operated manually. The automation’s action turns off the thermostat if the switch was turned on, otherwise it turns it on if the switch was turned off.

3 Likes

Hi there,
how would you apply this rule to multiple Generic Thermostats?
I tried to modify the code according to what you have suggested here, but I’m stuck at the point where I have to link a switch to the corresponding thermostat (target_fancoil)

alias: Override Generic Thermostat
id: override_generic_thermostat
trigger:
  - platform: state
    entity_id: 
      - switch.fabrizio_oo
      - switch.cucina_oo
      - switch.sala_oo
      - switch.cameretta_oo
      - switch.matrimoniale_oo
condition:
  - "{{ trigger.to_state.context.id != none }}"
  - "{{ trigger.to_state.context.parent_id == none }}"
  - "{{ trigger.to_state.context.user_id == none }}"
action:
  - variables:
      fancoils:
      - climate.termostato_fabrizio
      - climate.termostato_cucina
      - climate.termostato_sala
      - climate.termostato_cameretta
      - climate.termostato_matrimoniale
      target_fancoil: 
  - service: "climate.turn_{{ 'off' if trigger.to_state.state == 'off' else 'on' }}"
    target:
      entity_id: '{{ target_fancoil }}'
id: override_generic_thermostat
trigger:
  - platform: state
    entity_id: 
      - switch.fabrizio_oo
      - switch.cucina_oo
      - switch.sala_oo
      - switch.cameretta_oo
      - switch.matrimoniale_oo
condition:
  - "{{ trigger.to_state.context.id != none }}"
  - "{{ trigger.to_state.context.parent_id == none }}"
  - "{{ trigger.to_state.context.user_id == none }}"
action:
  - service: "climate.turn_{{ 'off' if trigger.to_state.state == 'off' else 'on' }}"
    target:
      entity_id: 'climate.termostato_{{ trigger.to_state.object_id[:-3] }}'
2 Likes

Worked perfectly, thank you so much. I would have never figured it out myself.

I believe something is missing in the condition tho.
Monitoring the behavior I found out the automation was triggered by the service “homeassistant.turn_off” which I suspect happened when the Thermostat reached the target temperature and the system itself toggled the switch.

I don’t see that service call anywhere in the posted code so I can’t help you with the issue you are experiencing.

Where Fancoil is the name of the switch and “Termostato Cucina” is the name of the corresponding generic thermostat I set up.

I can confirm it happens with all thermostats.

If you wish, you can report it as an Issue in the Home Assistant’s GitHub Core repository.

Thank you for your feedback.

I believe it’s not an issue with the core but rather the conditions logic.

This thread topic is exactly what I need to achieve and it might be useful to other users.

The solution you suggested is a good starting point, but it doesn’t work.
It would be nice to see someone else tested it.

Here’s how the context object usually behaves depending on who or what was the trigger’s source.

Action id parent_id user_id
Physical Not Null Null Null
Automation Not Null Not Null Null
UI Not Null Null Not Null

If your tests show that the behavior fails to match any of the combinations in the table, or corresponds to an unexpected combination, then it’s potentially an anomaly and something that might need to be reported.


EDIT

Corrected typo.

4 Likes

I’ve done it.

So, it turns out the Automation (at least the one triggered by the thermostat) does not have a parent_id as the context is considered the same.

Therefore telling apart a Physical or Thermostat trigger is impossible using the only context.

However, beyond the context, there is a core difference between the two actions, that is which entity initiated it.

The thermostat calls the service “homeassistant.turn_on”, in the other case the trigger is service “switch.turn_on”.

Looking at this log it is quite easy to tell when the switch (Fancoil) was triggered physically.

Is there a way to filter only the actions triggered by a device on itself?
Something like ‘trigger.entity_id == target.entity_id’ ?

Recently I’m receiving some situations in which the “physical” actually happens to behave as “automation” (i.e. parent_id is not null). That’s weird. Maybe something changed in the recent HA updates.

However, when trying to reproduce each case individually, I still get results based on the table.

Hm… I think Home Assistant sometimes assumes a state change was caused by some automation (while in fact it was not, but indeed the automation was triggered in the same timestamp). I’m adding a 5 seconds delay to the automation state trigger to help circumvent the issue. Not sure if it will help though.

How does this work for you? Seems to me that’s what user panhans uses in:

To detect a TRV’s physical change, these lines does the job (5 seconds and context conditions):

# trigger
  - platform: state
    entity_id: !input input_trvs
    attribute: temperature
    for:
      seconds: 5
    id: comfort_change

# further condition
      - condition: template
        value_template: "{{ trigger.id == 'comfort_change' and input_temperature_comfort != none and trigger.to_state.context.id != none and trigger.to_state.context.parent_id == none and trigger.to_state.context.user_id == none }}"