I have set up my home heating configuration based upon several items that I found across the forum. I have amended them all to my own need. It consists of a few parts that work together fine.
Background
The Eurotronic Spirit Thermostatic Radiator Valves (TRV from now on) have the ability to use an external thermometer (which can make sense, since reading the temperature right next to the radiator is not the most ideal solution on some cases). This however has some limits. One of which requires the thermometer to also be a zwave-device in the same zwave-group. (disclaimer. I don’t really understand how it would work, but apparently the required functionality isn’t supported in ZwaveJS and OpenZwave yet)
Since the external thermometer wasn’t a viable solution for me, I decided to make something that I can use to directly control the TRVs
I used to work with OpenZwave and pushed MQTT messages to change the valve position which worked fine. I however wanted to migrate to ZwaveJS and have amended my solution to the following. There are probably easier ways to do it, but this is how I got everything working for me
Step 1:Use OpenZwave to set the TRVs to 'Manufacturer Specific’
ZwaveJS does currently not support setting the TRVs to that mode.. A workaround for that is in the pipeline, but no indicative date for it.
I’m not going in details for that. Just use the GUI of OpenZwave. (using ozw-admin). If people really want, I’ll add some screenshots, but it should be fairly self-explanotory. But for completeness:
- Disable the ZwaveJs integration (which should also shut down the ZwaveJS addon)
- Start up an MQTT broker (if it isn’t running already)
- Start up OpenZwave
- Change the TRVs to be set on the Manufacturer Specific mode via ozw-admin or mqtt-commands
- Shut down OpenZwave
- Shut down MQTT broker (unless you use it for something else of course
)
- Enable the ZwaveJS integration (which should also start the ZwaveJS addon)
Step 2: Set up the Virtual Thermostats:
You will need to create a virtual thermostat, which needs a heater (switch) and a target_sensor (thermometer):
configuration.yaml:
climate: !include climate.yaml
climate.yaml:
## Virtuele thermostaten voor aansturing
- platform: generic_thermostat
name: Generieke thermostaat woonkamer
heater: switch.schakelaar_thermostaat_woonkamer
target_sensor: sensor.beweging_woonkamer_temperature
- platform: generic_thermostat
name: Generieke thermostaat gang
heater: switch.schakelaar_thermostaat_gang
target_sensor: sensor.beweging_gang_air_temperature
- platform: generic_thermostat
name: Generieke thermostaat slaapkamer
heater: switch.schakelaar_thermostaat_slaapkamer
target_sensor: sensor.beweging_slaapkamer_temperature
Step 3: Set up virtual heaters (switches)
The switches ‘turn on’ (which is what the virtual thermostat wants), by changing the position of the TRV. Effectively by calling a number.set_value
with to the entity_id of the TRV’s valve-position-entity with a value of how far the valve should open. It opens to a certain level depending on how much the ‘desired’ temperature deviates from the actual temperature’ to prevent ‘overheating’. So the closer the temperature gets to the
desired temperature, the lower the value of the radiator-valve. These values should be tweaked ‘per room’. In my example, they are all the same. If the desired temperature is 5 degrees lower than the actual temperature, the valve opens 99%. If it is 3 degrees lower, the valve open 85%, etc. etc. If you ‘switch off’ the virtual thermostat, the value shouldn’t be closed completely as appearently this can damage some boilers. (if all the valves are closed that can build up pressure somewhere. I am no specialist, but this is what I heard somewhere. Of course: Do as you will - I take no responsibility)
configuration.yaml:
switch: !include switch.yaml
switch.yaml:
## Virtuele schakelaars voor thermostaten
platform: template
switches:
schakelaar_thermostaat_woonkamer:
turn_on:
service: number.set_value
data_template:
entity_id: number.radiatorkraan_woonkamer_valve_control
value: >
{% if states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -5 %} 99
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -3 %} 85
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -2 %} 60
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -1 %} 50
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= 0.5 %} 35
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float >= 0.5 %} 0
{% else %} 10
{% endif %}
turn_off:
service: number.set_value
data:
entity_id: number.radiatorkraan_woonkamer_valve_control
value: 5
schakelaar_thermostaat_gang:
turn_on:
service: number.set_value
data_template:
entity_id: number.radiatorkraan_gang_valve_control
value: >
{% if states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -5 %} 99
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -3 %} 85
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -2 %} 60
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -1 %} 50
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -0.5 %} 35
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float >= 0.5 %} 0
{% else %} 10
{% endif %}
turn_off:
service: number.set_value
data:
entity_id: number.radiatorkraan_gang_valve_control
value: 5
schakelaar_thermostaat_slaapkamer:
turn_on:
service: number.set_value
data_template:
entity_id: number.radiatorkraan_slaapkamer_valve_control
value: >
{% if states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -5 %} 99
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -3 %} 85
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -2 %} 60
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -1 %} 50
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= 0.5 %} 35
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float >= 0.5 %} 0
{% else %} 10
{% endif %}
turn_off:
service: number.set_value
data:
entity_id: number.radiatorkraan_slaapkamer_valve_control
value: 5
Step 4: Change the valves as the temperature changes
In the step above, only the initial valve position is set. This will remain static as long as the virtual thermostat is ‘on’ so I created an automation that effective does the same as above, but is triggered when the reported temperatures change:
- id: radiatorknop_woonkamer_zet_goed
alias: Radiatorknop goedzetten woonkamer
description: Zet de thermostaat op een goede stand in woonkamer
initial_state: on
mode: single
trigger:
- platform: state
entity_id: sensor.beweging_woonkamer_temperature
condition:
- condition: state
entity_id: climate.generieke_thermostaat_woonkamer
state: heat
action:
service: number.set_value
data_template:
entity_id: number.radiatorkraan_woonkamer_valve_control
value: >
{% if states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -5 %} 99
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -3 %} 80
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -2 %} 60
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= -1 %} 50
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float <= 0.5 %} 35
{% elif states('sensor.beweging_woonkamer_temperature') | float - state_attr("climate.generieke_thermostaat_woonkamer", "temperature") | float >= 0.5 %} 0
{% else %} 10
{% endif %}
- id: radiatorknop_gang_zet_goed
alias: Radiatorknop goedzetten gang
description: Zet de thermostaat op een goede stand in gang
initial_state: on
mode: single
trigger:
- platform: state
entity_id: sensor.beweging_gang_air_temperature
condition:
- condition: state
entity_id: climate.generieke_thermostaat_gang
state: heat
action:
service: number.set_value
data_template:
entity_id: number.radiatorkraan_gang_valve_control
value: >
{% if states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -5 %} 99
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -3 %} 85
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -2 %} 60
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= -1 %} 50
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float <= 0.5 %} 35
{% elif states('sensor.beweging_gang_air_temperature') | float - state_attr("climate.generieke_thermostaat_gang", "temperature") | float >= 0.5 %} 0
{% else %} 10
{% endif %}
- id: radiatorknop_slaapkamer_zet_goed
alias: Radiatorknop goedzetten Slaapkamer
description: Zet de thermostaat op een goede stand in slaapkamer
initial_state: on
mode: single
trigger:
- platform: state
entity_id: sensor.beweging_slaapkamer_temperature
condition:
- condition: state
entity_id: climate.generieke_thermostaat_slaapkamer
state: heat
action:
service: number.set_value
data_template:
entity_id: number.radiatorkraan_slaapkamer_valve_control
value: >
{% if states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -5 %} 99
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -3 %} 85
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -2 %} 60
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= -1 %} 50
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float <= 0.5 %} 35
{% elif states('sensor.beweging_slaapkamer_temperature') | float - state_attr("climate.generieke_thermostaat_slaapkamer", "temperature") | float >= 0.5 %} 0
{% else %} 10
{% endif %}
For completeness. I switch my boiler off and on using a relais. The hallway (gang) is hashed out as I don’t the hallway to actually trigger the boiler. It only heats along if on of the other rooms requires heat.
- id: cv_aan_voor_warmte
alias: CV aan voor warmte
description: Schakelt de CV aan als er wamte nodig is.
trigger:
- entity_id: climate.generieke_thermostaat_woonkamer
platform: state
# - entity_id: climate.generieke_thermostaat_gang
# platform: state
- entity_id: climate.generieke_thermostaat_slaapkamer
platform: state
condition:
condition: or
conditions:
- condition: state
entity_id: climate.generieke_thermostaat_woonkamer
attribute: hvac_action
state: heating
# - condition: state
# entity_id: climate.generieke_thermostaat_gang
# attribute: hvac_action
# state: heating
- condition: state
entity_id: climate.generieke_thermostaat_slaapkamer
attribute: hvac_action
state: heating
action:
- service: switch.turn_on
entity_id: switch.cv_schakelaar
# CV uit voor verwarming (alle gemeten thermostaten moeten uit warmer zijn dan de ingestelde temperatuur)
- id: cv_uit_voor_warmte
alias: CV uit voor warmte
description: Schakelt de CV uit als er wamte nodig is.
trigger:
- entity_id: climate.generieke_thermostaat_woonkamer
platform: state
# - entity_id: climate.generieke_thermostaat_gang
# platform: state
- entity_id: climate.generieke_thermostaat_slaapkamer
platform: state
condition:
condition: and
conditions:
# - condition: state
# entity_id: climate.generieke_thermostaat_gang
# attribute: hvac_action
# state:
# - idle
# - off
- condition: state
entity_id: climate.generieke_thermostaat_woonkamer
attribute: hvac_action
state:
- idle
- off
- condition: state
entity_id: climate.generieke_thermostaat_slaapkamer
attribute: hvac_action
state:
- idle
- off
action:
- service: switch.turn_off
entity_id: switch.cv_schakelaar
Small caveat
This whole setup has one downside. And that is that you should NOT touch the ‘mode’ of your TRVs. If change the mode, then you will need to redo Step 1. (for as long as ZwaveJS is not able to change the mode to ‘Manufacturer Specific’.
Please let me know if you miss something or have suggestions to make this whole setup better/more efficient.
Edit: Missed a few typos during copy-pasting