ESPHome + Opentherm Compilation

Evening all,

I’m wanting to use D1 Mini + DIYLESS.com OpenTherm shield to control my Boiler using modulation.

I know there will be plenty questions / advice cause from searching I think my use case is semi unique… however.com right now I would settle for a Repo which will actually compile and install?

And suggestions? Just to get something installed and working I wanted to start with GitHub - rsciriano/ESPHome-OpenTherm: Example of how to control an opentherm boiler with esphome but I get the attached errors.

INFO Reading configuration /config/esphome/esphome-web-ab09f9.yaml...
INFO Generating C++ source...
INFO Compiling app...
Processing esphome-web-ab09f9 (board: d1_mini; framework: arduino; platform: platformio/espressif8266 @ 3.2.0)
--------------------------------------------------------------------------------
HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
Dependency Graph
|-- <OpenTherm Library> 1.1.0
|-- <ESP Async WebServer> 1.2.3
|   |-- <ESPAsyncTCP> 1.2.2
|   |-- <Hash> 1.0
|-- <ESPAsyncTCP-esphome> 1.2.3
|-- <ESPAsyncWebServer-esphome> 2.1.0
|   |-- <ESPAsyncTCP-esphome> 1.2.3
|   |-- <Hash> 1.0
|   |-- <ESP8266WiFi> 1.0
|-- <DNSServer> 1.1.1
|-- <ESP8266WiFi> 1.0
|-- <ESP8266mDNS> 1.2
Archiving /data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/libb84/libESPAsyncTCP.a
Archiving /data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/libaf0/libHash.a
Compiling /data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/lib3cd/ESP Async WebServer/AsyncEventSource.cpp.o
Compiling /data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/lib3cd/ESP Async WebServer/AsyncWebSocket.cpp.o
Compiling /data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/lib3cd/ESP Async WebServer/SPIFFSEditor.cpp.o
Compiling /data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/lib3cd/ESP Async WebServer/WebAuthentication.cpp.o
In file included from /data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/AsyncEventSource.h:29,
                 from /data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/AsyncEventSource.cpp:21:
/data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/ESPAsyncWebServer.h:35:10: fatal error: ESP8266WiFi.h: No such file or directory

*********************************************************************
* Looking for ESP8266WiFi.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:ESP8266WiFi.h"
* Web  > https://registry.platformio.org/search?q=header:ESP8266WiFi.h
*
*********************************************************************

   35 | #include <ESP8266WiFi.h>
      |          ^~~~~~~~~~~~~~~
compilation terminated.
In file included from /data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/AsyncWebSocket.h:32,
                 from /data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/AsyncWebSocket.cpp:22:
/data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/ESPAsyncWebServer.h:35:10: fatal error: ESP8266WiFi.h: No such file or directory

*********************************************************************
* Looking for ESP8266WiFi.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:ESP8266WiFi.h"
* Web  > https://registry.platformio.org/search?q=header:ESP8266WiFi.h
*
*********************************************************************

   35 | #include <ESP8266WiFi.h>
      |          ^~~~~~~~~~~~~~~
compilation terminated.
*** [/data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/lib3cd/ESP Async WebServer/AsyncEventSource.cpp.o] Error 1
*** [/data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/lib3cd/ESP Async WebServer/AsyncWebSocket.cpp.o] Error 1
In file included from /data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/SPIFFSEditor.h:3,
                 from /data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/SPIFFSEditor.cpp:1:
/data/esphome-web-ab09f9/.piolibdeps/esphome-web-ab09f9/ESP Async WebServer/src/ESPAsyncWebServer.h:35:10: fatal error: ESP8266WiFi.h: No such file or directory

*********************************************************************
* Looking for ESP8266WiFi.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:ESP8266WiFi.h"
* Web  > https://registry.platformio.org/search?q=header:ESP8266WiFi.h
*
*********************************************************************

   35 | #include <ESP8266WiFi.h>
      |          ^~~~~~~~~~~~~~~
compilation terminated.
*** [/data/esphome-web-ab09f9/.pioenvs/esphome-web-ab09f9/lib3cd/ESP Async WebServer/SPIFFSEditor.cpp.o] Error 1

TIA

I’m following this one.
Till few weeks ago I was using the yamp code below, but switched back to the mqtt code from the diyless github.
Reason was the unstability and many errors while communicating with the heater.
Still not convinced with the mqtt version also.

substitutions:
  devicename: opentherm
  upper_devicename: Opentherm

esphome:
  name: $devicename
  platform: ESP8266
  board: d1_mini
  platformio_options:
    lib_deps: 
    - ihormelnyk/OpenTherm Library @ 1.1.3
  includes:
    - esphome-opentherm/

wifi:
  ssid: "******************************"
  password: "**********"
  ap:
    ssid: "${devicename} Fallback"
    password: "**********"
  power_save_mode: none


captive_portal:
logger:
  level: DEBUG
#  logs:
#    opentherm_component: NONE
#    climate: NONE
#    sensor: NONE
api:
ota:
web_server:
  port: 80

status_led:
  pin: 
    number: D4    #GPIO2
    inverted: true

custom_component:
  - lambda: |-
      auto opentherm = new OpenthermComponent();
      return {opentherm};
    
    components:
      - id: opentherm
output:
  - platform: custom
    type: float
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      auto opentherm_pid_output = new OpenthermFloatOutput();
      openthermComp->set_pid_output(opentherm_pid_output);
      App.register_component(opentherm_pid_output);     
      return {opentherm_pid_output};
    outputs:
      id: pid_output
      #min_power: 30.00%
      #max_power: 60.00%

sensor:
#  - platform: wifi_signal
#    name: "WiFi Signal Sensor"
#    update_interval: 60s

  - platform: custom
    lambda: |-    
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return { 
        openthermComp->boiler_temperature, 
        openthermComp->external_temperature_sensor, 
        openthermComp->return_temperature_sensor, 
        openthermComp->pressure_sensor,
        openthermComp->modulation_sensor,
        openthermComp->heating_target_temperature_sensor
      };
    sensors:
    - name: Boiler Temperature
      unit_of_measurement: °C
      accuracy_decimals: 1
    - name: "External Temperature"
      unit_of_measurement: °C
      accuracy_decimals: 0  
    - name: "Return Temperature"
      unit_of_measurement: °C
      accuracy_decimals: 1
    - name: "Heating Water Pressure"
      unit_of_measurement: hPa
      accuracy_decimals: 1
    - name: "Boiler Modulation"
      unit_of_measurement: "%"
      accuracy_decimals: 0
    - name: "Heating Target Temperature"
      unit_of_measurement: °C
      accuracy_decimals: 1
      
  - platform: homeassistant
    id: temperature_sensor
    entity_id: sensor.woonkamer_temperatuur
    name: "Actual temperature"
    unit_of_measurement: °C
  - platform: pid
    name: "PID Climate Result"
    type: RESULT    
  - platform: pid
    name: "PID Climate HEAT"
    type: HEAT    
  - platform: pid
    name: "PID Climate ERROR"
    type: ERROR    

binary_sensor:
  - platform: custom
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return {openthermComp->flame};
    binary_sensors:
    - name: "Flame"
      #device_class: heat

switch:
  - platform: custom
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return {openthermComp->thermostatSwitch};
    switches:
      name: "Disable PID"
  - platform: template
    name: "PID Climate Autotune"
    turn_on_action:
      - climate.pid.autotune: pid_climate      

climate:
  - platform: custom
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return {
        openthermComp->hotWaterClimate, 
        openthermComp->heatingWaterClimate
      };
    climates:
      - id: hot_water
        name: "Hot water"
      - id: heating_water
        name: "Heating water"
  - platform: pid
    id: pid_climate
    name: "PID Climate Controller"
    visual:
      min_temperature: 16 °C
      max_temperature: 75 °C
      temperature_step: 0.5 °C
    sensor: temperature_sensor
    default_target_temperature: 20°C
    heat_output: pid_output
    control_parameters:
      kp: 20.0
      ki: 0.0
      kd: 0.0

Thanks; I’m still figuring it out but I’m starting to lean towards MQTT sketch!

I have the hardware too. I would need a working ESPhome version. Thanks in advance if anyone can help.

Hey @envagyok. I’m usiing the ESPhome verion by rsciriano.
I just finalised the first version of the documentation on that. Check it out, it does actually work :slight_smile:

My documentation

2 Likes

After a few weeks of head scratching and arguments with the wife over why the heating wasn’t working at 0600; I can confirm it does work and all the “subtleties” @foltymat mentions in his documentation are valid.

In fact it’s a big thanks to @foltymat for that because I could not figure out why if the set point and measured temperature were close the boiler would refuse to heat the water ( only pump at 25 deg. C). I’ve just now added a heartbeat: XXs filter to the Virtual Setpoint sensor and that seems to have done the trick :crossed_fingers:. I can also confirm 25 degrees recirculation seems unneeded ( I have UFH throughout bar 2 x Rads in the basement) and the PID Disable switch has no effect according to the logs!

For completeness see my yaml, templates sensor and automation below:

YAML:


substitutions:
  device_name: opentherm
  friendly_name: Opentherm

esphome:
  name: "$device_name"
  platform: ESP8266
  board: d1_mini
  platformio_options:
    lib_deps: 
    - ihormelnyk/OpenTherm Library @ 1.1.3
#    - https://github.com/opbokke/opentherm_library
  includes:
    - esphome-opentherm/

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none


captive_portal:
logger:
api:
ota:

custom_component:
  - lambda: |-
      auto opentherm = new OpenthermComponent();
      return {opentherm};
    
    components:
      - id: opentherm
output:
  - platform: custom
    type: float
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      auto opentherm_pid_output = new OpenthermFloatOutput();
      openthermComp->set_pid_output(opentherm_pid_output);
      App.register_component(opentherm_pid_output);     
      return {opentherm_pid_output};
    outputs:
      id: pid_output
      #min_power: 30.00%
      #max_power: 60.00%

sensor:
  - platform: custom
    lambda: |-    
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return { 
        openthermComp->boiler_temperature, 
        openthermComp->external_temperature_sensor, 
        openthermComp->return_temperature_sensor, 
        openthermComp->pressure_sensor,
        openthermComp->modulation_sensor,
        openthermComp->heating_target_temperature_sensor
      };
    sensors:
    - name: "Boiler Temperature"
      unit_of_measurement: °C
      accuracy_decimals: 1
    - name: "External Temperature"
      unit_of_measurement: °C
      accuracy_decimals: 0  
    - name: "Return Temperature"
      unit_of_measurement: °C
      accuracy_decimals: 1
    - name: "Heating Water Pressure"
      unit_of_measurement: hPa
      accuracy_decimals: 1
    - name: "Boiler Modulation"
      unit_of_measurement: "%"
      accuracy_decimals: 0
    - name: "Heating Target Temperature"
      unit_of_measurement: °C
      accuracy_decimals: 1
      
  - platform: homeassistant
    id: temperature_sensor
    entity_id: sensor.temperature_ch_virtual
    name: "Actual temperature"
    unit_of_measurement: °C
    filters:
      - heartbeat: 10s
  - platform: pid
    name: "PID Climate Result"
    type: RESULT    
  - platform: pid
    name: "PID Climate HEAT"
    type: HEAT    
  - platform: pid
    name: "PID Climate ERROR"
    type: ERROR    

binary_sensor:
  - platform: custom
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return {openthermComp->flame};
    binary_sensors:
    - name: "Flame"
      #device_class: heat

switch:
  - platform: custom
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return {openthermComp->thermostatSwitch};
    switches:
      name: "Disable PID"
  - platform: template
    name: "PID Climate Autotune"
    turn_on_action:
      - climate.pid.autotune: pid_climate      

climate:
  - platform: custom
    lambda: |-
      OpenthermComponent *openthermComp = (OpenthermComponent*) opentherm;
      return {
        openthermComp->hotWaterClimate, 
        openthermComp->heatingWaterClimate
      };
    climates:
      - id: hot_water
        name: "Hot water"
      - id: heating_water
        name: "Heating water"
  - platform: pid
    id: pid_climate
    name: "PID Climate Controller"
    visual:
      min_temperature: 5 °C
      max_temperature: 35 °C
      temperature_step: 0.1 °C
    sensor: temperature_sensor
    default_target_temperature: 18
    heat_output: pid_output
    control_parameters:
      kp: 0
      ki: 0
      kd: 0

Virtual Setpoint and Temperature:

      setpoint_ch_virtual:
        friendly_name: "CH Virtual Setpoint"
        unique_id: 73092-jdt
        unit_of_measurement: "°C"
        value_template: >
          {% set attic_td = (state_attr('climate.attic','temperature') - states('sensor.attic_temperature') | float) %}
          {% set basement_td = (state_attr('climate.basement','temperature') - states('sensor.basement_temperature') | float) %}
          {% set bedroom_td = (state_attr('climate.bedroom','temperature') - states('sensor.bedroom_temperature') | float) %}
          {% set kitchen_td = (state_attr('climate.kitchen','temperature') - states('sensor.kitchen_temperature') | float) %}
          {% set living_room_td = (state_attr('climate.living_room','temperature') - states('sensor.living_room_temperature') | float) %}
          {% set office_td = (state_attr('climate.office','temperature') - states('sensor.office_temperature') | float) %}
          {% set max_td = ([attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] | max) %}
          {% set list_room_td = [attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] %}
          {% set index_td = list_room_td.index(max_td) %}
          {% if index_td == 1 %}
            {{ state_attr('climate.basement','temperature') }}
          {% elif index_td == 2 %}
            {{ state_attr('climate.bedroom','temperature') }}
          {% elif index_td == 3 %}
            {{ state_attr('climate.kitchen','temperature') }}
          {% elif index_td == 4 %}
            {{ state_attr('climate.living_room','temperature') }}
          {% elif index_td == 5 %}
            {{ state_attr('climate.office','temperature') }}
          {% else %}
            {{ state_attr('climate.attic','temperature') }}
          {% endif %}
          
      temperature_ch_virtual:
        friendly_name: "CH Virtual Temperature"
        unique_id: 73926-slt
        unit_of_measurement: "°C"
        value_template: >
          {% set attic_td = (state_attr('climate.attic','temperature') - states('sensor.attic_temperature') | float) %}
          {% set basement_td = (state_attr('climate.basement','temperature') - states('sensor.basement_temperature') | float) %}
          {% set bedroom_td = (state_attr('climate.bedroom','temperature') - states('sensor.bedroom_temperature') | float) %}
          {% set kitchen_td = (state_attr('climate.kitchen','temperature') - states('sensor.kitchen_temperature') | float) %}
          {% set living_room_td = (state_attr('climate.living_room','temperature') - states('sensor.living_room_temperature') | float) %}
          {% set office_td = (state_attr('climate.office','temperature') - states('sensor.office_temperature') | float) %}
          {% set max_td = ([attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] | max) %}
          {% set list_room_td = [attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] %}
          {% set index_td = list_room_td.index(max_td) %}
          {% if index_td == 1 %}
            {{ states('sensor.basement_temperature') }}
          {% elif index_td == 2 %}
            {{ states('sensor.bedroom_temperature') }}
          {% elif index_td == 3 %}
            {{ states('sensor.kitchen_temperature') }}
          {% elif index_td == 4 %}
            {{ states('sensor.living_room_temperature') }}
          {% elif index_td == 5 %}
            {{ states('sensor.office_temperature') }}
          {% else %}
            {{ states('sensor.attic_temperature') }}
          {% endif %}

Automation:


alias: OpenTherm Heating
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.temperature_ch_virtual
  - platform: state
    entity_id:
      - sensor.setpoint_ch_virtual
condition: []
action:
  - service: climate.set_temperature
    entity_id: climate.pid_climate_controller
    data_template:
      temperature: "{{ states('sensor.setpoint_ch_virtual') }}"
  - if:
      - condition: state
        entity_id: climate.opentherm_nest_group
        attribute: hvac_action
        state: heating
    then:
      - service: climate.set_hvac_mode
        data:
          hvac_mode: heat
        target:
          entity_id: climate.heating_water
    else:
      - service: climate.set_hvac_mode
        data:
          hvac_mode: "off"
        target:
          entity_id: climate.heating_water
mode: single
3 Likes

Thanks for sharing the code @alxscott !
Mainly the virtual set point is on by bucket list once the new Smart TRVs arrive.

Quick update!

My ESPHome installation hadn’t been Autoupdating and was quite out of date.

After updating I lost the sensor showing the Virtual Temperature on the D1 Mini ( used for the PID Target Temp”).

Long story short I reformatted the Template Sesnors to the new format and that code is below:


      - name: CH Virtual Setpoint
        # friendly_name: "CH Virtual Setpoint"
        unique_id: 73092-jdt
        device_class: "temperature"
        state_class: "measurement"
        unit_of_measurement: "°C"
        state: >
          {% set attic_td = (state_attr('climate.attic','temperature') - states('sensor.attic_temperature') | float) %}
          {% set basement_td = (state_attr('climate.basement','temperature') - states('sensor.basement_temperature') | float) %}
          {% set bedroom_td = (state_attr('climate.bedroom','temperature') - states('sensor.bedroom_temperature') | float) %}
          {% set kitchen_td = (state_attr('climate.kitchen','temperature') - states('sensor.kitchen_temperature') | float) %}
          {% set living_room_td = (state_attr('climate.living_room','temperature') - states('sensor.living_room_temperature') | float) %}
          {% set office_td = (state_attr('climate.office','temperature') - states('sensor.office_temperature') | float) %}
          {% set max_td = ([attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] | max) %}
          {% set list_room_td = [attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] %}
          {% set index_td = list_room_td.index(max_td) %}
          {% if index_td == 1 %}
            {{ state_attr('climate.basement','temperature') }}
          {% elif index_td == 2 %}
            {{ state_attr('climate.bedroom','temperature') }}
          {% elif index_td == 3 %}
            {{ state_attr('climate.kitchen','temperature') }}
          {% elif index_td == 4 %}
            {{ state_attr('climate.living_room','temperature') }}
          {% elif index_td == 5 %}
            {{ state_attr('climate.office','temperature') }}
          {% else %}
            {{ state_attr('climate.attic','temperature') }}
          {% endif %}

      - name: CH Virtual Temperature
        # friendly_name: "CH Virtual Temperature"
        unique_id: 73926-slt
        device_class: "temperature"
        state_class: "measurement"
        unit_of_measurement: "°C"
        state: >
          {% set attic_td = (state_attr('climate.attic','temperature') - states('sensor.attic_temperature') | float) %}
          {% set basement_td = (state_attr('climate.basement','temperature') - states('sensor.basement_temperature') | float) %}
          {% set bedroom_td = (state_attr('climate.bedroom','temperature') - states('sensor.bedroom_temperature') | float) %}
          {% set kitchen_td = (state_attr('climate.kitchen','temperature') - states('sensor.kitchen_temperature') | float) %}
          {% set living_room_td = (state_attr('climate.living_room','temperature') - states('sensor.living_room_temperature') | float) %}
          {% set office_td = (state_attr('climate.office','temperature') - states('sensor.office_temperature') | float) %}
          {% set max_td = ([attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] | max) %}
          {% set list_room_td = [attic_td,basement_td,bedroom_td,kitchen_td,living_room_td,office_td] %}
          {% set index_td = list_room_td.index(max_td) %}
          {% if index_td == 1 %}
            {{ states('sensor.basement_temperature') }}
          {% elif index_td == 2 %}
            {{ states('sensor.bedroom_temperature') }}
          {% elif index_td == 3 %}
            {{ states('sensor.kitchen_temperature') }}
          {% elif index_td == 4 %}
            {{ states('sensor.living_room_temperature') }}
          {% elif index_td == 5 %}
            {{ states('sensor.office_temperature') }}
          {% else %}
            {{ states('sensor.attic_temperature') }}
          {% endif %}

PID disable doesn’t seem to do much but the PID Loop in general seems to be performing much more as expected.

1 Like

Hi @alxscott , thanks for sharing your code. I just got my DIYLESS module and flashed succesfully. Now integrating everything into HA, and trying to understand your automation. Could you explain what your “climate.opentherm_nest_group” is and what it does in the automation? Why does it define switching on or off the boiler? How is that related to the heat request in the virtual temp and setpoint?
Hope you can explain, as I really like your setup.

Hi @gdschut

Thanks for the compliment!

So, full disclosure, I’m not using the Opentherm setup anymore. 90% of my house is UFH and as it’s a modern boiler that automatically reduces boiler modulation dependant on return water temperature the Opentherm setup was just introducing additional lag to an already slow system to heat. This then led to increased heating costs negating the advantage of the PID loop.

However; if I remember rightly the reason I needed the automation was that although the Opentherm Thermostat was receiving a set point higher than measured; it was not calling for heat.

The Nest group is simply a manually created group of my Thermostats which shows a “hvac action” value of Heating if any of the thermostats are currently making a call for heat. This then nicely adds the additional requirement for a Heating call to the PID Controller ( Opentherm).

Does that make sense?

1 Like

Thanks! Makes sense to me :slight_smile:
I will create another virtual sensor for heat demand.

Hi everybody,
I just released a Home Assistant thermostat (Climate entity) named Advanced_Thermostat.ino that can be compiled and flashed from the Arduino IDE (search for ‘EasyOpenTherm’ in the library manager). It is tested on an ESP32-S2 mini, an ESP32-C3 mini and a ESP32-S NodeMCU. The thermostat depends on the MQTT Integration and offers auto discovery of the thermostat itself, along with several central heating boiler sensors. It subscribes to a MQTT topic to receive the room temperature from any thermometer in Home Assistant. I use an automation for this.
Is this of any interest to you? Would love to get feedback! :slight_smile:

1 Like

Hi everybody…
anyone has worked on Esphome implementation useful for a Opentherm GATEWAY? I’ve “played” with all the arduino codes connected to HA via MQTT , but the esphome integration is preferred to me.

All that i can find are working esphome configuration as a Thermostat , with a Master opentherm connection. But in this case i have to “forget” to use all the existing wall thermostats (legacy fixed hw).
Anyone implemented in esphome the gateway function (master and slave) available in OT libraries and arduino code?

EDIT: Obviusly i have the gateway needed hw (from dyiless)

1 Like

I’ve just started to try. I’ve been previously running a gateway on Arduino, and I’ve wanted to migrate to esphome for some time.

I now have the very first version based on Ihor’s OpenTherm library. Although my Arduino implementation was based on jprusa’s library, I could not get it up and running on ESP32 - seemed to be a timing problem. It does not do much, just forwarding requests and responses; I plan to 1. add sensors to report values, and then 2. to allow to override requests coming from thermostat, to for example force-boost circulation pump or DHW temperature temporarily.

2 Likes

So, if anyone is still interested, I created an external ESPHome component for an Opentherm gateway.

Please check here:

2 Likes

thanks for your effort! I’d love to be able to use both the master and slave in ESPhome. I’ve implemented your code in my project but I’m unable to read any data from the boiler.
Am I missing something?
I’m using an esp8266 (Wemos D1 mini)

[20:33:06][D][openthermgw_component_21:214]: acme messages handdled: 17
[20:33:06][D][openthermgw_component_21:215]: acme binary messages handdled: 1
[20:33:06][D][openthermgw_component_21:216]: acme binary overrides handdled: 1
esphome:
  name: thermostat
  friendly_name: thermostat

esp8266:
  board: esp01_1m

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "***"

ota:
  password: "***"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Thermostat Fallback Hotspot"
    password: "***"

captive_portal:

external_components:
  - source: github://Reproduktor/esphome-openthermgw
    components: [ openthermgw ]

