Esphome code, dsmr not working, for unencrypted ascii uart-data using Slimmelezer+

New post for problem with new code:

I live in Finland, Tampere region and finally got HAN enabled meter from utility company. Bought Slimmelezer+ several years ago and was hoping to have easy integration to HA. Sadly, DSMR did not work and after few hours of debugging and scratching head, needed just to do all parsing bymyself with own code.

So, if you live in Finland in Tampere region (maybe someone elsewhere in the world can use same code too, let me know!) and you can read unencrypted uart-data, here’s one example of esphome code:


esphome:
  name: slimmelezer
  name_add_mac_suffix: false
  platform: ESP8266
  esp8266_restore_from_flash: true
  board: d1_mini
  project:
    name: nikop.slimmelezer
    version: "2024.11.0"

api:
  encryption:
    key: **your*encryption*key*here*

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: slimmelezer
  manual_ip:
    static_ip: 192.168.1.200
    gateway: 192.168.1.1
    subnet: 255.255.255.0

safe_mode:

captive_portal:

logger:
  baud_rate: 0

ota:
  platform: esphome

dashboard_import:
  package_import_url: github://zuidwijk/dsmr/slimmelezer.yaml@main
  import_full_config: true

web_server:
  port: 80

uart:
  - id: uart_bus
    baud_rate: 115200
    rx_pin: D7
    rx_buffer_size: 1700
    debug:
      direction: RX
      dummy_receiver: true
      after:
        delimiter: "\r\n"
      sequence:
        - lambda: |-
            std::string data(bytes.begin(), bytes.end());

            // Helper function to extract a numeric value safely
            auto extract_value = [&](const std::string& key, float& result) -> bool {
              size_t start = data.find(key + "(");
              if (start == std::string::npos) return false;
              size_t end = data.find(')', start);
              if (end == std::string::npos) return false;
              std::string value_str = data.substr(start + key.length() + 1, end - start - key.length() - 1);
              char* end_ptr;
              result = strtof(value_str.c_str(), &end_ptr);
              return end_ptr != value_str.c_str();  // Ensure valid conversion
            };

            // Helper function to extract a string value
            auto extract_string = [&](const std::string& key, std::string& result) -> bool {
              size_t start = data.find(key + "(");
              if (start == std::string::npos) return false;
              size_t end = data.find(')', start);
              if (end == std::string::npos) return false;
              result = data.substr(start + key.length() + 1, end - start - key.length() - 1);
              return true;
            };

            float value;
            std::string str_value;

            // OBIS codes and sensor publishing
            if (extract_string("0-0:1.0.0", str_value)) {  // Timestamp
              id(timestamp_sensor).publish_state(str_value.c_str());
            }

            if (extract_value("1-0:1.8.0", value)) {  // Active energy import
              id(active_energy_import_sensor).publish_state(value);
            }
            if (extract_value("1-0:2.8.0", value)) {  // Active energy export
              id(active_energy_export_sensor).publish_state(value);
            }
            // OBIS Code: 1-0:1.7.0 (Active power import)
            if (extract_value("1-0:1.7.0", value)) {
              id(active_power_import_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:2.7.0 (Active power export)
            if (extract_value("1-0:2.7.0", value)) {
              id(active_power_export_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:3.7.0 (Reactive power import)
            if (extract_value("1-0:3.7.0", value)) {
              id(reactive_power_import_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:4.7.0 (Reactive power export)
            if (extract_value("1-0:4.7.0", value)) {
              id(reactive_power_export_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:5.7.0 (Apparent power)
            if (extract_value("1-0:5.7.0", value)) {
              id(apparent_power_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:6.7.0 (Active energy)
            if (extract_value("1-0:6.7.0", value)) {
              id(active_energy_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:7.7.0 (Reactive energy)
            if (extract_value("1-0:7.7.0", value)) {
              id(reactive_energy_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:8.7.0 (Apparent energy)
            if (extract_value("1-0:8.7.0", value)) {
              id(apparent_energy_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:9.7.0 (Power factor)
            if (extract_value("1-0:9.7.0", value)) {
              id(power_factor_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:10.7.0 (Voltage L1)
            if (extract_value("1-0:10.7.0", value)) {
              id(voltage_L1_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:11.7.0 (Voltage L2)
            if (extract_value("1-0:11.7.0", value)) {
              id(voltage_L2_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:12.7.0 (Voltage L3)
            if (extract_value("1-0:12.7.0", value)) {
              id(voltage_L3_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:13.7.0 (Current L1)
            if (extract_value("1-0:13.7.0", value)) {
              id(current_L1_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:14.7.0 (Current L2)
            if (extract_value("1-0:14.7.0", value)) {
              id(current_L2_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:15.7.0 (Current L3)
            if (extract_value("1-0:15.7.0", value)) {
              id(current_L3_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:16.7.0 (Power demand)
            if (extract_value("1-0:16.7.0", value)) {
              id(power_demand_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:17.7.0 (Maximum power demand)
            if (extract_value("1-0:17.7.0", value)) {
              id(max_power_demand_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:18.7.0 (Energy import in last 15 minutes)
            if (extract_value("1-0:18.7.0", value)) {
              id(energy_import_15min_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:19.7.0 (Energy export in last 15 minutes)
            if (extract_value("1-0:19.7.0", value)) {
              id(energy_export_15min_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:20.7.0 (Energy import in last 60 minutes)
            if (extract_value("1-0:20.7.0", value)) {
              id(energy_import_60min_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:21.7.0 (Energy export in last 60 minutes)
            if (extract_value("1-0:21.7.0", value)) {
              id(energy_export_60min_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:22.7.0 (Total energy import)
            if (extract_value("1-0:22.7.0", value)) {
              id(total_energy_import_sensor).publish_state(value);
            }

            // OBIS Code: 1-0:23.7.0 (Total energy export)
            if (extract_value("1-0:23.7.0", value)) {
              id(total_energy_export_sensor).publish_state(value);
            }


sensor:
  - platform: template
    name: "Active Energy Import"
    id: active_energy_import_sensor
    unit_of_measurement: "kWh"
    state_class: total
    device_class: energy
    accuracy_decimals: 3

  - platform: template
    name: "Active Energy Export"
    id: active_energy_export_sensor
    unit_of_measurement: "kWh"
    state_class: total
    device_class: energy
    accuracy_decimals: 3

  - platform: template
    name: "Active Power Import"
    id: active_power_import_sensor
    unit_of_measurement: "W"
    accuracy_decimals: 2

  - platform: template
    name: "Active Power Export"
    id: active_power_export_sensor
    unit_of_measurement: "W"
    accuracy_decimals: 2

  - platform: template
    name: "Reactive Power Import"
    id: reactive_power_import_sensor
    unit_of_measurement: "var"
    accuracy_decimals: 2

  - platform: template
    name: "Reactive Power Export"
    id: reactive_power_export_sensor
    unit_of_measurement: "var"
    accuracy_decimals: 2

  - platform: template
    name: "Apparent Power"
    id: apparent_power_sensor
    unit_of_measurement: "VA"
    accuracy_decimals: 2

  - platform: template
    name: "Active Energy"
    id: active_energy_sensor
    unit_of_measurement: "kWh"
    accuracy_decimals: 3

  - platform: template
    name: "Reactive Energy"
    id: reactive_energy_sensor
    unit_of_measurement: "kvarh"
    accuracy_decimals: 3

  - platform: template
    name: "Apparent Energy"
    id: apparent_energy_sensor
    unit_of_measurement: "kVAh"
    accuracy_decimals: 3

  - platform: template
    name: "Power Factor"
    id: power_factor_sensor
    unit_of_measurement: ""
    accuracy_decimals: 2

  - platform: template
    name: "Voltage L1"
    id: voltage_L1_sensor
    unit_of_measurement: "V"
    accuracy_decimals: 2

  - platform: template
    name: "Voltage L2"
    id: voltage_L2_sensor
    unit_of_measurement: "V"
    accuracy_decimals: 2

  - platform: template
    name: "Voltage L3"
    id: voltage_L3_sensor
    unit_of_measurement: "V"
    accuracy_decimals: 2

  - platform: template
    name: "Current L1"
    id: current_L1_sensor
    unit_of_measurement: "A"
    accuracy_decimals: 2

  - platform: template
    name: "Current L2"
    id: current_L2_sensor
    unit_of_measurement: "A"
    accuracy_decimals: 2

  - platform: template
    name: "Current L3"
    id: current_L3_sensor
    unit_of_measurement: "A"
    accuracy_decimals: 2

  - platform: template
    name: "Power Demand"
    id: power_demand_sensor
    unit_of_measurement: "W"
    accuracy_decimals: 2

  - platform: template
    name: "Maximum Power Demand"
    id: max_power_demand_sensor
    unit_of_measurement: "W"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Import in Last 15 Minutes"
    id: energy_import_15min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Export in Last 15 Minutes"
    id: energy_export_15min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Import in Last 60 Minutes"
    id: energy_import_60min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Export in Last 60 Minutes"
    id: energy_export_60min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Total Energy Import"
    id: total_energy_import_sensor
    unit_of_measurement: "kWh"
    accuracy_decimals: 3

  - platform: template
    name: "Total Energy Export"
    id: total_energy_export_sensor
    unit_of_measurement: "kWh"
    accuracy_decimals: 3

text_sensor:
  - platform: template
    name: "Timestamp"
    id: timestamp_sensor  
  - platform: wifi_info
    ip_address:
      name: "SlimmeLezer IP Address"
    ssid:
      name: "SlimmeLezer Wi-Fi SSID"
    bssid:
      name: "SlimmeLezer Wi-Fi BSSID"
  - platform: version
    name: "ESPHome Version"
    hide_timestamp: true

Hi Nikop,

I hope you can help me as I start to loose my mind. I’m trying to get DSMR working for a couple of days now and I’m on the point to give up. One way or an other the parsing is not working. So I was very happy to see your post. But trying to use it, I have still the same issue. I’m using an ESP32 Wroom and invert the RX port so I can connect straight to the pin 5 of the P1 connector. I do receive the telegram data but the data is not parsed and loaded in the sensors. I do not know enough about ESPHome to understand how the parser is invoked but it is clearly not happening. Can you provide me with any hint what is going wrong.

Many thanks,
Eric

Hi @eric116!
I migth not be the best person to help you as I managed only to get my code fully working state after battling it together with Claude.ai. I suggest you give some AI-coding assistant task to check your own code.

I’ve now several times tried to optimize the code above and always come back to the limitations of esphome: only way to get those ascii info from port is to use debug: which in turn is huge overhead and sometimes logs will taunt you that everything is taking quite a long time in the functions. :slight_smile:

Here’s my current code which might have some redundant sensors:


esphome:
  name: slimmelezer
  name_add_mac_suffix: false
  project:
    name: slimmelezer
    version: "2025.3.1"

esp8266:
  board: d1_mini
  restore_from_flash: true
  framework:
    version: 3.1.2



api:
  encryption:
    key: your-own-key-here

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: slimmelezer
  manual_ip:
    static_ip: your-own-ip-info-here
    gateway: your-own-ip-info-here
    subnet: your-own-ip-info-here

safe_mode:

captive_portal:

logger:
  baud_rate: 0
  level: INFO
  logs:
    component: ERROR  # Only show errors for component timeouts
    uart: WARN     # See important UART warnings
    sensor: INFO      # Track sensor state changes


ota:
  platform: esphome

dashboard_import:
  package_import_url: github://zuidwijk/dsmr/slimmelezer.yaml@main
  import_full_config: true

web_server:
  port: 80

uart:
  - id: uart_bus
    baud_rate: 115200
    rx_pin: D7
    rx_buffer_size: 2048  # Increased from 1700
    debug:
      direction: RX
      dummy_receiver: true
      after:
        timeout: 50ms  # Increased from 30ms
        delimiter: "\r\n"
      sequence:
        - lambda: |-
            const char* data = reinterpret_cast<const char*>(bytes.data());
            size_t len = bytes.size();
            const char* ptr = data;
            const char* end = data + len;
            
            // Define variables for debouncing
            static float last_values[23] = {NAN};
            static unsigned long last_updates[23] = {0};
            const unsigned long min_update_interval = 5000;  // 5 seconds minimum interval
            
            // Helper function for sensor updates with debouncing
            auto update_sensor = [&](int index, auto& sensor, float val) {
              unsigned long now = millis();
              if (isnan(last_values[index]) ||
                  abs(val - last_values[index]) > 0.001 ||  // Only update if value changed significantly
                  (now - last_updates[index] >= min_update_interval)) {
                sensor->publish_state(val);
                last_values[index] = val;
                last_updates[index] = now;
              }
            };
            
            while (ptr < end) {
              const char* open = static_cast<const char*>(memchr(ptr, '(', end - ptr));
              if (!open) break;
              const char* code_start = ptr;
              const char* newline = static_cast<const char*>(memrchr(ptr, '\n', open - ptr));
              if (newline) code_start = newline + 1;
              size_t code_len = open - code_start;
              char code_buf[16];
              if (code_len >= sizeof(code_buf)) code_len = sizeof(code_buf) - 1;
              memcpy(code_buf, code_start, code_len);
              code_buf[code_len] = '\0';
              char* code_ptr = code_buf;
              while (*code_ptr && isspace(*code_ptr)) code_ptr++;
              
              const char* close = static_cast<const char*>(memchr(open, ')', end - open));
              if (!close) break;
              size_t value_len = close - open - 1;
              char value_buf[32];
              if (value_len >= sizeof(value_buf)) value_len = sizeof(value_buf) - 1;
              memcpy(value_buf, open + 1, value_len);
              value_buf[value_len] = '\0';
              char* val_start = value_buf;
              while (*val_start && isspace(*val_start)) { val_start++; }
              char* val_end = value_buf + strlen(value_buf) - 1;
              while (val_end > val_start && isspace(*val_end)) { *val_end = '\0'; val_end--; }
              
              if (strcmp(code_ptr, "0-0:1.0.0") == 0) {
                id(timestamp_sensor).publish_state(val_start);
              } else {
                float val = strtof(val_start, nullptr);
                if (strcmp(code_ptr, "1-0:1.8.0") == 0) update_sensor(0, id(active_energy_import_sensor), val);
                else if (strcmp(code_ptr, "1-0:2.8.0") == 0) update_sensor(1, id(active_energy_export_sensor), val);
                else if (strcmp(code_ptr, "1-0:1.7.0") == 0) update_sensor(2, id(active_power_import_sensor), val);
                else if (strcmp(code_ptr, "1-0:2.7.0") == 0) update_sensor(3, id(active_power_export_sensor), val);
                else if (strcmp(code_ptr, "1-0:3.7.0") == 0) update_sensor(4, id(reactive_power_import_sensor), val);
                else if (strcmp(code_ptr, "1-0:4.7.0") == 0) update_sensor(5, id(reactive_power_export_sensor), val);
                else if (strcmp(code_ptr, "1-0:5.7.0") == 0) update_sensor(6, id(apparent_power_sensor), val);
                else if (strcmp(code_ptr, "1-0:6.7.0") == 0) update_sensor(7, id(active_energy_sensor), val);
                else if (strcmp(code_ptr, "1-0:7.7.0") == 0) update_sensor(8, id(reactive_energy_sensor), val);
                else if (strcmp(code_ptr, "1-0:8.7.0") == 0) update_sensor(9, id(apparent_energy_sensor), val);
                else if (strcmp(code_ptr, "1-0:9.7.0") == 0) update_sensor(10, id(power_factor_sensor), val);
                else if (strcmp(code_ptr, "1-0:10.7.0") == 0) update_sensor(11, id(voltage_L1_sensor), val);
                else if (strcmp(code_ptr, "1-0:11.7.0") == 0) update_sensor(12, id(voltage_L2_sensor), val);
                else if (strcmp(code_ptr, "1-0:12.7.0") == 0) update_sensor(13, id(voltage_L3_sensor), val);
                else if (strcmp(code_ptr, "1-0:13.7.0") == 0) update_sensor(14, id(current_L1_sensor), val);
                else if (strcmp(code_ptr, "1-0:14.7.0") == 0) update_sensor(15, id(current_L2_sensor), val);
                else if (strcmp(code_ptr, "1-0:15.7.0") == 0) update_sensor(16, id(current_L3_sensor), val);
                else if (strcmp(code_ptr, "1-0:16.7.0") == 0) update_sensor(17, id(power_demand_sensor), val);
                else if (strcmp(code_ptr, "1-0:17.7.0") == 0) update_sensor(18, id(max_power_demand_sensor), val);
                else if (strcmp(code_ptr, "1-0:18.7.0") == 0) update_sensor(19, id(energy_import_15min_sensor), val);
                else if (strcmp(code_ptr, "1-0:19.7.0") == 0) update_sensor(20, id(energy_export_15min_sensor), val);
                else if (strcmp(code_ptr, "1-0:20.7.0") == 0) update_sensor(21, id(energy_import_60min_sensor), val);
                else if (strcmp(code_ptr, "1-0:21.7.0") == 0) update_sensor(22, id(energy_export_60min_sensor), val);
              }
              ptr = close + 1;
            }


# debug:
#   update_interval: 60s 
            
sensor:
  - platform: template
    name: "Active Energy Import"
    id: active_energy_import_sensor
    unit_of_measurement: "kWh"
    state_class: total
    device_class: energy
    accuracy_decimals: 3

  - platform: template
    name: "Active Energy Export"
    id: active_energy_export_sensor
    unit_of_measurement: "kWh"
    state_class: total
    device_class: energy
    accuracy_decimals: 3

  - platform: template
    name: "Active Power Import"
    id: active_power_import_sensor
    unit_of_measurement: "kW"
    accuracy_decimals: 2

  - platform: template
    name: "Active Power Export"
    id: active_power_export_sensor
    unit_of_measurement: "kW"
    accuracy_decimals: 2

  - platform: template
    name: "Reactive Power Import"
    id: reactive_power_import_sensor
    unit_of_measurement: "var"
    accuracy_decimals: 2

  - platform: template
    name: "Reactive Power Export"
    id: reactive_power_export_sensor
    unit_of_measurement: "var"
    accuracy_decimals: 2

  - platform: template
    name: "Apparent Power"
    id: apparent_power_sensor
    unit_of_measurement: "VA"
    accuracy_decimals: 2

  - platform: template
    name: "Active Energy"
    id: active_energy_sensor
    unit_of_measurement: "kWh"
    accuracy_decimals: 3

  - platform: template
    name: "Reactive Energy"
    id: reactive_energy_sensor
    unit_of_measurement: "kvarh"
    accuracy_decimals: 3

  - platform: template
    name: "Apparent Energy"
    id: apparent_energy_sensor
    unit_of_measurement: "kVAh"
    accuracy_decimals: 3

  - platform: template
    name: "Power Factor"
    id: power_factor_sensor
    unit_of_measurement: ""
    accuracy_decimals: 2

  - platform: template
    name: "Voltage L1"
    id: voltage_L1_sensor
    unit_of_measurement: "V"
    accuracy_decimals: 2

  - platform: template
    name: "Voltage L2"
    id: voltage_L2_sensor
    unit_of_measurement: "V"
    accuracy_decimals: 2

  - platform: template
    name: "Voltage L3"
    id: voltage_L3_sensor
    unit_of_measurement: "V"
    accuracy_decimals: 2

  - platform: template
    name: "Current L1"
    id: current_L1_sensor
    unit_of_measurement: "A"
    accuracy_decimals: 2

  - platform: template
    name: "Current L2"
    id: current_L2_sensor
    unit_of_measurement: "A"
    accuracy_decimals: 2

  - platform: template
    name: "Current L3"
    id: current_L3_sensor
    unit_of_measurement: "A"
    accuracy_decimals: 2

  - platform: template
    name: "Power Demand"
    id: power_demand_sensor
    unit_of_measurement: "W"
    accuracy_decimals: 2

  - platform: template
    name: "Maximum Power Demand"
    id: max_power_demand_sensor
    unit_of_measurement: "W"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Import in Last 15 Minutes"
    id: energy_import_15min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Export in Last 15 Minutes"
    id: energy_export_15min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Import in Last 60 Minutes"
    id: energy_import_60min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Energy Export in Last 60 Minutes"
    id: energy_export_60min_sensor
    unit_of_measurement: "Wh"
    accuracy_decimals: 2

  - platform: template
    name: "Total Energy Import"
    id: total_energy_import_sensor
    unit_of_measurement: "kWh"
    accuracy_decimals: 3

  - platform: template
    name: "Total Energy Export"
    id: total_energy_export_sensor
    unit_of_measurement: "kWh"
    accuracy_decimals: 3

  # Laitteen käyttöaikasensori
  - platform: uptime
    name: "Slimmelezer Käyttöaika"
    filters:
      - lambda: return x / 3600.0;
    unit_of_measurement: "h"
    
     
  # WiFi-signaalin voimakkuussensori
  - platform: wifi_signal
    name: "Slimmelezer WiFi-signaali"
    update_interval: 60s

text_sensor:
  - platform: template
    name: "Timestamp"
    id: timestamp_sensor  
  - platform: wifi_info
    ip_address:
      name: "SlimmeLezer IP Address"
    ssid:
      name: "SlimmeLezer Wi-Fi SSID"
    bssid:
      name: "SlimmeLezer Wi-Fi BSSID"
  - platform: version
    name: "ESPHome Version"
    hide_timestamp: true
  - platform: version
    name: "Slimmelezer ESPHome Versio"
    
  - platform: wifi_info
    ip_address:
      name: "Slimmelezer IP-osoite"
    ssid:
      name: "Slimmelezer WLAN-verkko"
      

Hi Hikop,

Thanks for your reply. In the meantime I have DSMR working on a ESP32-C3 super mini board. Everything works fine with just the mini board and an JR12 connector. No need for a circuit to inverse the phase of the RX bitstream, nor I need a volt shifter for the Data Request. 3.3V on the DR pin does it on my meter.
My main problem was the unreliable Wifi circuit of the ESP-Wroom boards.

I also used your original parser code to add Gas consumption and the Time Stamp to the reporting. So I really get all the information I was looking for.

I looked at your latest code but I’m afraid that I do not have enough C++ knowledge to understand it.

Best regards,
Eric

1 Like

@eric116, nice to hear that you got it working. And do not worry about the code, if it is working for you now with the old code, just go with it! :slight_smile: