HASS, Integrating Haus-bus.de devices / ESPHome, reading HEX from MAX485/UART

Cool, thanks for that hint, I did know the Github Repo but not that particular file. In general, the command is not the problem, I’m able to control the switches via PC already (used the template generator you mentioned), the headache comes with the ESPhome implementation :wink:

Yep, I got that, thanks for that hint too. But before sending commands from ESP to the switches, I’d like to read the data.

I found these pages as well, what I don’t understand: How do I read data from UART? In the UART Text Sensor, they use read() (must be Serial.read() according to arduino docs) to get data and publish_state(buffer) to push the buffer (where the text is stored) to terminal output. In the Custom UART Device documentation it is more generic. I’m not the best in programming and C# looks a bit weird to me, especially when it comes to data types. With Serial.read() or Serial.ReadBytes() I should be able to get the input, but how do I handle that input? Simply changing the line endings from \n and \r to 0xFD or 0xFE in the text sensor’s header file will not work… What I need is a kick in the butt to stumble on the solution :innocent:

Okay, now I first tried to collect the messages from the switch with a little program in Arduino IDE. Currently it’s working with the following quick-n-dirty code (adapted from that guy here):

#define DE       D6    // RS485 Direction control
#define RE      D5    // RS485 Direction control 2
#define RS485Transmit    HIGH
#define RS485Receive     LOW
 
void setup() {
  pinMode(DE, OUTPUT);
  pinMode(RE, OUTPUT);

  Serial.begin(57600, SERIAL_8O1);   
  delay(100);
}

void loop() {
  char RS485Eingabe[36];
  RS485Eingabe[1] = 0;
  while( RS485Eingabe[1] != 0x03 ) {    // if received message has an error
    // MODBUS Reception of the anemometer's answer
    digitalWrite(DE, RS485Receive);      // init Receive
    digitalWrite(RE, RS485Receive);      // init Receive
    Serial.readBytes(RS485Eingabe, 99);
    Serial.print("Antwort: ");
      for( byte i=0; i<36; i++ ) {
        if( RS485Eingabe[i] < 253 ) {
          Serial.print(RS485Eingabe[i]);
          }
        else if( RS485Eingabe[i] == 254 ) {
          break;
        }
      }
    Serial.println(); 
    delay(500);
  }   // end of while 
}     // end of loop

Next step: I have to convert this in a header file to use that code in ESPHome. But not tonight - my brain is burnt out :sweat_smile:

1 Like

I hope it’s okay, that I use this thread to write down my findings… maybe it will help someone in the future with a similar problem.
After a short night, I started again to get that stuff running. As I’m still a bit lost concerning the lambda functions in ESPHome, I started redefining my code in the Arduino IDE and will hopefully be able to adapt that to ESPHome later.
This is my current code with all unnecessary stuff removed (for ESP8266):

#define DE      D6    // RS485 Direction control (Low when recieve)
#define RE      D5    // RS485 Direction control 2 (Low when recieve)
#define RS485Transmit    HIGH
#define RS485Receive     LOW

void setup() {
  pinMode(DE, OUTPUT);
  pinMode(RE, OUTPUT);

  Serial.begin(57600, SERIAL_8O1);   
  delay(100);
}

void loop() {
  char RS485Eingabe[36] = {};                     // decline array of char with a dedicated length (length tbd)
  digitalWrite(DE, RS485Receive);                 // init Receive on MAX485
  digitalWrite(RE, RS485Receive);                 // init Receive on MAX485
  Serial.readBytesUntil(0xFE, RS485Eingabe, 99);  // read from UART until you find character 0xFE, if you don't find 0xFE, stop after 99 bytes
    if( RS485Eingabe[0] == 0xFD ) {               // only do something when the recieved stuff begins with 0xFD
      Serial.print("Antwort: ");                    // tbd
      for( byte i=1; i<36; i++ ) {                // start with second character (first is always 0xFD), iterate over all characters in array
          Serial.print(RS485Eingabe[i]);          // gimme that shit on screen
      }  // end of for
      Serial.println();                             // daddy needs a new line
    }  // end of if
}  // end of loop

My findings so far: Serial.ReadBytesUntil(character, buffer, length) is a very useful function, as you can detect a custom end character (which is 0xFE in my case) to read from UART until that character. A benefit compared to Serial.ReadByte() is that I don’t miss any RS485 messages, e.g. when pressing two buttons of the switch at the same time or when a sensor is sending its current value and I press a button at the same time.
So currently I’m reading from UART with Serial.readBytesUntil(0xFD, RS485Eingabe, 99); where the length parameter is currently just a stupid way of error handling and has to be adjusted accordingly in the future.
In that stage, the code works very well and I can detect every message from the switch an print that to the terminal in plain text.
Next step: Port it to ESPHome :sweat_smile:.
Feel free to comment every single bit of my posts as I’ve got no programming skills at all :wink:

Okay, I went further and was able to migrate my code to an ESP32 and to convert it into a header file to use in ESPHome. I created the header-file haus-bus-test.h:

#include "esphome.h"
#include <string>
//#include "sensor.h"

using namespace std;

class HausbusRead : public PollingComponent, public UARTDevice {
 public:
  HausbusRead(UARTComponent *parent) : PollingComponent(1000), UARTDevice(parent) {}
  Sensor *LichtschalterTest = new Sensor();

  void setup() override {
    // nothing to do here
  }

  void update() override {
    char RS485Eingabe[36] = {};
    readBytesUntil(0xFE, RS485Eingabe, 99);
    if( RS485Eingabe[0] == 0xFD ) { 
        LichtschalterTest->publish_state(RS485Eingabe[2]);   // for testing simply publish 2nd char
        }
    }
};

The ESPHome YAML:

esphome:
  includes:
    - haus-bus-test.h
  name: hausbus
  platform: ESP32
  board: esp-wrover-kit

wifi:
  ssid: "IDIoT"
  password: "verySecure"
  manual_ip:
    static_ip: ip
    gateway: gw
    subnet: subnet
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Hausbus Fallback Hotspot"
    password: "veryUnsecure"

captive_portal:

# Enable logging
logger:
  level: VERBOSE
  baud_rate: 0

# Enable Home Assistant API
api:
  password: "api-pw"
ota:
  password: "ota-pw"

uart:
  tx_pin: TX
  rx_pin: RX
  baud_rate: 57600
  parity: ODD
  id: HausbusUART

output:
  - platform: gpio
    pin: 18
    id: gpio_18
  - platform: gpio
    pin: 19
    id: gpio_19

interval:
  - interval: 3sec
    then:
      - output.turn_on: gpio_18
      - output.turn_on: gpio_19
      - uart.write: "ESP is still there"
      - output.turn_off: gpio_18
      - output.turn_off: gpio_19

sensor:
  - platform: custom
    lambda: |-
      auto my_hausbus = new HausbusRead(id(HausbusUART));
      App.register_component(my_hausbus);
      return {my_hausbus->LichtschalterTest};
    sensors:
    - name: "Hausbus Multitaster ESP32"

After adding the ESPHome Integration manually via integrations page, the device is created and the entity is discovered after a button on the switch is pressed.
On that current stage, I have only two issue so far: There is one error and a warning repeatedly in the ESP Log:

[22:09:00][E][uart_esp32:147]: Reading from UART timed out at byte 0!
[22:09:00][V][app:081]: A component took a long time in a loop() cycle (1.00 s).
[22:09:00][V][app:082]: Components should block for at most 20-30ms in loop().
[22:09:01][E][uart_esp32:147]: Reading from UART timed out at byte 0!
[22:09:01][V][app:081]: A component took a long time in a loop() cycle (1.01 s).
[22:09:01][V][app:082]: Components should block for at most 20-30ms in loop().
[22:09:02][E][uart_esp32:147]: Reading from UART timed out at byte 0!
[22:09:02][V][app:081]: A component took a long time in a loop() cycle (1.00 s).
[22:09:02][V][app:082]: Components should block for at most 20-30ms in loop().
...

After reading this thread, I thought it is related to the old ESP8266 I’m using here and switch to an ESP32 board, but the issue seems to be hardware agnostic.
It’s late again, I will look into that tomorrow…

This is a blocking function that should not be used in this environment.

You must check if bytes are available, read all available bytes and process them. If the data you have received is not complete, remember the actual state and continue on the next loop cycle.

Maybe this will help: esphome/esphome/components/teleinfo/teleinfo.cpp at 5e239d3d882aa2b6d2d8556b7c08ea54f238c91a · esphome/esphome · GitHub

I had a similar usecase with my standing desk, you can find my header file here:
LoctekMotion_IoT/desk_height_sensor.h at main · iMicknl/LoctekMotion_IoT (github.com)

Please note that this code could be cleaner, but I am walking through it hex by hex :smiley:

I just came here to help you. (Didn’t have time yesterday to answer and got a email notification that you answered to my post).

But well: I am really surprised how far you have come. Nice work. But I do not believe that you don’t have progrmaming skills.

Regarding your loop issue: @EnRav is correct. The loop() method is not “allowed” to block and has to return very quick. There is no real multitasking like you know it on your PC. ESPHome will call “loop” of every component one after another, then wait a few ms (to let the ESP do things like WLAN) and call loop again for all components. If your component blocks in loop everything else is blocked for that time.

(The same is true for the loop() method in Arduino. But it is even worse in ESPHome because it has lots of components interacting).

Thank you very much and I really appreciate your help, as you’re heading me to the right direction! I had the same problem the last couple of days (no time for playing with ESP).

Thanks again for that and I’m surprised too, but there is no alternative: The switches are mounted and the cabeling is done, so this has to work and that motivates me to go ahead.

You’re honoring my google and maybe some little reverse engineering skills, I didn’t type any line of code by myself so far :sweat_smile:

Now, I got that point. I will try to design a better solution with the help from @EnRav and @imick, thank you guys! :partying_face:

Maybe you can help me out with your ESP skills in another topic: In the first concept I’d liked to outsource some “intelligence” to the ESP such as handling the identification of the component (this is the kitchen's switch talking) and updating the according home assistant entity (switch kitchen: temp 21°C, button 1 pressed and released) as this is really static - the ID of the kitchen switch will not change until it is broken and will be replaced, either will the kitchen switch not suddenly change to a bedroom switch without an apocalyptic impact to my house’s physics. With your input, do you think this could maybe be to much for one little ESP32? I have a total of 26 devices connected to that rs485 bus and it could become a bit crowded on the bus I think. As far as I understand, there is some overhead related to ESPHome stuff and the available code execution time is limited to several milliseconds, right?
Currently I’m thinking about a simple RS485-to-MQTT or RS485-to-Hass-API gateway where all the logic is done on the powerful home assistant hardware.

Okay, it’s time to feed the kids, the dog, my wife, the birds, the dolphins… Just for documentation my current progress on that - as always: Feel free to comment and tell me why my code looks weird or strange :slight_smile:

I adopted the LoctekMotion-Code from @imick (once again: thank you very much :partying_face:!) and googled a bit more than the last time, resulting in a working prototype.
The header file for ESPHome is now:

#include "esphome.h"
#include <string>
//#include "sensor.h"

using namespace std;

class HausbusRead : public PollingComponent, public UARTDevice {
 public:
  HausbusRead(UARTComponent *parent) : PollingComponent(1000), UARTDevice(parent) {}
  TextSensor *LichtschalterTest = new TextSensor();

  char RS485Eingabe[36] = {};

  int msg_len = 0;
  
  std::string Ausgabe = "";

  void setup() override {
    // nothing to do here
  }

  void update() override
  {
    while (available() > 0)
    {
        byte incomingByte = read();             		// get last byte from UART
        
        switch (incomingByte)                   		// is it the last or the first Byte?
        {
            case 0xFD:                        			// every message starts with 0xFD, this means here starts a new message
                msg_len = 0;
		RS485Eingabe[msg_len] = incomingByte;		// add the last byte to the array
		break;
            case 0xFE:                           		// every message ends with 0xFE, this means the message ends here
                for (int i = 1; i <=msg_len; i++)		// create a string from the array for the HAss Sensor
    	        {
    		        Ausgabe = Ausgabe + RS485Eingabe[i];
    	        }
    	        LichtschalterTest->publish_state(Ausgabe);	// push the string to the sensor
    	        Ausgabe = "";					// Reset the string
                break;
            default:
                msg_len = msg_len + 1;				// in case it is not the beginning oder the end of the message
		RS485Eingabe[msg_len] = incomingByte;		// add the last byte to the array
                if (msg_len == 36)
                {
                	msg_len = 0; 				// stupidest way of error handling so far
                }
                break;
        }
	}
  }
};

I finally found out what a TextSensor is :nerd_face:, so the the sensors section in my hausbus.yml changed slightly to:

text_sensor:
  - platform: custom
    lambda: |-
      auto my_hausbus = new HausbusRead(id(HausbusUART));
      App.register_component(my_hausbus);
      return {my_hausbus->LichtschalterTest};
    text_sensors:
    - name: "Hausbus Multitaster ESP32"

With this running, I reached a milestone: The messages from the hausbus-devices are finalyy available in HAss, as you can see in the ESPHome Logs:

[17:37:17][D][text_sensor:015]: 'Hausbus Multitaster ESP32': Sending state '1395.BRS.23.STATUS.5'

Now I have to decide if I’d like to process this text sensor in HAss or if I’d like to update different entities from a more advanced lambda function/header file.

1 Like

In general that should be no problem for the ESP32 if those devices devices are not too busy. The ESP32 itself is quite fast for what it is. But I have no experince with RS485.

Congrats for getting it to work. I think it’s OK. While software development is my job with all the requirements like tests, code reviews, documentation and so on I quite enjoy working on such sma.l ESP based project were the main metric of qualitiy is “if its works and I understand it it is good enough”.

But there is a small bug in it (maybe more): In the default case you are adding on the msg_len (you can write that as msg_len++;) and then write into that position in the array. The problem: You will write into it even if it is 36 but your array is only from 0 to 35. So you either need to move the if ==36 before the write into the array or change the check to 35.

Do you know that you cann use ESP_LOGD to output debug messages?

Like so:

ESP_LOGE("Hausbus", "Message longer than 36 characters"); // E for error
ESP_LOGD("Hausbus", "Got new message: %s", Ausgabe.c_str()); // D for Debug

I would do such a type of processing on the ESPHome node and send a more real world representation to HA. Like a Switch or temperature reading and so on.

Btw: (Now that you got it running ESPHome :wink: ) You can have a quick lock into MySensors. It is similar to ESPHome (without the yaml) but more of focus on RS485 and it has a HA integration. But: I have never used it for RS485 .

A short update with some very little progress so far (mainly for my documentation :sweat_smile:).

Thank you once again @danielw, that’s a good hint and I think a typical beginner’s bug :wink:

Thanks for the hint, I will definitely use this in future code!

I didn’t decide yet where to manage the processing of the different devices (aka: Is the ESP only a RS485-to-HA-API-Gateway or will it do more). Pros and Cons are currently: I’m a bit more adept at the HAss yaml structure then with C++ Code but doing it on the ESP seems to be a more elegant solution. I’d like to decide this later when I went a bit further with sending commands to the ESP.
In the last night I migrated the code from a generic ESP32 board to a WT32-ETH01 which comes with an integrated Ethernet Port but without an USB-Port. So, I manged to flash stuff via UART Pins and an external USB-to-Serial adaptor and got it now running with some tweaks. Due to the Ethernet-Chip, there are only a few GPIOs left and the manufacturer’s documentation is a bit unclear about the purposes of the Pins, so it was a kind of trial and error but finally I got it working.
For your reference (and mine of course) here is the current Pinout overview which works for me (how can I paste ASCII art??)


I decided not to use one of the two UART interfaces and use PIN IO33/32 for the serial connection. In the docs they are labeled with 485_EN and CFG but I found no information what that means, maybe the ESP32 is capable of RS485 by default? As I use a MAX485 board, I just needed some Pins for my serial communication and didn’t care about the labels. The issue with both of the UART interfaces was, that this board always shouts some terminal output to the UART, even if you configure the logger with baud_rate: 0. There are some other curiosity with this board, e.g. only the lower EN Pin (Pin 4 on the left) works for me while flashing via Serial, the upper (Pin 1 left) didn’t work. And flashing is only possible via RX0/TX0 Pins.

With this setup, my current yaml is:

esphome:
  includes:
    - hausbus-efficient.h
  name: hausbuswrite
  platform: ESP32
  board: esp-wrover-kit

wifi:
  ssid: ""
  password: ""
  manual_ip:
    static_ip: 
    gateway: 
    subnet: 
    
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Fallback Hotspot"
    password: ""

captive_portal:

# Enable logging
logger:
  level: VERBOSE
  baud_rate: 0

# Enable Home Assistant API
api:
  password: ""

ota:
  password: ""

uart:
  tx_pin: GPIO32
  rx_pin: GPIO33
  baud_rate: 57600
  parity: ODD
  id: HausbusUART

output:
  - platform: gpio
    pin: GPIO12
    id: DE
  - platform: gpio
    pin: GPIO14
    id: RE
    
interval:
  - interval: 3sec
    then:
      - output.turn_on: DE
      - output.turn_on: RE
      - uart.write: "ESP ist noch da"

text_sensor:
  - platform: custom
    lambda: |-
      auto my_hausbus = new HausbusRead(id(HausbusUART));
      App.register_component(my_hausbus);
      return {my_hausbus->LichtschalterTest};
    text_sensors:
    - name: "Hausbus Multitaster ESP32"

And I drive the DE and RE Pins now in the C++ Code, so the header file changed to:

#include "esphome.h"
#include <string>
//#include "sensor.h"

using namespace std;

class HausbusRead : public PollingComponent, public UARTDevice {
 public:
  HausbusRead(UARTComponent *parent) : PollingComponent(1000), UARTDevice(parent) {}
  TextSensor *LichtschalterTest = new TextSensor();

  char RS485Eingabe[36] = {};

  int msg_len = 0;
  
  std::string Ausgabe = "";

  void setup() override {
    pinMode(12, OUTPUT);
    pinMode(14, OUTPUT);// nothing to do here
  }

  void update() override
  {
    while (available() > 0)
    {
        digitalWrite(12, LOW);
        digitalWrite(14, LOW);
        byte incomingByte = read();             		// get last byte from UART
        
        switch (incomingByte)                   		// is it the last or the first Byte?
        {
            case 0xFD:                        			// every message starts with 0xFD, this means here starts a new message
                msg_len = 0;
		RS485Eingabe[msg_len] = incomingByte;		// add the last byte to the array
		break;
            case 0xFE:                           		// every message ends with 0xFE, this means the message ends here
                for (int i = 1; i <=msg_len; i++)		// create a string from the array for the HAss Sensor
    	        {
    		        Ausgabe = Ausgabe + RS485Eingabe[i];
    	        }
    	        LichtschalterTest->publish_state(Ausgabe);	// push the string to the sensor
    	        Ausgabe = "";					// Reset the string
                break;
            default:
                msg_len = msg_len + 1;				// in case it is not the beginning oder the end of the message
		RS485Eingabe[msg_len] = incomingByte;		// add the last byte to the array
                if (msg_len == 35)
                {
                	msg_len = 0; 				// stupidest way of error handling so far
                }
                break;
        }
	}
  }
};

Currently I’m facing an issue where a power cycle of the board prevents the board from working. When I unplug the MAX485 board from the power Pins (3V3 and GND), I can start the board and reattaching the MAX board will work than flawlessly. I think it’s a matter of power supply which will be solved when I can install the board in the final place. Additionally, I ordered some MAX3485 boards, they will need 3,3V input whereas the MAX485 officially needs 5V - and that doesn’t match the TTL levels of the ESP. That’s why I’m using the MAX485 with 3V3 so far.
My next step will be to get the Ethernet interface running for a reliable connection (I use it for an connection to light switches, they have to work on every keypress without waiting for Wifi) and managing/flashing the board over LAN.

Okay, that was easy! Got the board successfully running with Ethernet and changed the MAX485 board to a MAX3485 board. I was a bit curious that the MAX3485 board has no pinout for DE/RE, but with some better glasses I found out, that they’re hardwired so that Send and Recieve channels are always open in parallel. This probably helps to detect collisions on the bus - I hope I’ll remember that later!
Edit: The reboot issue is no longer present when using the MAX3485 board. :white_check_mark:

I changed the title to a more matching one as I basically solved the initial task with the HEX readings from UART in ESPHome. Now, it’s more about the handling of the messages from these haus-bus devices and that is no longer a question of HEX readings.

I’m lucky with MAX3485 and the WT32-ETH01, both is currently working stable for production use.

Now I’ve to decide how to handle all the messages on the bus. I got a bit used to C++ code and will try it that way. First of all, I will focus on the haus-bus Multitaster, later I’ll implement the I/O Modul.
My current idea is creating an ESPHome sensor for each sensor of each Multitaster in the header-file. This will lead to an huge amount of sensors for one ESP32 board (3 sensors per board * 22 devices = 66 sensors in sum). Or is there any easier solution?
I’d prefer to define the sensors in HA as template-sensors and simply update them via ESPHome, but can I update a sensor in HA with a lambda function from ESPHome without defining this sensor in the header-file?
If yes, how would I define the value_template in a template-sensor that it uses the return from the ESPHome sensor?

After a long day with C++ Code, I ended up with a non-working solution. I tweaked the main code here and there, substitute the german variables with the corresponding english ones and separated the messages into their substrings.
I tried to get through the sensor data with an if statement but struggled. Here is my current progress and some description if anyone is able to point out what I missed here (@danielw? :innocent:).

#include "esphome.h"
#include <string>
//#include "sensor.h"

using namespace std;

class HausbusRead : public PollingComponent, public UARTDevice {
 public:
  HausbusRead(UARTComponent *parent) : PollingComponent(1000), UARTDevice(parent) {}
  TextSensor *LastHausBusMessage = new TextSensor(), *LastHausBusID = new TextSensor(), *LastHausBusSensorType = new TextSensor(), *LastHausBusSensorID = new TextSensor(), *LastHausBusSensorState = new TextSensor(), *LastHausBusSensorValue = new TextSensor();

  Sensor *Hausbus_taster_1395_brightness = new Sensor();
  Sensor *Hausbus_taster_1395_temperature = new Sensor();
  Sensor *Hausbus_taster_1395_humidity = new Sensor();

  char RS485Message[36] = {};
  std::string Message = "", device_id = "", sensor_type = "", sensor_id = "", sensor_state = "", sensor_value = "", outputsensor = "";

  int msg_len = 0;
  int point_counter = 0;


  void setup() override
  {
    //pinMode(12, Message);	// no longer necessary with RS3485 (only needed for DE/RE)
    //pinMode(14, Message);	// no longer necessary with RS3485 (only needed for DE/RE)
  }//end of setup

  void update() override
  {
    while (available() > 0)
    {
        //digitalWrite(12, LOW);	// no longer necessary with RS3485 (only needed for DE/RE)
        //digitalWrite(14, LOW);	// no longer necessary with RS3485 (only needed for DE/RE)
        byte incomingByte = read();             		// get last byte from UART
        
        switch (incomingByte)                   		// is it the last or the first Byte?
        {
            case 0xFD:                        			// every message starts with 0xFD, this means here starts a new message
                msg_len = 0;
                point_counter = 0;
		RS485Message[msg_len] = incomingByte;		// add the incoming byte to the array
		break;
            case 0xFE:                           		// every message ends with 0xFE, this means the message ends here
                for (int i = 1; i <=msg_len; i++)		// create a string from the array for the HAss Sensor
    	        {
    		        Message = Message + RS485Message[i];
    	        }//end of for
    	        
		outputsensor = "Hausbus_taster_" + device_id + "_";
		if (sensor_type == "BRS") { outputsensor = outputsensor + "brightness"; }
		else if (sensor_type == "TMP") { outputsensor = outputsensor + "temperature"; }
		else if (sensor_type == "RHD") { outputsensor = outputsensor + "humidity"; }
		
		outputsensor->publish_state(sensor_value);
		
		LastHausBusMessage->publish_state(Message);	// push the strings to the sensors (currently for debugging purposes only)
		LastHausBusID->publish_state(device_id);
		LastHausBusSensorType->publish_state(sensor_type);
		LastHausBusSensorID->publish_state(sensor_id);
		LastHausBusSensorState->publish_state(sensor_state);
		LastHausBusSensorValue->publish_state(sensor_value);
		
    	        Message = "";					// Reset the strings
    	        device_id = "";
    	        sensor_type = "";
    	        sensor_id = "";
    	        sensor_state = "";
    	        sensor_value = "";
		outputsensor = "";
                break;
            case '.':						// Count the . chars to separate the substrings of the message
            	point_counter++;				// no break, we want to go to default after increasing the . counter
            default:						// in case it is not the beginning oder the end of the message
                msg_len = msg_len + 1;
		RS485Message[msg_len] = incomingByte;		// add the last byte to the array
                if (msg_len == 35)
                {
                	msg_len = 0; 				// stupidest way of error handling so far
                	Message = "";
                }//end of if
                if(RS485Message[msg_len] != '.')		// jump over the . chars, they are useless
                {
		        switch (point_counter)
		        {
		        	case 0:				// 1st substring is the DeviceID
		        	    device_id = device_id + RS485Message[msg_len];
		        	    break;
		        	case 1:				// 2nd substring is the sensor type
		        	    sensor_type = sensor_type + RS485Message[msg_len];
		        	    break;
		        	case 2:				// 3rd substring is the sensor id
		        	    sensor_id = sensor_id + RS485Message[msg_len];
		        	    break;
		        	case 3:				// 4th substring is the sensor state
		        	     sensor_state = sensor_state + RS485Message[msg_len];
		        	     break;
		        	case 4:				// 5th substring is the sensor value
		        	     sensor_value = sensor_value + RS485Message[msg_len];
		        	     break;
		        }//end of switch
                }//end of if
                break;
        }//end of switch
    }//end of while
  }//end of update
};//end of class

As stated before, I think I have to define all sensors of all Multitaster as TextSensors or Sensors in the C++ Code (currently only one device with three sensors is defined in the code above). I realized that every switch is a sensor as well, so every Multitaster has 9 sensor definitions, with 22 devices, I end up in nearly 220 variables, most work is copy-paste and with some excel magic, it is not too hard. In general the scaling is very bad, though it’s static and therefore acceptable.

But I’m a bit lost in the part where I’d like to publish the current sensor’s value

outputsensor = "Hausbus_taster_" + device_id + "_";
		if (sensor_type == "BRS") { outputsensor = outputsensor + "brightness"; }
		else if (sensor_type == "TMP") { outputsensor = outputsensor + "temperature"; }
		else if (sensor_type == "RHD") { outputsensor = outputsensor + "humidity"; }
		
		outputsensor->publish_state(sensor_value);

In my opinion it’s a very elegant solution, to work with variables here and to publish only what I read from UART before - for example the temperature value 19,5 of the sensor Hausbus_taster_1395_temperature.
To accomplish that, I’d like to ‘create’ the correct sensors name (aka the correct variable name to publish) at runtime and - after that - publish exactly that one. I had no luck googling around how to convert a string value to be interpreted as the name of a variable - maybe I was looking for the wrong search terms - how is this called in a programmer’s language?

Additionally to the handling of the code above, I’m not sure how to handle the output of the code in the yaml. Currently I’m using something like

sensor:
  - platform: custom
    lambda: |-
      auto my_hausbus = new Hausbusread(id(HausbusUART));
      App.register_component(my_hausbus);
      return {my_hausbus->Hausbus_taster_1395_brightness, my_hausbus->Hausbus_taster_1395_temperature, my_hausbus->Hausbus_taster_1395_humidity};
    sensors:
    - name: "Hausbus_taster_1395_brightness"
    - name: "Hausbus_taster_1395_temperature"
    - name: "Hausbus_taster_1395_humidity"

to get the values to the corresponding sensors in HA. But this scales even worse than in the variable definition. Should I really return 220 values? Usally, there is only one of the 220 values at a time, e.g. Hausbus_taster_1395_temperature = 19,5, will the other 219 values simply be ignored in that case?

Once again: Please excuse my sometimes very stupid questions and maybe I should invest more time in googling, but it’s hard when you do not know the correct term. I hope my ignorance will help out others with the same questions one day.

I don’t think you can do that. Maybe you can do something with MQTT on the HA side. In the end I think the 66 sensors are the simplest thing. You can do it with relative clean code if you generate the sensors in your code.

I don’t really understand how you are getting to 220 sensors? You have 22 multitaster devices and they all have 9 sensors?

Are we talking ESP8266? You should consider switching to an ESP32 it has at lot more memory.

That is something you can not do in C++ (or other static languages). The variables do not really exists anymore after compilation. I would call it some form of meta programing. You would change the program at runtime.

But: There is a solution for your issue. You should take a look at std::unordered_map. That allows you to store an object inside a map together with a key (for example a string) and to get the object back using the key.

Just a few hints to get you started.

// you have to add an include at the top of the your file
#include <unordered_map>

// as a variable inside your class
std::unordered_map<std::string, Sensor* > sensor_map;

// In the constructor of your class  (inside of the {}  in the first line after public:)
//  you can generate all the sensors and put them inside the map. This is only an 
// example. You can do it in a loop or but it inside another method. (I would write a method that always adds all sensors for one device_id to the map). 

// example for one sensor (you can do this smart as only the number has to change for every brightness sensor)
sensor_map["1395_BRS"] = new Sensor("Hausbus_taster_1395_brightness");

// in the update() method (which you should split up into multiple methods

Sensor* outputsensor = sensor_map[device_id+"_"+sensor_type];
outputsensor->publish_state(sensor_value);

for the yaml. I am not one 100% sure if it works to just name the sensors in your C++ to remove the need to add them to “sensors”. I hope, you must try for yourself.
The general idea is to just have this:

sensor:
  - platform: custom
    lambda: |-
      auto my_hausbus = new Hausbusread(id(HausbusUART));
      App.register_component(my_hausbus);
     std::vector<Sensor*> sensors;
     for (auto const& sensor_entry : my_hausbus->sensor_map)
     { 
             sensors.push_back(sensor_entry.second);
     }
      return sensors;

That should add all the sensors in your map to ESPHome.

I didn’t not test that all. They might be error or things do not 100% work.

Good luck.

First of all: @danielw Thank you so much! I really appreciate your help, you pushed my further than I ever expected! I never thought, that I solve this exercise in such a short amount of time and still understand most of that what I’m doing here. I definitely owe you a beer! :beers:

Never heard about that before… sounds like alchemy :crazy_face:

I will definitely do that as it sounds very promising. Yesterday after the coding session I was thinking about my issue and ended up to try it with an array, but the unordered map looks like a much better solution. Another :beers: for you :wink:
I’ll post an update on that as far as it is a kind of working.

The next cool idea :beers:, the implementation of lambda code is still a bit nibulous to me and that part is currently a simple copy-paste work.

I replaced the ESP8266 with an ESP32 at a very early stage of development. The main reason was that you can use Ethernet very easy with the ESP32 (I don’t even know if it works with the 8266 at all) and the WT32-ETH01 board - the cheapest ESP with integrated Ethernet and ESPHome support I could find so far - is based on an ESP32. Besides that, I like the increased performance of the board and am very lucky with my choice - but still a good hint as the code is growing and becoming more demanding.

I apologize that I was a bit unclear about that. Every Multitaster has 6 buttons and one brightness, one temperature and one humidity sensor. This sums up to 9 entities (6 switches, 3 sensors) per Multitaster. Additionally, I have 3 I/O Modules where every module has 16 in- and 16 output pins. Only the input will be sensors, outputs are only writable I think, so 316 + 922 = 246 entities/variable definitions.
I’ll start with only one Multitaster in my lab and then expand that to the full set when it’s working. I’ll keep you posted :beers:.

Ok, so much beer, this could end badly :wink:

One thing you should now:

Sensor* outputsensor = sensor_map[device_id+"_"+sensor_type];
outputsensor->publish_state(sensor_value);

That will crash if no sensor for that device_id and sensor_type is in the map.

You do the following the be save from a crash:

auto it = sensor_map.find(device_id+"_"+sensor_type);
if (it!=sensor_map.end) {
   (*it)->publish_state(sensor_value);
} else {
     ESP_LOGW("Hausbus", "Could not findesensor for  device_id %s and sensor_type %s", device_id.c_str(), sensor_type.c_str()); 
}

You don’t want to refuse one or two (litres of) beer, want you? :wink:
I went trough your hints and started the first implementation of the unordered_map with ease. Your C++ code was nearly perfect and went out of the box. The creation of the sensors in the yaml was no trouble at all, a simple copy job. Did you write this from mind? Chapeau!
So, now it’s working in my lab environment, but there is time where the ESP is not reachable (and therefor not updating the sensors) which is definitely caused by the crash you mentioned later.
Unfortunately, I’m absolutely lost with the variable definition auto it and couldn’t find too much about that (or at least something that I understand and can use). When I’d like to implement it, the compiler complains about

src/hausbus.h: In member function 'virtual void HausbusRead::update()':
src/hausbus.h:54:20: error: no match for 'operator!=' (operand types are 'std::__detail::_Node_iterator<std::pair<const std::__cxx11::basic_string<char>, esphome::text_sensor::TextSensor*>, false, true>' and '<unresolved overloaded function type>')
              if (it!=sensor_map.end)
                    ^

As far as I understand, the operator != is unknown but… only in this comparison or in general? I guess it’s related to the variable type auto as no other compare operator works here as well.
Maybe you can help me out another time? I feel like this is the last part to get the full

Haus-bus.de Components Integration

running :partying_face:

Edit1: I just went around this issue for further testing with an additional if (sensor_type == "BRS") { publish ... } and can confirm that the ESP is no longer crashing.
As it is a combination of device_id and sensor_type that has to match, your code seems the most elegant solution, when we get solved this issue with the operator.
I think I should order a whole beer truck until we’re finish here :beers: .

“auto” is no real type. It just lets the compiler know that it should infer the type. The variable will still have a defined type after that. I used auto there, because the real type name for std:: iterators is very long and pointless.

The cause of the compile error is, that it has to be “end()”. “end” is a method of an std::(unordered_)map. It will return a pointer (iterator) to the end of the map (end meaning virtual element after the last element. ). “find” returns either a pointer to the element found or if not found it returns a pointer to the end.

Short update for those who are following this thread: My lab with the Haus-Bus-Devices still exists and I want to have that stuff running. There is unfortunately no progress due to very limited spare time. I’ll come back to that topic when I solved some higher prioritized stuff.