openthermgw:
  master_in_pin: 4
  master_out_pin: 5
  slave_in_pin: 12
  slave_out_pin: 13
  acme_opentherm_sensor_list:
    - name: "ACME Control setpoint"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 1
      value_on_request: false
      value_type: 2
    - name: "ACME ASF flags / OEM Fault code"
      message_id: 5
      value_on_request: false
      value_type: 0
    - name: "ACME ASF flags / OEM Fault code response"
      message_id: 5
      value_on_request: false
      value_type: 7
    - name: "ACME Control setpoint 2"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 8
      value_on_request: false
      value_type: 2
    - name: "ACME OEM diagnostic code"
      message_id: 115
      value_on_request: false
      value_type: 0
    - name: "ACME OEM diagnostic code response"
      message_id: 115
      value_on_request: false
      value_type: 7

    - name: "ACME Room setpoint"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 16
      value_on_request: false
      value_type: 2
    - name: "ACME Relative modulation level"
      device_class: "signal_strength"
      accuracy_decimals: 0
      unit_of_measurement: "%"
      message_id: 17
      value_on_request: false
      value_type: 2
    - name: "ACME CH water pressure"
      device_class: "pressure"
      accuracy_decimals: 2
      unit_of_measurement: "bar"
      message_id: 18
      value_on_request: false
      value_type: 2

    - name: "ACME Room setpoint CH2"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 23
      value_on_request: false
      value_type: 2
    - name: "ACME Room temperature"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 24
      value_on_request: false
      value_type: 2
    - name: "ACME Boiler water temperature"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 25
      value_on_request: false
      value_type: 2
    - name: "ACME DHW temperature"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 26
      value_on_request: false
      value_type: 2
    - name: "ACME Outside temperature"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 27
      value_on_request: false
      value_type: 2
    - name: "ACME Return water temperature"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 28
      value_on_request: false
      value_type: 2
    - name: "ACME Flow temperature CH2"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 31
      value_on_request: false
      value_type: 2
    - name: "ACME Exhaust temperature"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 33
      value_on_request: false
      value_type: 1

    - name: "ACME DHW setpoint"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 56
      value_on_request: false
      value_type: 2

    - name: "ACME Max CH water setpoint"
      device_class: "temperature"
      accuracy_decimals: 1
      unit_of_measurement: "°C"
      message_id: 57
      value_on_request: false
      value_type: 2

  acme_opentherm_binary_sensors:
    - name: "ACME Boiler fault"
      message_id: 0
      value_on_request: false
      bitindex: 1
    - name: "ACME Boiler CH mode"
      message_id: 0
      value_on_request: false
      bitindex: 2
    - name: "ACME Boiler DHW mode"
      message_id: 0
      value_on_request: false
      bitindex: 3
    - name: "ACME Boiler flame status"
      message_id: 0
      value_on_request: false
      bitindex: 4
    - name: "ACME Boiler CH2 mode"
      message_id: 0
      value_on_request: false
      bitindex: 6
    - name: "ACME Boiler diagnostic indication"
      message_id: 0
      value_on_request: false
      bitindex: 7

    - name: "ACME Control CH enable"
      message_id: 0
      value_on_request: false
      bitindex: 9
    - name: "ACME Control DHW enable"
      message_id: 0
      value_on_request: false
      bitindex: 10
    - name: "ACME Control CH2 enable"
      message_id: 0
      value_on_request: false
      bitindex: 13

  acme_opentherm_override_binary_switches:
    - name: "OT override DHW circulating pump"
      message_id: 129
      value_on_request: true
      bitindex: 4
      acme_opentherm_override_binary_value:
        name: "OT override DHW circulating pump with"  

Hi, first of all - the component outputs each request and reply in the debug log. Do you see any of those logged, or none at all? If none, then I’d start with checking if the configured pins are correct for master (thermostat) and slave(boiler).

Another question regarding potential compatibility problem - do you already know, if your opentherm gateway and your esp work with Ihor’s opentherm library? If no, it would maybe be worth checking first.

I have these 2 products:
https://diyless.com/product/master-opentherm-shield
https://diyless.com/product/slave-opentherm-shield

last winter t I was able to control the boiler with the master shield + esp8266 in HA and could read various data from it. I used the following code (not ESPhome): GitHub - Joparmen/diyless-opentherm-ota: OTA addition to diyless opentherm for home assistant

The master+slave shields are supposed to be stacked on top of each other, and on top of that sits a wemos d1 mini (esp8266). I have used the esp with just the master shield and that worked before (last winter). I have triple checked the pins and removed the slave shield, but unfortunately can’t get it to work. All the sensors show up in HA but every sensor shows ‘unknown’ as it’s data. The output in the log repeats the following:

[21:32:32][D][openthermgw_component_21:214]: acme messages handdled: 17
[21:32:32][D][openthermgw_component_21:215]: acme binary messages handdled: 1
[21:32:32][D][openthermgw_component_21:216]: acme binary overrides handdled: 1

I see, so you have this setup: ESP32/ESP8266 OpenTherm Gateway - DIYLESS Electronics
… and you connect the thermostat to your slave shield, and your boiler to your master shield. Correct?

Then maybe just a note, to be certain we have this correct, and sorry if this is redundant: in the gateway component setup, the Master pins are meant to mean the pins connecting to the Master device (and slave pins connect to the slave device). Your master device is of course the thermostat, which is connected via the ‘Slave’ shield. So the master_in_pin and master_out_pin need to be configured to the pins where your SLAVE shield is connected. And vice versa. I hope it makes sense :slight_smile:

1 Like

Your master device is of course the thermostat, which is connected via the ‘Slave’ shield. So the master_in_pin and master_out_pin need to be configured to the pins where your SLAVE shield is connected. And vice versa.

omg that was it! Thank you so much. It’s almost bed time, so I’m going to tinker further with this later this week. It is possible to override values like the ‘room setpoint’? With the previous code, I was able to with mqtt set commands. That would be great to have because then I can set a bunch of automations in HA based on home/not-home, time, outside temp etc AND be able to override it by using the thermostat in the living room. Being able to override it would greatly improve the WAF.

1 Like