Multi zone heating control with open window detection for opentherm boiler

The Good:
After a lot of planning, searching, and HELP from a lot of people I now consider friends for putting up with my requests/needs and for their time spent helping me, I present to you a very ugly but simple and efficient way to control the heating in one’s house or even multi apartment block, if one needs.
I guess this will be the most uninteresting post picture-wise:

There is also a third thermostat for the 3rd room.

This little card can be omitted, I just got some info displayed to be sure everything is working, and to check the status of some sensors.

What I wanted to share is the required coding needed in configuration.yaml . As this was very hard for me to do, and I hope that it will help other people looking to heat their homes more efficiently and more comfortably, without spending hundreds of dollars/euros or even more for this.

This project will allow one to control the heating temperature, heating on/off, enable/disable heating based on window being close/open per each zone or room, while controlling only one opentherm boiler, and allowing it to work as efficiently as possible.

The Bad:
Of course, the bad thing here (although a lot better than buying a ready system) is the cost.
You will need:
a. an opentherm adapter shield. Designed by Ihor, from http://ihormelnyk.com/ , the latest version can be bought from https://diyless.com/ .
20 euros shipped.
b. any kind of esp computer. nodemcu, d1 mini, esp32, ANYTHING. I used a nodemcu. I draw power from the boiler’s main unit (230VAC) and power the nodemcu through a tiny 230vac-5vcc psu rom aliexpress.
3+2 euros.
c. one zigbee or wifi TRV for each radiator you want to control. For areas wih multiple radiators, check if you have any kind of general distributor, which will save you a lot of money! Usually each room has a separate line from the main distributor.
I used saswell 801 zigbee from aliexpress
25 euros per piece, 75 euros for my 3 rooms.
d. window/door sensor. Any. xiaomi are best represented on aliexpress.
10 euros per piece, 30 euros for my 3 rooms.
e. special radiator/distributor valves, that are able to be controlled with TRVs. They should cost around ~10 euros a piece.
10 euros per piece, 30 euros for my 3 rooms.
f. zigbee coordinator, raspberry pi if you do not have a home assistant instance already, power supply, sd card…
50 euro.
TOTAL: 160 euro for me. instead of who knows how much… I already have a sevrer with a vm running HA.

The ugly:
Configuration.yaml:

climate:
#
  - platform: mqtt
    modes:
      - "off"
      - "heat"
    name: Dormitor HVAC
    current_temperature_topic: "HVAC/dormitor/ct"
    mode_command_topic: "HVAC/dormitor/mode/set"
    temperature_command_topic: "HVAC/dormitor/st"
    min_temp: 12
    max_temp: 26
    value_template: "{{ value }}"
    temp_step: 0.5
#
  - platform: mqtt
    modes:
      - "off"
      - "heat"
    name: Copii HVAC
    current_temperature_topic: "HVAC/copii/ct"
    mode_command_topic: "HVAC/copii/mode/set"
    temperature_command_topic: "HVAC/copii/st"
    min_temp: 12
    max_temp: 26
    value_template: "{{ value }}"
    temp_step: 0.5
#
  - platform: mqtt
    modes:
      - "off"
      - "heat"
    name: Sufragerie HVAC
    current_temperature_topic: "HVAC/sufragerie/ct"
    mode_command_topic: "HVAC/dormitor/sufragerie/set"
    temperature_command_topic: "HVAC/sufragerie/st"
    min_temp: 12
    max_temp: 26
    value_template: "{{ value }}"
    temp_step: 0.5
#
binary_sensor:
#
  - platform: mqtt
    name: copii_geam
    state_topic: 'zigbee2mqtt/Copii_geam'
    value_template: "{{ value_json.contact }}"
    payload_on: false
    payload_off: true
    qos: 0
#
  - platform: mqtt
    name: dormitor_geam
    state_topic: 'zigbee2mqtt/Dormitor_geam'
    value_template: "{{ value_json.contact }}"
    payload_on: false
    payload_off: true
    qos: 0
#
  - platform: mqtt
    name: sufragerie_geam
    state_topic: 'zigbee2mqtt/Sufragerie_geam'
    value_template: "{{ value_json.contact }}"
    payload_on: false
    payload_off: true
    qos: 0
#

One will have to make a thermostat integration for each needed zone that he wants controlled and a binary sensor (if needed, depending on the devices’ integration) for each window sensor.
automations.yaml:

- alias: temp_dormitor
  trigger:
    - platform: state
      entity_id: sensor.0x00158d000465809a_temperature
  action:
    service: mqtt.publish
    data: 
      payload: "{{ states('sensor.0x00158d000465809a_temperature') }}"
      topic: "HVAC/dormitor/ct"
      qos: 0
      retain: true
#
- alias: temp_copii
  trigger:
    - platform: state
      entity_id: sensor.0x00158d000464b329_temperature
  action:
    service: mqtt.publish
    data: 
      payload: "{{ states('sensor.0x00158d000464b329_temperature') }}"
      topic: "HVAC/copii/ct"
      qos: 0
      retain: true
#
- alias: temp_sufragerie
  trigger:
    - platform: state
      entity_id: sensor.0x00158d00045d01f4_temperature
  action:
    service: mqtt.publish
    data: 
      payload: "{{ states('sensor.0x00158d00045d01f4_temperature') }}"
      topic: "HVAC/sufragerie/ct"
      qos: 0
      retain: true
#
- alias: trv_copii
  trigger:
  - platform: state
    entity_id: climate.copii_hvac, sensor.0x00158d000464b329_temperature, binary_sensor.copii_geam
  action:
  - service: mqtt.publish
    data:
      payload_template: >-
        {% set targetTemp = state_attr('climate.copii_hvac', 'temperature') %}
        {% set currentTemp = states('sensor.0x00158d000464b329_temperature') %}
        {% set window = states('binary_sensor.copii_geam') %}
        {% set mode = states('climate.copii_hvac') %}
        {% if targetTemp == None or currentTemp == None or currentTemp == 'unknown' or window == None and mode == 'heat' %}
            {{ '{"system_mode": "heat"}' }}
        {% else %}
          {% if targetTemp|float > currentTemp|float and window == 'off' and mode == 'heat' %}
            {{ '{"system_mode": "heat"}' }}
          {% else %}
            {{ '{"system_mode": "off"}' }}
          {% endif %}
        {% endif %}
      topic: "zigbee2mqtt/Copii/set"
  - event: FIRE_OT_RECALC_EVENT
#
- alias: trv_dormitor
  trigger:
  - platform: state
    entity_id: climate.dormitor_hvac, sensor.0x00158d000465809a_temperature, binary_sensor.dormitor_geam
  action:
  - service: mqtt.publish
    data:
      payload_template: >-
        {% set targetTemp = state_attr('climate.dormitor_hvac', 'temperature') %}
        {% set currentTemp = states('sensor.0x00158d000465809a_temperature') %}
        {% set window = states('binary_sensor.dormitor_geam') %}
        {% set mode = states('climate.dormitor_hvac') %}
        {% if targetTemp == None or currentTemp == None or currentTemp == 'unknown' or window == None and mode == 'heat' %}
            {{ '{"system_mode": "heat"}' }}
        {% else %}
          {% if targetTemp|float > currentTemp|float and window == 'off' and mode == 'heat' %}
            {{ '{"system_mode": "heat"}' }}
          {% else %}
            {{ '{"system_mode": "off"}' }}
          {% endif %}
        {% endif %}
      topic: "zigbee2mqtt/Dormitor/set"
  - event: FIRE_OT_RECALC_EVENT
#
- alias: trv_sufragerie
  trigger:
  - platform: state
    entity_id: climate.sufragerie_hvac, sensor.0x00158d00045d01f4_temperature, binary_sensor.sufragerie_geam
  action:
  - service: mqtt.publish
    data:
      payload_template: >-
        {% set targetTemp = state_attr('climate.sufragerie_hvac', 'temperature') %}
        {% set currentTemp = states('sensor.0x00158d00045d01f4_temperature') %}
        {% set window = states('binary_sensor.sufragerie_geam') %}
        {% set mode = states('climate.sufragerie_hvac') %}
        {% if targetTemp == None or currentTemp == None or currentTemp == 'unknown' or window == None and mode == 'heat' %}
            {{ '{"system_mode": "heat"}' }}
        {% else %}
          {% if targetTemp|float > currentTemp|float and window == 'off' and mode == 'heat' %}
            {{ '{"system_mode": "heat"}' }}
          {% else %}
            {{ '{"system_mode": "off"}' }}
          {% endif %}
        {% endif %}
      topic: "zigbee2mqtt/Sufragerie/set"
  - event: FIRE_OT_RECALC_EVENT
#
- alias: OpenTherm Calculation
  trigger:
    - platform: event
      event_type: FIRE_OT_RECALC_EVENT
  action:
    - service: mqtt.publish
      data:
        payload_template: >-
          {% set targetTemp1 = state_attr('climate.dormitor_hvac', 'temperature') %}
          {% set currentTemp1 = states('sensor.0x00158d000465809a_temperature') %}
          {% set window1 = states('binary_sensor.dormitor_geam') %}
          {% set mode1 = states('climate.dormitor_hvac') %}
          {% set targetTemp2 = state_attr('climate.copii_hvac', 'temperature') %}
          {% set currentTemp2 = states('sensor.0x00158d000464b329_temperature') %}
          {% set window2 = states('binary_sensor.copii_geam') %}
          {% set mode2 = states('climate.copii_hvac') %}
          {% set targetTemp3 = state_attr('climate.sufragerie_hvac', 'temperature') %}
          {% set currentTemp3 = states('sensor.0x00158d00045d01f4_temperature') %}
          {% set window3 = states('binary_sensor.sufragerie_geam') %}
          {% set mode3 = states('climate.sufragerie_hvac') %}
          {% if targetTemp1|float > currentTemp1|float and window1 == 'off' and mode1 == 'heat' %}
            {% set result1 = targetTemp1 %}
          {% else %}
            {% set result1 = '30' %}
          {% endif %}
          {% if targetTemp2|float > currentTemp2|float and window2 == 'off' and mode2 == 'heat' %}
            {% set result2 = targetTemp2 %}
          {% else %}
            {% set result2 = '30' %}
          {% endif %}
          {% if targetTemp3|float > currentTemp3|float and mode3 == 'heat' %}
            {% set result3 = targetTemp3 %}
          {% else %}
            {% set result3 = '30' %}
          {% endif %}
          {% if result1 == [result1|float, result2|float, result3|float]|min and result1 < 30 %}
            {% set result = (targetTemp1,currentTemp1|float,'heat') %}
          {% else %}
            {% if result2 == [result1|float, result2|float, result3|float]|min and result2 < 30 %}
              {% set result = (targetTemp2,currentTemp2|float,'heat') %}
            {% else %}
              {% if result3 == [result1|float, result2|float, result3|float]|min and result3 < 30 %}
                {% set result = (targetTemp3,currentTemp3|float,'heat') %}
              {% else %}
                {% set result = (1.0, 20.0, 'off') %}
              {% endif%}
            {% endif %}
          {% endif %}
          {{ result }}
        topic: "HVAC/otresult"
        retain: true
#
- alias: OpenTherm Force Recalc
  trigger:
    - platform: time_pattern
      seconds: "/20"
  action:
    - event: FIRE_OT_RECALC_EVENT
#
- alias: OpenTherm Send
  trigger:
    - platform: mqtt
      topic: "HVAC/otresult"
  action:
    - service: mqtt.publish
      data:
        payload_template: "{{ trigger.payload.split(',')[0][1:] }}"
        topic: "opentherm-thermostat/setpoint-temperature/set"
        retain: true
        qos: 0
    - service: mqtt.publish
      data:
        payload_template: "{{ trigger.payload.split(',')[1][:-1:] }}"
        topic: "opentherm-thermostat/current-temperature/set"
        retain: true
        qos: 0
    - service: mqtt.publish
      data:
        payload_template: >
          {{ trigger.payload.split(',')[2][:-1] | replace("'","") }}
        topic: "opentherm-thermostat/mode/set"
        retain: true
        qos: 0
#

One will have to have a temperature sensor for each zone/room he needs to control, and this value must be published to a topic, as you can see in the first 3 automations.

One will have to have an automation for each of the TRVs that he wants to control. As I have 3 TRVs on my radiators, I have 3 automations, that will open the TRV in case there is any error in the messages regarding temperature or window and the mode of that area is set to heat; it will open if the mode is set to heating and current temperature is lower than set temperature; and t will close in case heating for that area is set to off or the room temperature is higher than set temperature.

The next automation is taking care that the opentherm boiler is only working when needed (any one of the areas/rooms needs hear e.g. set temp>current temp) and is always taking into consideration the room that needs the lowest set temperature, as this will make the boiler run most efficient for a condensing boiler unit.

The rest is just a way to publish the required data to the required mqtt topics.

This can be used to learn a lot about forwarding mqtt messages or parts of one dictionary. This gave me a big headache.

7 Likes

@nonyhaha could you please share some more info on the nodemcu part of this?
I have all the same parts over here, but not sure of I could stack the nodemcu on the shield or if I should use jumper wires.

What sketch did you use on the nodemcu?

Hi @marchano . If you take a look at part “the bad” point a, you will see in the blog posts the sketches used for nodemcu. I personally used a simple nodemcu with jumpwires and an ot adapter built by me to test everything out, but afterwards i ordered an ot adapter from diyless and mounted it on a d1 mini board.
I hope this is the info you are looking for, otherwise, let me know.
L.E. the sketch for nodemcu is the exact same for D1 board. The only difference is that you have to choose another board in arduino ide, when compiling the bin to flash it.

@nonyhaha i’ve used the sketches from Ihor Melnyk, whithout success…

in combination with the diyless master shield, does the led lights up on the master shield?
I’m not sure what it should do, maybe i’ve switched the input and output.

whatever I try, I have a message: invalid boiler response 0
t is allways -127.

maybe the boiler (Bulex Thermomaster 3HR) isn’t supporting opentherm, altough it is mentioned in the manual.

From its manual, I also understand it does support opentherm. Just make sure you set the correct input, output pins on the nodemcu. Be aware! Gpio numbers are not the same as on the d1 mini. Search on google for pinout, and be sure to match the d4,5 pins. Not the gpio ones.

It is connected to pin d1&d2, in the sketch 4 and 5.
Is this not correct?
Does the led lights on your shield?

Hi. I’m not sure if it’s easy or not to do the same with HomeAssistant. With Domoticz I have made a simple LUA script that record the temperature in each room and if it detected that temperature is rising (in Summer) or falling (in Winter) quickly, it sends a message by Telegram to notify that a window is open (and in the same way it’s possible to stop heating/cooling that room).
In this way the system works even for those who have not any sensor in the windows/doors.
Maybe you can do the same here. Less reliable than having sensors on windows, but works without installing those sensors.
Regards.
Paolo.

Those are the same pins. So it should be correct.
I think my led is not lighting up but i must take a look again.

According Diyless, the led should light up if there is communication between boiler and shield.
I do not have the knowledge to check if there is any communication between shield and nodemcu.

is it possible to set gas temperature with this module? I’m using a Turkish brand named DemirDokum boiler which is model Atromix and i couldn’t find any usefull information that my boilerplate is support opentherm protocol or not,

I would like to get one piece if it’s possible to use with my bolier.

Thanks for information…

Hello @hardc0der . This adapter is intended to be used only with opentherm. It is best to check first if your boiler supports it. If it has modulation, it should support some kind of communication. If it has a wired thermostat without batteries it definetly has smart communication.that is all i can say about a hvac system without its manual.

1 Like