Create custom uart sensor - Delta Solivia Inverter 3.0 EU G4 TR

EDIT: SOLVED !!
In a few days I’ll post complete ESPHome config with additional explanation on the registers etc.

I really could do with some help on this subject, as I’ve searched in vain for examples on how to create some custom uart sensors for my Delta inverter.

I’ve a Solivia gateway on the modbus already, which is quite chatty (a data package every second or so).
So I’m not able to use a modbus custom_command sensor, as my requests and the replies will drown in the gateway packages (tested that already). I’ve also an issue actually identifying the correct register commands, as all but one tested so far differs from the Solivia protocol data that can be found on the web.

I’ve looked into the gateway package response from the inverter which differs a lot from all the examples I’ve found on the net. So I had to analyse the package and have now identified the specific bytes (registers) I would like to pull out as sensors.

My package is for 0xff bytes (normally a lot fewer bytes are send). Both the request and reply is in the uart buffer, as I’m only sniffing the uart data:

02:05:01:02:60:01:85:FC:03
02:06:01:FF:60:01
45:4F:45:34:36:30:31:30:32:38:37:31:31:33:32:38:37:30:38:31:33:30:31:30:30:33:33:39:38:31:33:30
31:30:38:01:02:1A:00:00:00:00:23:34:00:00:00:00:23:34:00:00:00:00:00:00:00:00:00:00:23:34:00:00
00:00:00:00:00:00:00:00:01:00:03:96:01:9A:00:16:00:00:00:00:00:00:00:00:00:00:00:00:00:23:00:EC
13:88:03:64:FF:4E:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:08:98:07:D0:00:33
00:33:00:00:00:00:02:14:3E:3E:00:26:48:A6:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:08:F7:00:00:01:16:00:00:00:00:00:00
00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:34:A4:03

02:14:3E:3E = total yield = 34881086 = 34881,086kWh
08:F7 = daily yield = 2295W
(Haven’t located current AC power - yet :slight_smile:)

So the question is how do I make a sensor out of eg. the 32float register at position 0x86 (first position as 0x00) in the actual package data ?

ANY help getting me started on this would be much appreciated.
My programming skills are somewhat limited, so I’m quite clueless on how to proceed.

Thank you in advance
Henning

With the help from this link I finally managed to tweak that code into something useful for my little project (Great help :tada::sunglasses:). I had serious issues with the package lenght I need to store and check in the buffer (262 bytes). But finally managed to get a stable result, checking for the ETX byte at the right position as a quick and dirty ‘package integrity check’. Might be crude, but hey it works and is absolutely stable :laughing:

But i really have an issue to get multiple sensors reported correctly back to ESPHome. Simply can’t get anything besides publish_state(data.UInt16) to update the sensors. publish_state is only useful when there’s one sensor only. So I really could do with some help to get that part working as well :slightly_smiling_face:

Rgds.
Henning

Custom_sensor:


class solivia : public PollingComponent, public Sensor, public UARTDevice {
//class solivia : public Sensor, public UARTDevice {
  public:
    solivia(UARTComponent *parent) : PollingComponent(10000), UARTDevice(parent) {}
    //solivia(UARTComponent *parent) : UARTDevice(parent) {}
    Sensor *yield = new Sensor();
    Sensor *production = new Sensor();
  
  void setup() override {

  }

  std::vector<int> bytes;

  //void loop() override {  
  void update() {
    while(available() > 0) {
      bytes.push_back(read());      

      //make sure at least 8 header bytes are available to check
      if(bytes.size() < 8)       
      {
        continue;  
      }
      //ESP_LOGD("custom", "checking for init bytes");
      // Check for Delta Solivia Gateway package response.
      if(bytes[0] != 0x02 || bytes[1] != 0x06 || bytes[2] != 0x01 || bytes[3] != 0xFF || bytes[4] != 0x60 || bytes[5] != 0x01) {
        bytes.erase(bytes.begin()); //remove first byte from buffer
        //buffer will never get above 8 until the header is correct
        continue;
      }      
      
	    if (bytes.size() == 262) {

        TwoByte production_data;
        production_data.Byte[0] = bytes[105];// Seems to be DV voltage not production though
        production_data.Byte[1] = bytes[104];
        TwoByte yield_data;
        yield_data.Byte[0] = bytes[187]; // Daily yield lsb
        yield_data.Byte[1] = bytes[186]; // Daily yield msb
        char etx;
        etx = bytes[261]; // ETX byte

        // Quick and dirty check for package integrity is needed, to avoid irratic sensor value updates 
        // This effectively blocks out any false sensor updates
        // Check if ETX = 3. If not (invalid package), ditch whole package, clear buffer and continue
        if (etx != 0x03) {
          ESP_LOGI("custom", "ETX check failure - No sensor update. ETX: %i", etx);
          bytes.clear();
          continue;
        }
          //publish_state(yield_data.UInt16); //Only works with one sensor !
          //id(yield).publish_state(yield_data.UInt16); //Not updating sensor ?
          //yield->publish_state(yield.UInt16);
          yield->publish_state(yield_data.UInt16);

          //publish_state(production_data.UInt16); //Only works with one sensor !
          //production->publish_state(production.UInt16);
          id(production).publish_state(production_data.UInt16); //Not updating sensor ?

	        ESP_LOGI("custom", "ETX: %i", etx);
          ESP_LOGI("custom", "Daily yield: %i", yield_data.UInt16);
	        ESP_LOGI("custom", "Current production: %i", production_data.UInt16);
        
          bytes.clear();
      }
      else {
      }    
    }    
  }

  typedef union
  {
    unsigned char Byte[2];
    int16_t Int16;
    uint16_t UInt16;
    unsigned char UChar;
    char Char;
  }TwoByte;};

ESPHome yaml:

  name: Delta
  platform: ESP32
  board: nodemcu-32s
  includes:
    - test.h
wifi:
  ssid: "my_ssid"
  password: "my_password"
  manual_ip:
    static_ip: xxx.xxx.x.x
    gateway: xxx.xxx.x.x
    subnet: 255.255.255.0
    
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32 Fallback Hotspot"
    password: "my_password"

captive_portal:

# Enable logging
logger:
  level: VERBOSE
  #baud_rate: 0
  
# Enable Home Assistant API
api:

ota:

binary_sensor:
  - platform: status
    name: "Inverter Status"
    id: system_status
 
uart:
  id: mod_bus
  tx_pin: 17
  rx_pin: 16
  baud_rate: 19200
  parity: NONE
  stop_bits: 1
  rx_buffer_size: 1024 # Increase buffer size as package is 262 bytes in total
  #debug:
    
sensor:
- platform: custom
  lambda: |-
    auto production = new solivia(id(mod_bus));
    App.register_component(production);
     return {production};
  sensors:
    name: "Current production"
    unit_of_measurement: W
    accuracy_decimals: 0
    filters:
    - throttle: 60s

- platform: custom
  lambda: |-
    auto yield = new solivia(id(mod_bus));
    App.register_component(yield);
    return {yield};
  sensors:
    name: "Solar daily yield"
    unit_of_measurement: W
    accuracy_decimals: 0
    filters:
    - throttle: 60s
1 Like