HERMES ELECTRONIC WR3223 ventilation controller ESP32

Edit: @schmurgel created a custom component - thanks very much!

Hi there,

I know - it may be a strange request - I completely (almost) moved from OH to HA but in my house I’ve got a “Hermes Electronic WR3223” ventilation controller which I was able to control from OH because there is an existing binding:

It is on GitHub available, please refer to these links:

Unfortunately I’m not able to create integrations in HA - this is why I’m looking for a dev who could create this for me and the community. Surely this could be rewarded.

Maybe there is a brave soul out there who could help.
If this is not the right place to ask for help (in this community), please let me know or delete this topic.

PS: currently I’m using the custom component “openHAB integration” by kubawolanin ! Thanks man!


Hi Chris,

today I tried to connect my WR3223 (Schwörer WGT134) via ESPHome with HA.

I used a very simple setup to test the possibilities and it was a success.
I connected the ESP32 directly to the TTL port in the WR3223:


You can connect TX and RX to your defined UART GPIOs and feed the ESP32 with the 5V directly from the port.

I used this specification:
together with this:

In this way I was able to establish a simple connection between the WR3223 and Home Assistant very quickly. As a test, I first queried the temperature T2.
I have not yet processed the values ​​or set values. I will probably use the existing openHab binding as a guide here.

Best regards

1 Like

I made some progress und puplished most of the temperature sensors of the wr3223 to ha.
So I programmed a custom UARTDevice (see [Custom UART Device — ESPHome])(Custom UART Device — ESPHome)

Here is the code for my custom device (sorry for the mess, its my first time developing in c++ :sweat_smile:)

#include "esphome.h"

class MessageControl {
    /** Start der Nachricht */
    static constexpr const uint8_t STX = 0x02;
    /** Ende der Nachricht */
    static constexpr const uint8_t ETX = 0x03;
    /** Ende der Übertragung */
    static constexpr const uint8_t EOT = 0x04;
    /** Anfrage / Anforderung */
    static constexpr const uint8_t ENQ = 0x05;
    /** Positive Rückmeldung */
    static constexpr const uint8_t ACK = 0x06;
    /** Negative Rückmeldung */
    static constexpr const uint8_t NAK = 0x15;

class WR3223Commands {
    /** Commando Bit für die Temperatursensoren */
    static constexpr const char Temperatur = 'T';
    /** Verdampfertemperatur istwert auslesen */
    static constexpr const char T_Verdampfer = '1';
    /** Kondensatortemperatur auslesen */
    static constexpr const char T_Kondensator = '2';
    /** Aussentemperatur auslesen */
    static constexpr const char T_Aussen = '3';
    /** Ablufttemperatur (Raumtemperatur) */
    static constexpr const char T_Raum = '4';
    /** Temperatur nach Wärmetauscher (Fortluft) lesen */
    static constexpr const char T_Fortluft = '5';
    /** Zulufttemperatur auslesen */
    static constexpr const char T_Zuluft = '6';        
    /** Temperatur nach Vorheizregister lesen */
    static constexpr const char T_NachVorheizregister = '8';

class WR3223Connector : public PollingComponent, public UARTDevice {

  WR3223Connector(int update_intervall, UARTComponent *parent) : PollingComponent(update_intervall), UARTDevice(parent) { }

  uint8_t CmdQuery[8] = { MessageControl::EOT, 0x30, 0x30, 0x31, 0x31, 0x00, 0x00, MessageControl::ENQ };
  Sensor *verdampfertemperatur_sensor = new Sensor();
  Sensor *kondensatortemperatur_sensor = new Sensor();
  Sensor *aussentemperatur_sensor = new Sensor();
  Sensor *ablufttemperatur_sensor = new Sensor();
  Sensor *fortlufttemperatur_sensor = new Sensor();
  Sensor *zulufttemperatur_sensor = new Sensor();
  Sensor *nachvorheizregistertemperatur_sensor = new Sensor();

  void setup() override {  }

  int readline(int readch, char *buffer, int len)
    static int pos = 0;
    int rpos;

    if (readch > 0) {
      switch (readch) {
        case '\n': // Ignore new-lines
        case MessageControl::ETX: // Ende der Nachricht
          buffer[pos++] = readch; 
          buffer[pos++] = read(); // Checksumme noch mit dranhängen
          rpos = pos;
          pos = 0;  // Reset position index ready for next time
          return rpos;
          if (pos < len-1) {
            buffer[pos++] = readch;
            buffer[pos] = 0;
    // No end of line has been found, so return -1.
    return -1;

  void publish_temp(char * data, Sensor *sensor) {
      ESP_LOGD("PUBLISHER", "Temperatur: %s", data);
      std::string fs(data);
      float f = std::stof(fs);
    catch(const std::exception& e)
      ESP_LOGE("PUBLISHER", "Fehler beim veröffentlichen der Temperatur: %s", e.what()); 

  const int data_start = 3;

  int get_data_length(char *data, int maxLength)
    int counter = 0;
    if(maxLength < data_start)
      return 0;

    //ESP_LOGD("get_data_length", "Data MaxLength: %i", maxLength); 

    for (int i = data_start; i < maxLength + data_start; i++)
      //ESP_LOGD("get_data_length", "Data Position: %i, Data Char: %c",i, data[i]); 
      if(uint8_t(data[i]) == MessageControl::ETX)
        return counter;

    ESP_LOGD("get_data_length", "Kein Abschluss-Flag gefunden, so können wir die Datenlänge nicht bestimmen. (counter: %i)", counter); 
    return 0;

  void publish_temp(char *data, int len)
    int dataLength = get_data_length(data, len - 3);
    if(dataLength < 1)

    char tempData[dataLength+1];
    std::copy(data + data_start, data + data_start + dataLength, tempData);
    tempData[dataLength+1] = 0;

        case WR3223Commands::T_Verdampfer:
            publish_temp(tempData, verdampfertemperatur_sensor);  
        case WR3223Commands::T_Kondensator:
            publish_temp(tempData, kondensatortemperatur_sensor);
        case WR3223Commands::T_Aussen:
            publish_temp(tempData, aussentemperatur_sensor);
        case WR3223Commands::T_Raum:
            publish_temp(tempData, ablufttemperatur_sensor);
        case WR3223Commands::T_Fortluft:
            publish_temp(tempData, fortlufttemperatur_sensor);
        case WR3223Commands::T_Zuluft:
            publish_temp(tempData, zulufttemperatur_sensor);
        case WR3223Commands::T_NachVorheizregister:
            publish_temp(tempData, nachvorheizregistertemperatur_sensor);
            ESP_LOGD("PUBLISHER", "Unbehandeltes Kommando: %c", data[2]); 

  void publish_data(char *data, int len)
    if(uint8_t(data[0]) != MessageControl::STX)
      ESP_LOGD("PUBLISHER", "Falscher Beginn der Nachricht: %x",uint8_t(data[0])); 

    if(data[1] == WR3223Commands::Temperatur)
      publish_temp(data, len);

  /// @brief Setzt die Anfrage für den Controller
  /// @param pRequest 
  /// @return 
  void SetCommand(const char pCommandCharOne, const char pCommandCharTwo)
    ESP_LOGD("Writer", "SetCommand: %c%c", pCommandCharOne, pCommandCharTwo); 
    CmdQuery[5] = uint8_t(pCommandCharOne);
    CmdQuery[6] = uint8_t(pCommandCharTwo);

  void loop() override {
    const int max_line_length = 80;
    static char buffer[max_line_length];
    while (available()) {      
      int len = readline(read(), buffer, max_line_length);      
      if(len > 0) 
        ESP_LOGD("Reader", "Buffer length: %i", len);
        publish_data(buffer, len);

    const int cmd_delay = 500;

  void update() override {


    SetCommand(WR3223Commands::Temperatur, WR3223Commands::T_Verdampfer);
    write_array(CmdQuery, 8);


    SetCommand(WR3223Commands::Temperatur, WR3223Commands::T_Kondensator);
    write_array(CmdQuery, 8);

    SetCommand(WR3223Commands::Temperatur, WR3223Commands::T_Aussen);
    write_array(CmdQuery, 8);

    SetCommand(WR3223Commands::Temperatur, WR3223Commands::T_Raum);
    write_array(CmdQuery, 8);

    SetCommand(WR3223Commands::Temperatur, WR3223Commands::T_Fortluft);
    write_array(CmdQuery, 8);

    SetCommand(WR3223Commands::Temperatur, WR3223Commands::T_Zuluft);
    write_array(CmdQuery, 8);

    SetCommand(WR3223Commands::Temperatur, WR3223Commands::T_NachVorheizregister);
    write_array(CmdQuery, 8);

And here the yaml file for the esp32:

  name: "heizung-controller"
    - wr3223_component.h

  board: az-delivery-devkit-v4  
    type: arduino    

# Enable logging
  baud_rate: 0

# Enable Home Assistant API
  password: !secret api_password
  password: !secret ota_password

  ssid: !secret wifi_ssid
  password: !secret wifi_password

    ssid: "Heizung-Control Fallback Hotspot"
    password: !secret wifi_fallback_ap_password



  - id: uart_1
    tx_pin: GPIO19
    rx_pin: GPIO18
    baud_rate: 9600
    data_bits: 7
    parity: EVEN
    stop_bits: 1

- platform: custom
  lambda: |-
    auto wr3223 = new WR3223Connector(5000, id(uart_1));
    return {wr3223->verdampfertemperatur_sensor, 

  - name: "Verdampfertemperatur"
    unit_of_measurement: °C
    accuracy_decimals: 1
  - name: "Kondensatortemperatur"
    unit_of_measurement: °C
    accuracy_decimals: 1
  - name: "Aussentemperatur"
    unit_of_measurement: °C
    accuracy_decimals: 1
  - name: "Ablufttemperatur (Raumtemperatur)"
    unit_of_measurement: °C
    accuracy_decimals: 1
  - name: "Temperatur nach Wärmetauscher (Fortluft)"
    unit_of_measurement: °C
    accuracy_decimals: 1
  - name: "Zulufttemperatur"
    unit_of_measurement: °C
    accuracy_decimals: 1
  - name: "Temperatur nach Vorheizregister"
    unit_of_measurement: °C
    accuracy_decimals: 1    

The Problem with the wr3223 is, you have to ask the system for every sensor. I dont know if this ist the best way to solve it, but it works pretty well:

've read that to set states in the wr3223 using this method, the normal control unit must be pulled out. That’s why I haven’t implemented any switches yet. But I think this is a good start.
I would be happy about any suggestions for improvement or hints, especially about the c++ code.
Best regards

Hi Marcus,

thanks for your effort.

A relais to disconnect the serial interface of the control unit could help and would be worth a test to set values via switches without the need for removing the panel :slight_smile:

Another idea is to replace the control panel with a Sonoff NSPanel.

If I have one wish free, I’d like you to implement some switches :wink:

My colleague will have a look on your code but from my side it’s looking pretty neat.

Hi booze, I’ve finished the project so far.

You can control most of what you could control with the front panel and even a few more things (such as the individual speeds of each air stage).

In my case, the electricity for the heat pump is turned off twice a day for one hour. So that the configuration is not lost, it can be saved directly on the ESP.

It is important that either the control panel or the ESP32 sends the configuration to the system at least every 20 seconds, otherwise it reverts to a default. (I think then air level 3 and heat pump and additional heating are always on).
Currently I no longer use the control panel and control everything via HomeAssistant.
Thanks for the tip, I already have several Sonoff NSPanel Pro in use. :slight_smile:

Here are som pics:

Unfortunately I don’t have a geothermal heat exchanger (EWT) so I left out all this information. I also didn’t include the configuration regarding the defrosting phases and their status. Should this be required, it can be easily extended.

Since there are quite a few c++ classes now, I’ll see if I can get them uploaded to GitHub somewhere. I’ll get back to you here with the link.


Wow, I’m very happy, to see, that this now also works at home assistant.

I’m actually at the beginning, so will I be able to connect the wr3223 via USB to my raspi?

Hi mcweis,

I used ESPHome for that.

I connected an ESP32 directly to the RS232 port of the WR3223 (see picture in second post).

The ESP32 then communicates with HomeAssistant via WiFi. So you don’t have to connect anything to your Raspberry. You just need some 5-8$ ESP :slight_smile:

By the way, I used an ESP32 instead of an ESP8266, because as far as I’ve read, it can do ‘real’ hardware UART. But an 8266 should also work.

Hi schmurgel,

I’m happy you finished the project and I’m looking forward to use your code. My ESP32 is waiting :wink:
Unfortunately the NSPanel needs 100-240V AC and therefore needs some cabling before replacing the original control panel (5V DC).

@mcweis, see schmurgels post above for the connection schema :slight_smile:

can’t wait <3

Hey booze,

I uploaded the code, you can find it here:
GitHub - schmurgel-tg/esphome

Feel free to contact me and let me know if everything works. Have fun :upside_down_face:


Hi schmurgel,

great! just flashed it and will give it a try asap!

Just one question… the reminder to change filters after 90 days: does this come from the WR3223 or was this related to the original control panel in the living room?

Boom! Seems to work! Great. I will make some tests… Unplugging X1 for instance and see if I can control the ventilation level and so on. Thanks again very very much.

Hey Booze,
I’m glad it worked right away.

The reminder function for the filters exists only in the original panel. At least I didn’t find them directly in the wr3223.
But I think you can easily implement it directly in Homeassistant. Especially with the calendar integration :slight_smile:

The calendar functions of the original panel needs also to be rebuild in HomeAssistant if you need them. My purpose of doing all this was exactly this funktion of the original panel. It was really hard to configure and very inflexible.
Sadly the room temperature sensor is part of the original panel, so the room temperature is not available. Also I could not set the target temperature in the WR3223. Possibly because it lacks the room temperature. (I think this combination would enable automatic de/activation of the heatpump.

I haven’t been able to test the cooling function yet because it’s currently too cold outside. The WR3223 accepts the activation command, but the relay does not switch. The original control panel gave the information that the cooling can only be activated above a certain outside temperature.

I have also seen you suggested correction, thx for that :sweat_smile:

If you have any suggestions, don’t hesitate :wink:

1 Like

Hi schmurgel,
thx for your great repository, I have integrated everything in HA, but everytime if I start installation, ends it with this:

I don´t know whats wrong. modbus and Ip are activated. Fritzbox could find it. Can you pls help me.

Hello Burnchip,

looks like it compiled successfully. But it can’t connect to write the firmware to the ESP.

It seems that the OTA settings are not working. Have you configured the OTA setting in the heating-controller.yaml? The file in the repo contains my secret settings. You must change this.

Look here for more Details: OTA Update Component — ESPHome

Alternatively, you can try directly via USB.

And don’t forget to adjust the API settings so that HomeAssistant can also connect afterwards.

Hello Schmurgel,
first to know: i use the direct connected USB and Lan-Port at Schwoerer WRG 334 KL not az-Delivery devices, because i dont know where i have to bind it.

Hi everybody,
I’ve now installed everything on an ESP32 dev kit - but I don’t know where I have to plug it to the board of my wrg 334.
Can somebody help me with this?

Sorry for the late reply.
My solution is only fo the WR3223 with a serial interface (RS232).
You already have an ethernet Port on the board. I dont know what exact type of system do you have, but i do not think that my solution is compatible.
Maybe you have a ventcube or similar?
Take a look at this Video: [Smart Home] Schwörer Lüftung mit Smart Home verbinden - TEIL 1 (ioBroker und Alexa) - YouTube

Good luck

Hi, I’ve bougt now an AZ-Delivery ESP32 Dev KitC V2 and flashed your files to the ESP32, flashing was success, but the ESP32 seems to be not reachable after flashing. In my router i can see it online and I can ping the ESP.

Can somebody help me with this failure?
WARNING Can’t connect to ESPHome API for heizung.local: Error connecting to (‘’, 6053): [Errno 113] Connect call failed (‘’, 6053)

So does this mean that we can’t remove the original panel? Because without a target temperature being set, the heat pump is warming up the rooms on the second floor too much (e.g. 25 °C) while the ground floor has only 22 °C.


Hello booze,

I don’t know your exact structure, but the room temperature sensor is in the original control element. Without it, the WR3223 has no info about the room temperature (the parameter is T4 (Ablufttemperatur (Raumtemperatur))
I don’t know if it’s possible to connect another sensor to the board, but I think you can’t set a target temperature since the T4 is missing.

I use a normal temperature sensor with an automation to activate or deactivate the heat pump with a certain threshold and a minimum time after deactivation/activation.

The problem is that the WR3223 can only turn HeatPump on or off. And this is the same for every room, cause its central. We have the same problem with the temperature difference between the second floor and the ground floor. Especially in winter when the fireplace is on. The temperature in the living room is very high and the heat pump turns off, but the second floor is still cold and I want to keep the heat pump running, so I usually set the target temperature to 30°C on the control panel. With Home Assistant I now have more options for that :upside_down_face: