Working ESPHome Solution for Finnish Smart Electricity Meters (SESKO SK 13-1:2022)

Hello community!

I’ve developed a reliable ESPHome configuration for Finnish P1-compatible smart electricity meters that fully complies with the SESKO SK 13-1:2022 standard. The solution has been tested successfully with Finnish meter (messages starting with /ADN9 identification strings).

Original Thread: ESPHome code DSMR not working for unencrypted ASCII UART data using SlimmeLezer

:pushpin: Key Updates:

  1. SESKO SK 13-1:2022 Compliance

    • Supports all OBIS codes used in Finland, including phase-specific currents, voltages, and reactive power.
    • Fixes issues with non-Dutch identification strings (e.g., /ADN9 instead of /ISK5).
  2. Why Not the DSMR Component?

    • Identification String Validation: The official DSMR component strictly validates Dutch/Belgian meter IDs, rejecting Finnish formats.
    • Missing OBIS Codes: The DSMR component lacks support for critical SESKO-specific codes (e.g., 1-0:31.7.0 for phase currents).
    • Reactive Power Handling: Finnish meters use different OBIS codes for reactive power export/import per phase.
  3. Country-Specific Standards

    • :finland: Finland: SESKO SK 13-1:2022

      • Search: “SESKO SK 13-1 OBIS-koodit”, “Suomalainen sähkömittarin rajapintaspesifikaatio”*
    • :sweden: Sweden: SEK Handbook 191

      • Search: “SEK Handbook 191 electricity metering codes”, “Svenska elmätarkoder”*
    • :norway: Norway: NEK 441

      • Search: “NEK 441 smart meter interface”, “Norsk målerstandard NEK 441”*
    • :denmark: Denmark: DS/INF 163

      • Search: “DS/INF 163 Danish smart meter specs”, “Dansk målerstandard DS/INF 163”*
    • :earth_africa: EU: EN 62056-61
      Search: “EN 62056-61 OBIS identifiers”, “European electricity metering standard”

:wrench: Customization Guide:

To adapt this for other Nordic/European meters:

  1. Modify the obis_table[] array in the ESPHome code to include your country’s OBIS codes.
  2. Example for Swedish meters (SEK 191):
    {"1-0:1.8.0", 0},   // Active energy import (Sweden)
    {"1-0:32.7.0", 6},  // Voltage L1 (Sweden)
    
  3. Use uart.debug to identify your meter’s OBIS codes.

:warning: Notes:

  • Tested only with ESPHome 2025.5+.
  • Supports unencrypted ASCII UART data (common in Nordic countries).

:chart_with_upwards_trend: Next Steps:

  • Community Collaboration Needed!
    • ESPHome Component Experts Wanted: If you have experience with ESPHome component development, let’s work together to upstream Nordic support into the official DSMR component!
    • Specific Needs:
      • Relaxed meter ID validation for Nordic formats
      • Expanded OBIS code registry supporting SESKO/NEK/SEK codes
    • if you can, please help bridge the gap between this solution and official ESPHome support!

Huge thanks in advance to anyone who wants to help! :bulb: Special shoutout to @zuidwijk for the original SlimmeLezer concept.

Tags: #dsmr ESPHome #slimmelezer #nordic #smartmeter #finland


Full configuration on the next post. Word of caution: if you are implementing whole thing to your own little ESP, use only sensors you really need as memory get fast sparse when using DEBUG. Let’s work together to make this an official ESPHome feature! :blush:

1 Like

Cfg:

#############################################################################
#                                                                           #
#                           SLIMMELEZER ESP8266                            #
#                                                                           #
#############################################################################
#
# ESPHome configuration for SlimmeLezer P1 Smart Meter Reader
#
# Project: Finnish Smart Meter (DSMR) Reader with SESKO SK 13-1:2022 compliance
# Version: 2025.5.4
# Author: nikop
# Hardware: ESP8266 D1 Mini + SlimmeLezer P1 interface
# Meter Type: Finnish smart meter with P1 port
#
# Features:
# - Complete SESKO SK 13-1:2022 OBIS code support
# - UART debugging and statistics
# - WiFi signal monitoring
# - Timeout handling and success rate tracking
# - Finnish meter identification support (/ADN9)
#
# References:
# - SESKO SK 13-1:2022 "OBIS codes for electricity metering"
# - ESPHome UART Component: https://esphome.io/components/uart.html
# - Home Assistant DSMR: https://www.home-assistant.io/integrations/dsmr/
# - Original SlimmeLezer: https://github.com/zuidwijk/dsmr
#
# OBIS Code Sources:
# - Table 1: Basic energy and power values
# - Annex 2 s.10: Phase-specific reactive power
# - Annex 2 s.11: Phase-specific voltage and current
#
# Last Updated: 2025-05-27
# ESPHome Version: 2025.5.0+
#
#############################################################################

esphome:
  name: slimmelezer
  name_add_mac_suffix: false
  project:
    name: nikop.slimmelezer
    version: "2025.5.4"                     # Version incremented .3 → .4
  on_boot:
    then:
      - if:
          condition:
            lambda: return id(has_key);
          then:
            - lambda: |-
                std::string key(id(stored_decryption_key), 32);
                ESP_LOGI("dsmr", "Using stored decryption key");
          else:
            - logger.log:
                level: info
                format: "Not using decryption key. If you need to set a key use Home Assistant service 'ESPHome: slimmelezer_set_dsmr_key'"

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

api:
  encryption:
    key: XXXXXXXXXXXXXXXXXX
  services: 
    - service: set_dsmr_key
      variables:
        private_key: string
      then:
        - logger.log:
            format: Setting private key %s. Set to empty string to disable
            args: [private_key.c_str()]
        - globals.set:
            id: has_key
            value: !lambda "return private_key.length() == 32;"
        - lambda: |-
            if (private_key.length() == 32)
              private_key.copy(id(stored_decryption_key), 32);

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: slimmelezer
  manual_ip:
    static_ip: X.X.X.X
    gateway: X.X.X.X
    subnet: 255.255.255.0
  output_power: 20dB                        # Fixed: 18db → 20dB
  fast_connect: false                       # Better AP selection for weak signal
  power_save_mode: none                     # Maximum WiFi stability

safe_mode:

captive_portal:

# Logger configuration
# DEBUG level required for UART debug functionality
logger:
  baud_rate: 0
  level: DEBUG                              # Required for UART parser to function
  logs:
    component: ERROR                        # Only critical component errors
    sensor: WARN                            # Sensor warnings and above
    uart: DEBUG                             # Essential for UART parser functionality

ota:
  platform: esphome

web_server:
  port: 80

# Global variables for UART statistics tracking
globals:
  - id: has_key
    type: bool
    restore_value: yes
    initial_value: "false"
  - id: stored_decryption_key
    type: char[32]
    restore_value: yes
  # UART statistics (reset on reboot for clean sessions)
  - id: uart_timeout_counter_global
    type: int
    restore_value: false
    initial_value: '0'
  - id: uart_packet_counter_global
    type: int
    restore_value: false
    initial_value: '0'
  - id: last_timeout_timestamp_global
    type: unsigned long
    restore_value: false
    initial_value: '0'

# UART statistics update interval (optimized for 10s)
interval:
  - interval: 10s
    then:
      - lambda: |-
          // Read global values in interrupt-safe manner
          int current_timeouts = id(uart_timeout_counter_global);
          int current_packets = id(uart_packet_counter_global);
          unsigned long current_time = id(last_timeout_timestamp_global);
          
          static int last_reported_timeouts = 0;
          static int last_reported_packets = 0;
          static unsigned long last_reported_time = 0;
          
          // Update sensors only on changes (saves CPU)
          if (current_timeouts != last_reported_timeouts) {
            id(uart_timeout_sensor).publish_state(current_timeouts);
            last_reported_timeouts = current_timeouts;
          }
          
          if (current_packets != last_reported_packets) {
            id(uart_packets_sensor).publish_state(current_packets);
            
            if (current_packets > 0) {
              float success_rate = 100.0 - ((float)current_timeouts / current_packets * 100.0);
              id(uart_success_rate_sensor).publish_state(success_rate);
            }
            last_reported_packets = current_packets;
          }
          
          // Minutes since last timeout calculation
          if (current_time != last_reported_time) {
            if (current_time > 0) {
              unsigned long minutes_since = (millis() - current_time) / 60000;
              id(minutes_since_timeout_sensor).publish_state(minutes_since);
            }
            last_reported_time = current_time;
          }

# UART configuration with debug parser
# Essential for Finnish smart meter compatibility
uart:
  - id: uart_bus
    baud_rate: 115200
    rx_pin: D7
    rx_buffer_size: 1024
    debug:
      direction: RX
      dummy_receiver: true                  # Required for parser functionality
      after:
        timeout: 50ms                       # Optimized timeout for DSMR telegrams
        delimiter: "\r\n"                   # DSMR line termination
      sequence:
        - lambda: |-
            // Lightweight timeout detection (interrupt-safe)
            if (bytes.size() < 3) {
              id(uart_timeout_counter_global) += 1;
              id(last_timeout_timestamp_global) = millis();
              return; // Skip processing empty data
            }
            
            id(uart_packet_counter_global) += 1;

            // Complete SESKO SK 13-1:2022 OBIS table (ordered by standard)
            struct ObisEntry {
              const char* code;
              int index;
            };
            
            static const ObisEntry obis_table[] = {
              // Basic energies (SESKO SK 13-1:2022 Table 1, Annex 2)
              {"1-0:1.8.0", 0},   // Active energy import, cumulative total
              {"1-0:2.8.0", 1},   // Active energy export, cumulative total
              {"1-0:3.8.0", 2},   // Reactive energy import total
              {"1-0:4.8.0", 3},   // Reactive energy export total
              
              // Instantaneous power totals (SESKO SK 13-1:2022 Table 1)
              {"1-0:1.7.0", 4},   // Active power import
              {"1-0:2.7.0", 5},   // Active power export
              {"1-0:3.7.0", 6},   // Reactive power import
              {"1-0:4.7.0", 7},   // Reactive power export
              
              // Phase-specific active power (SESKO SK 13-1:2022 Table 1)
              {"1-0:21.7.0", 8},  // Active power L1+
              {"1-0:22.7.0", 9},  // Active power export L1
              {"1-0:41.7.0", 10}, // Active power L2+
              {"1-0:42.7.0", 11}, // Active power export L2
              {"1-0:61.7.0", 12}, // Active power L3+
              {"1-0:62.7.0", 13}, // Active power export L3
              
              // Phase-specific reactive power (SESKO SK 13-1:2022 Annex 2 s.10)
              {"1-0:23.7.0", 14}, // Reactive power import L1
              {"1-0:24.7.0", 15}, // Reactive power export L1
              {"1-0:43.7.0", 16}, // Reactive power import L2
              {"1-0:44.7.0", 17}, // Reactive power export L2
              {"1-0:63.7.0", 18}, // Reactive power import L3
              {"1-0:64.7.0", 19}, // Reactive power export L3
              
              // Voltages (SESKO SK 13-1:2022 Annex 2 s.11)
              {"1-0:32.7.0", 20}, // Phase voltage L1
              {"1-0:52.7.0", 21}, // Phase voltage L2
              {"1-0:72.7.0", 22}, // Phase voltage L3
              
              // Currents (SESKO SK 13-1:2022 Annex 2 s.11)
              {"1-0:31.7.0", 23}, // Phase current L1
              {"1-0:51.7.0", 24}, // Phase current L2
              {"1-0:71.7.0", 25}, // Phase current L3
              
              // Additional technical values
              {"1-0:14.7.0", 26}, // Frequency
              {"1-0:13.7.0", 27}, // Signed current L1
              {"1-0:33.7.0", 28}, // Signed current L2
              {"1-0:53.7.0", 29}, // Signed current L3
              
              // Tariff energies
              {"1-0:1.8.1", 30},  // Energy import tariff 1
              {"1-0:1.8.2", 31},  // Energy import tariff 2
              {"1-0:2.8.1", 32},  // Energy export tariff 1
              {"1-0:2.8.2", 33},  // Energy export tariff 2
              
              // Maximum power demand
              {"1-0:1.6.0", 34},  // Max power demand period 1
              {"1-0:2.6.0", 35},  // Max power demand period 2
              
              // Error statistics
              {"0-0:96.7.21", 36}, // Electricity failures (short)
              {"0-0:96.7.19", 37}, // Electricity failures (long)
              
              // Meter identification (text sensors)
              {"0-0:96.1.0", 38}, // Meter serial number
              {"0-0:96.1.1", 39}, // Meter type
              {"0-0:42.0.0", 40}, // Meter ID
              {"1-0:0.2.0", 41},  // Firmware version
              {"0-0:96.13.0", 42} // Consumer message
            };
            
            static const int table_size = sizeof(obis_table) / sizeof(ObisEntry);
            
            // Optimized debouncing array (43 elements)
            static float last_values[43] = {NAN};
            static unsigned long last_updates[43] = {0};
            const unsigned long min_update_interval = 5000;
            
            // Optimized sensor update function
            auto update_sensor_by_index = [&](int index, float val) {
              unsigned long now = millis();
              if (index >= 43) return; // Safety check
              
              if (isnan(last_values[index]) || 
                  fabs(val - last_values[index]) > 0.001 ||
                  (now - last_updates[index] >= min_update_interval)) {
                
                // SESKO-ordered switch-case
                switch (index) {
                  // Basic energies (0-3)
                  case 0: id(active_energy_import_sensor)->publish_state(val); break;
                  case 1: id(active_energy_export_sensor)->publish_state(val); break;
                  case 2: id(reactive_energy_import_sensor)->publish_state(val); break;
                  case 3: id(reactive_energy_export_sensor)->publish_state(val); break;
                  
                  // Instantaneous power totals (4-7)
                  case 4: id(active_power_import_sensor)->publish_state(val); break;
                  case 5: id(active_power_export_sensor)->publish_state(val); break;
                  case 6: id(reactive_power_import_sensor)->publish_state(val); break;
                  case 7: id(reactive_power_export_sensor)->publish_state(val); break;
                  
                  // Phase-specific active power (8-13)
                  case 8: id(active_power_L1_sensor)->publish_state(val); break;
                  case 9: id(active_power_export_L1_sensor)->publish_state(val); break;
                  case 10: id(active_power_L2_sensor)->publish_state(val); break;
                  case 11: id(active_power_export_L2_sensor)->publish_state(val); break;
                  case 12: id(active_power_L3_sensor)->publish_state(val); break;
                  case 13: id(active_power_export_L3_sensor)->publish_state(val); break;
                  
                  // Phase-specific reactive power (14-19)
                  case 14: id(reactive_power_import_L1_sensor)->publish_state(val); break;
                  case 15: id(reactive_power_export_L1_sensor)->publish_state(val); break;
                  case 16: id(reactive_power_import_L2_sensor)->publish_state(val); break;
                  case 17: id(reactive_power_export_L2_sensor)->publish_state(val); break;
                  case 18: id(reactive_power_import_L3_sensor)->publish_state(val); break;
                  case 19: id(reactive_power_export_L3_sensor)->publish_state(val); break;
                  
                  // Voltages (20-22)
                  case 20: id(phase_voltage_L1_sensor)->publish_state(val); break;
                  case 21: id(phase_voltage_L2_sensor)->publish_state(val); break;
                  case 22: id(phase_voltage_L3_sensor)->publish_state(val); break;
                  
                  // Currents (23-25)
                  case 23: id(phase_current_L1_sensor)->publish_state(val); break;
                  case 24: id(phase_current_L2_sensor)->publish_state(val); break;
                  case 25: id(phase_current_L3_sensor)->publish_state(val); break;
                  
                  // Additional technical values (26-29)
                  case 26: id(frequency_sensor)->publish_state(val); break;
                  case 27: id(signed_current_L1_sensor)->publish_state(val); break;
                  case 28: id(signed_current_L2_sensor)->publish_state(val); break;
                  case 29: id(signed_current_L3_sensor)->publish_state(val); break;
                  
                  // Tariff energies (30-33)
                  case 30: id(energy_import_tariff1_sensor)->publish_state(val); break;
                  case 31: id(energy_import_tariff2_sensor)->publish_state(val); break;
                  case 32: id(energy_export_tariff1_sensor)->publish_state(val); break;
                  case 33: id(energy_export_tariff2_sensor)->publish_state(val); break;
                  
                  // Maximum power demand (34-35)
                  case 34: id(max_power_demand1_sensor)->publish_state(val); break;
                  case 35: id(max_power_demand2_sensor)->publish_state(val); break;
                  
                  // Error statistics (36-37)
                  case 36: id(electricity_failures_sensor)->publish_state(val); break;
                  case 37: id(long_electricity_failures_sensor)->publish_state(val); break;
                  
                  // Indexes 38-42 are text sensors, handled separately
                }
                last_values[index] = val;
                last_updates[index] = now;
              }
            };
            
            auto find_obis_index = [&](const char* code) -> int {
              for (int i = 0; i < table_size; i++) {
                if (strcmp(code, obis_table[i].code) == 0) {
                  return obis_table[i].index;
                }
              }
              return -1;
            };
            
            // Main parsing logic (optimized C-string handling)
            const char* data = reinterpret_cast<const char*>(bytes.data());
            size_t len = bytes.size();
            const char* ptr = data;
            const char* end = data + len;
            
            while (ptr < end) {
              const char* open = strchr(ptr, '(');
              if (!open || open >= end) break;
              
              const char* code_start = ptr;
              const char* newline = strrchr(ptr, '\n');
              if (newline && newline < open) code_start = newline + 1;
              
              size_t code_len = open - code_start;
              if (code_len >= 16) { ptr = open + 1; continue; }
              
              char code_buf[16];
              strncpy(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 = strchr(open, ')');
              if (!close || close >= end) break;
              
              size_t value_len = close - open - 1;
              if (value_len >= 32) { ptr = close + 1; continue; }
              
              char value_buf[32];
              strncpy(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 = val_start + strlen(val_start) - 1;
              while (val_end > val_start && isspace(*val_end)) *val_end-- = '\0';
              
              // Text sensor handling (SESKO standard compliant)
              if (strcmp(code_ptr, "0-0:1.0.0") == 0) {
                id(date_time_stamp_sensor)->publish_state(val_start);
              } else if (strcmp(code_ptr, "0-0:96.1.0") == 0) {
                id(meter_serial_sensor)->publish_state(val_start);
              } else if (strcmp(code_ptr, "0-0:96.1.1") == 0) {
                id(meter_type_sensor)->publish_state(val_start);
              } else if (strcmp(code_ptr, "0-0:42.0.0") == 0) {
                id(meter_id_sensor)->publish_state(val_start);
              } else if (strcmp(code_ptr, "1-0:0.2.0") == 0) {
                id(firmware_version_sensor)->publish_state(val_start);
              } else if (strcmp(code_ptr, "0-0:96.13.0") == 0) {
                id(consumer_message_sensor)->publish_state(val_start);
              } else {
                int index = find_obis_index(code_ptr);
                if (index >= 0 && index <= 37) { // Only numeric values (0-37)
                  float val = strtof(val_start, nullptr);
                  update_sensor_by_index(index, val);
                }
              }
              
              ptr = close + 1;
            }

sensor:
  # ==========================================
  # BASIC ENERGIES (SESKO SK 13-1:2022 Table 1)
  # ==========================================
  
  - platform: template
    name: "Active energy import"              # SESKO SK 13-1:2022 Table 1 (1-0:1.8.0)
    id: active_energy_import_sensor
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    accuracy_decimals: 3
    icon: mdi:home-import-outline

  - platform: template
    name: "Active energy export"              # SESKO SK 13-1:2022 Table 1 (1-0:2.8.0)
    id: active_energy_export_sensor
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    accuracy_decimals: 3
    icon: mdi:home-export-outline

  - platform: template
    name: "Reactive energy import"            # SESKO SK 13-1:2022 (1-0:3.8.0)
    id: reactive_energy_import_sensor
    unit_of_measurement: "kvarh"
    state_class: total_increasing
    accuracy_decimals: 3
    icon: mdi:sine-wave

  - platform: template
    name: "Reactive energy export"            # SESKO SK 13-1:2022 (1-0:4.8.0)
    id: reactive_energy_export_sensor
    unit_of_measurement: "kvarh"
    state_class: total_increasing
    accuracy_decimals: 3
    icon: mdi:sine-wave

  # ========================================
  # INSTANTANEOUS POWER TOTALS (SESKO compliant)
  # ========================================

  - platform: template
    name: "Active power import"               # SESKO SK 13-1:2022 Table 1 (1-0:1.7.0)
    id: active_power_import_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 2
    icon: mdi:flash

  - platform: template
    name: "Active power export"               # SESKO SK 13-1:2022 Table 1 (1-0:2.7.0)
    id: active_power_export_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 2
    icon: mdi:flash-outline

  - platform: template
    name: "Reactive power import"             # SESKO SK 13-1:2022 Table 1 (1-0:3.7.0)
    id: reactive_power_import_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  - platform: template
    name: "Reactive power export"             # SESKO SK 13-1:2022 Table 1 (1-0:4.7.0)
    id: reactive_power_export_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  # ================================================
  # PHASE-SPECIFIC ACTIVE POWER (SESKO ordered)
  # ================================================

  - platform: template
    name: "Active power L1"                   # SESKO SK 13-1:2022 Table 1 (1-0:21.7.0)
    id: active_power_L1_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash

  - platform: template
    name: "Active power export L1"            # SESKO SK 13-1:2022 (1-0:22.7.0)
    id: active_power_export_L1_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash-outline

  - platform: template
    name: "Active power L2"                   # SESKO SK 13-1:2022 Table 1 (1-0:41.7.0)
    id: active_power_L2_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash

  - platform: template
    name: "Active power export L2"            # SESKO SK 13-1:2022 (1-0:42.7.0)
    id: active_power_export_L2_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash-outline

  - platform: template
    name: "Active power L3"                   # SESKO SK 13-1:2022 Table 1 (1-0:61.7.0)
    id: active_power_L3_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash

  - platform: template
    name: "Active power export L3"            # SESKO SK 13-1:2022 (1-0:62.7.0)
    id: active_power_export_L3_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash-outline

  # ===================================================
  # PHASE-SPECIFIC REACTIVE POWER (SESKO Annex 2 s.10)
  # ===================================================

  - platform: template
    name: "Reactive power import L1"          # SESKO SK 13-1:2022 Annex 2 s.10 (1-0:23.7.0)
    id: reactive_power_import_L1_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  - platform: template
    name: "Reactive power export L1"          # SESKO SK 13-1:2022 Annex 2 s.10 (1-0:24.7.0)
    id: reactive_power_export_L1_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  - platform: template
    name: "Reactive power import L2"          # SESKO SK 13-1:2022 Annex 2 s.10 (1-0:43.7.0)
    id: reactive_power_import_L2_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  - platform: template
    name: "Reactive power export L2"          # SESKO SK 13-1:2022 Annex 2 s.10 (1-0:44.7.0)
    id: reactive_power_export_L2_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  - platform: template
    name: "Reactive power import L3"          # SESKO SK 13-1:2022 Annex 2 s.10 (1-0:63.7.0)
    id: reactive_power_import_L3_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  - platform: template
    name: "Reactive power export L3"          # SESKO SK 13-1:2022 Annex 2 s.10 (1-0:64.7.0)
    id: reactive_power_export_L3_sensor
    unit_of_measurement: "kvar"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  # ===============================================
  # VOLTAGES (SESKO SK 13-1:2022 Annex 2 s.11)
  # ===============================================

  - platform: template
    name: "Phase voltage L1"                  # SESKO SK 13-1:2022 Annex 2 s.11 (1-0:32.7.0)
    id: phase_voltage_L1_sensor
    unit_of_measurement: "V"
    device_class: voltage
    accuracy_decimals: 1
    icon: mdi:lightning-bolt

  - platform: template
    name: "Phase voltage L2"                  # SESKO SK 13-1:2022 Annex 2 s.11 (1-0:52.7.0)
    id: phase_voltage_L2_sensor
    unit_of_measurement: "V"
    device_class: voltage
    accuracy_decimals: 1
    icon: mdi:lightning-bolt

  - platform: template
    name: "Phase voltage L3"                  # SESKO SK 13-1:2022 Annex 2 s.11 (1-0:72.7.0)
    id: phase_voltage_L3_sensor
    unit_of_measurement: "V"
    device_class: voltage
    accuracy_decimals: 1
    icon: mdi:lightning-bolt

  # =========================================
  # CURRENTS (SESKO SK 13-1:2022 Annex 2 s.11)
  # =========================================

  - platform: template
    name: "Phase current L1"                  # SESKO SK 13-1:2022 Annex 2 s.11 (1-0:31.7.0)
    id: phase_current_L1_sensor
    unit_of_measurement: "A"
    device_class: current
    accuracy_decimals: 2
    icon: mdi:current-ac

  - platform: template
    name: "Phase current L2"                  # SESKO SK 13-1:2022 Annex 2 s.11 (1-0:51.7.0)
    id: phase_current_L2_sensor
    unit_of_measurement: "A"
    device_class: current
    accuracy_decimals: 2
    icon: mdi:current-ac

  - platform: template
    name: "Phase current L3"                  # SESKO SK 13-1:2022 Annex 2 s.11 (1-0:71.7.0)
    id: phase_current_L3_sensor
    unit_of_measurement: "A"
    device_class: current
    accuracy_decimals: 2
    icon: mdi:current-ac

  # ================================
  # ADDITIONAL TECHNICAL VALUES
  # ================================

  - platform: template
    name: "Frequency"                         # Network frequency (1-0:14.7.0)
    id: frequency_sensor
    unit_of_measurement: "Hz"
    accuracy_decimals: 2
    icon: mdi:sine-wave

  - platform: template
    name: "Signed current L1"                 # Signed current L1 (1-0:13.7.0)
    id: signed_current_L1_sensor
    unit_of_measurement: "A"
    device_class: current
    accuracy_decimals: 2
    icon: mdi:current-ac

  - platform: template
    name: "Signed current L2"                 # Signed current L2 (1-0:33.7.0)
    id: signed_current_L2_sensor
    unit_of_measurement: "A"
    device_class: current
    accuracy_decimals: 2
    icon: mdi:current-ac

  - platform: template
    name: "Signed current L3"                 # Signed current L3 (1-0:53.7.0)
    id: signed_current_L3_sensor
    unit_of_measurement: "A"
    device_class: current
    accuracy_decimals: 2
    icon: mdi:current-ac

  # =========================
  # TARIFF ENERGIES (SESKO)
  # =========================

  - platform: template
    name: "Energy import tariff 1"            # Tariff 1 import (1-0:1.8.1)
    id: energy_import_tariff1_sensor
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    accuracy_decimals: 3
    icon: mdi:counter

  - platform: template
    name: "Energy import tariff 2"            # Tariff 2 import (1-0:1.8.2)
    id: energy_import_tariff2_sensor
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    accuracy_decimals: 3
    icon: mdi:counter

  - platform: template
    name: "Energy export tariff 1"            # Tariff 1 export (1-0:2.8.1)
    id: energy_export_tariff1_sensor
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    accuracy_decimals: 3
    icon: mdi:counter

  - platform: template
    name: "Energy export tariff 2"            # Tariff 2 export (1-0:2.8.2)
    id: energy_export_tariff2_sensor
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    accuracy_decimals: 3
    icon: mdi:counter

  # ========================
  # MAXIMUM POWER DEMAND
  # ========================

  - platform: template
    name: "Max power demand period 1"         # Maximum power demand period 1 (1-0:1.6.0)
    id: max_power_demand1_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash-triangle

  - platform: template
    name: "Max power demand period 2"         # Maximum power demand period 2 (1-0:2.6.0)
    id: max_power_demand2_sensor
    unit_of_measurement: "kW"
    device_class: power
    accuracy_decimals: 3
    icon: mdi:flash-triangle

  # ============================
  # ERROR STATISTICS (SESKO compliant)
  # ============================

  - platform: template
    name: "Electricity failures"              # Short electricity failures (0-0:96.7.21)
    id: electricity_failures_sensor
    icon: mdi:alert
    accuracy_decimals: 0

  - platform: template
    name: "Long electricity failures"         # Long electricity failures (0-0:96.7.19)
    id: long_electricity_failures_sensor
    icon: mdi:alert-outline
    accuracy_decimals: 0

  # ==============================
  # UART MONITORING (diagnostics)
  # ==============================

  - platform: template
    name: "UART Timeout Count"
    id: uart_timeout_sensor
    icon: mdi:timer-alert
    accuracy_decimals: 0
    
  - platform: template
    name: "UART Success Rate"
    id: uart_success_rate_sensor
    unit_of_measurement: "%"
    icon: mdi:chart-line
    accuracy_decimals: 1
    
  - platform: template
    name: "UART Packets Total"
    id: uart_packets_sensor
    icon: mdi:counter
    accuracy_decimals: 0
    
  - platform: template
    name: "Minutes Since Last Timeout"
    id: minutes_since_timeout_sensor
    unit_of_measurement: "min"
    icon: mdi:clock-outline
    accuracy_decimals: 0

  # ====================
  # WIFI MONITORING
  # ====================

  - platform: wifi_signal
    name: "WiFi Signal Strength"
    update_interval: 60s

text_sensor:
  # =======================================================
  # TIMESTAMP (SESKO SK 13-1:2022 Annex 2 s.10 0-0:1.0.0)
  # =======================================================
  
  - platform: template
    name: "Date and time stamp"               # SESKO SK 13-1:2022 Annex 2 s.10 (0-0:1.0.0)
    id: date_time_stamp_sensor
    icon: mdi:clock-time-eight
    
  # =========================================
  # METER IDENTIFICATION (SESKO compliant)
  # =========================================
  
  - platform: template
    name: "Meter serial number"               # Meter serial number (0-0:96.1.0)
    id: meter_serial_sensor
    icon: mdi:identifier
    
  - platform: template
    name: "Meter type"                        # Meter type (0-0:96.1.1)
    id: meter_type_sensor
    icon: mdi:meter-electric
    
  - platform: template
    name: "Meter ID"                          # Meter ID (0-0:42.0.0)
    id: meter_id_sensor
    icon: mdi:card-account-details
    
  - platform: template
    name: "Firmware version"                  # Firmware version (1-0:0.2.0)
    id: firmware_version_sensor
    icon: mdi:chip

  - platform: template
    name: "Consumer message"                  # Consumer message (0-0:96.13.0)
    id: consumer_message_sensor
    icon: mdi:message-text
    
  # =====================
  # WIFI INFORMATION
  # =====================
  
  - platform: wifi_info
    ip_address:
      name: "SlimmeLezer IP Address"
    ssid:
      name: "SlimmeLezer WiFi Network"
    bssid:
      name: "SlimmeLezer WiFi BSSID"
      
  # =================
  # VERSION INFORMATION
  # =================
  
  - platform: version
    name: "ESPHome Version"
    hide_timestamp: true

#############################################################################
#                              END OF CONFIG                               #
#############################################################################

Awesome job @nikop :clap:

I’ve paid @glmnet to develop the DSMR component as it wasn’t supported at that time (that was in 2021). Software isn’t my strongest point and I’d like to stay far away from that :sweat_smile: Once developed I’ve given it to esphome with my wish to make it public available for everyone.

I had the chance to test it on Luxembourg meters which have encrypted data and luckily my southern neighbors had my SlimmeLezer too and I knew it worked there too!

Unfortunately I don’t have the luxury to test/support it in all countries. Seeing the amount I’ve send I know it must work.

Work like this from you is wat I need, more specifically, what the end user needs.

I haven’t looked into your code yet, but is this something an “inexperienced” end user can do? I’m trying to offer regular updates but that’s based on default installation.

Looking forward to do more with this and see if I can offer updates per country specific.

2 Likes

I’ll take the opportunity to quickly add some feedback here
I own a repository where updates for this component (Pull Requests) are often sent. I do not have the time to maintain this, this requires code quality assurance and of course making sure updates are non-breaking and non-conflicting with other user interests, i.e. this requires time for maintenance.
If anyone is willing to maintain the project just ping me here

2 Likes

There have too many years when I’ve been coding for living, so I prolly are not right person to maintain any code as I would need to vibe-code it. :slight_smile:

But, I’ve been thinking an idea:

The Idea: A More Flexible DSMR Component! :bulb:

What if we could extend the existing DSMR component to allow users to define their own custom OBIS codes and their corresponding sensors directly in their ESPHome YAML configuration?

This would mean:

  • :earth_africa: Broader international compatibility: Easily adapt the component to local P1 port specifics.
  • :bar_chart: Access to all data your meter provides: Read any OBIS code your meter offers, not just the pre-defined ones.
  • :jigsaw: Increased user flexibility: Tailor the component precisely to your needs.
  • :handshake: Community-driven definitions: Users could share YAML snippets for specific meter models or regions.

How It Could Work (A High-Level Glimpse):

  • YAML Configuration: Users could add a new section to their dsmr: configuration, for example, like this:

    dsmr:
      uart_id: my_uart_bus
      # ...other existing DSMR settings...
      custom_obis_sensors:
        - code: "1-0:99.97.0"  # Your custom OBIS code
          name: "Meter's Special Value"
          type: sensor        # Type: 'sensor' for numerical, 'text_sensor' for text
          unit_of_measurement: "kWh" # Or other unit
          accuracy_decimals: 2
          device_class: energy
          state_class: total_increasing
          icon: "mdi:flash"
        - code: "0-0:96.1.10" # Another example
          name: "Manufacturer Specific ID"
          type: text_sensor
          icon: "mdi:information-outline"
        # ... add other custom codes as needed
    

And just for fun side of things, I let Gemini 2.5 to research original DSMR-component and figure out how this migth be done.

TL;DR of document:
This project successfully extended ESPHome’s dsmr component to support user-defined OBIS codes from smart meters. Users can now add custom OBIS codes in their YAML configuration, allowing the component to dynamically create sensors for non-standard or country-specific meter data. The changes improve flexibility, support both numeric and text values, and include debounce logic for efficient updates. The full report details the implementation, testing, and provides example configurations.

Aaand I just YOLO-coded some kind of local working version of dsmr_custom which takes obis codes from the yaml, but I think keeps original dsmr-functionality if one is not using custom codes. Letting it run without any rail guards for the night :sweat_smile: . snippet from esphome-yaml:

# Globaalit muuttujat salauksenpurkuavaimelle
globals:
  - id: has_key
    type: bool
    restore_value: yes
    initial_value: "false"
  - id: stored_decryption_key
    type: char[33] # 🔥 MUUTETTU KOKO, jotta 32 merkkiä + null-terminaattori mahtuu
    restore_value: yes
    # initial_value poistettu, oletetaan nolla-alustus tai API/on_boot asettaa sen

# UART-väylän perusmäärittely
uart:
  id: uart_bus
  baud_rate: 115200
  rx_pin: D7
  rx_buffer_size: 1024

# --- Määritellään C++ DSMR-komponentin käyttö ---
external_components:
  - source:
      type: local
      # Polku kansioon, joka sisältää dsmr_custom/__init__.py, sensor.py, text_sensor.py, dsmr.h, dsmr.cpp
      path: custom_components/ 
    components: [ dsmr_custom ] # Kerrotaan ESPHomelle, että dsmr_custom-komponentti löytyy täältä


# --- UUSI DSMR CUSTOM KOMPONENTIN KONFIGURAATIO ---
dsmr_custom: # Käytetään uutta komponentin nimeä
  id: dsmr_hub
  uart_id: uart_bus
  max_telegram_length: 1700 
  crc_check: true
  receive_timeout: "600ms"
  # Salauksenpurkuavain asetetaan on_bootissa tai API-palvelulla.
  # Jos haluat staattisen avaimen YAML:sta, poista kommentti ja aseta avain:
  # decryption_key: "YOUR_STATIC_32_HEX_CHAR_KEY_HERE" 
  # request_pin: D5 
  # request_interval: "10s"

  custom_obis_sensors:
    # Tähän listataan kaikki SESKO-koodisi ja niiden sensorimääritykset
    # Kopioitu ja muokattu edellisestä vastauksestasi:
    - code: "1-0:1.8.0"
      name: "Active energy import"
      type: sensor
      unit_of_measurement: "kWh"
      state_class: total_increasing
      device_class: energy
      accuracy_decimals: 3
      icon: mdi:home-import-outline
    - code: "1-0:2.8.0"
      name: "Active energy export"
      type: sensor
      unit_of_measurement: "kWh"
      state_class: total_increasing
      device_class: energy
      accuracy_decimals: 3
      icon: mdi:home-export-outline
    - code: "1-0:3.8.0"
      name: "Reactive energy import"
      type: sensor
      unit_of_measurement: "kvarh"
      state_class: total_increasing
      accuracy_decimals: 3
      icon: mdi:sine-wave
    - code: "1-0:4.8.0"
      name: "Reactive energy export"
      type: sensor
      unit_of_measurement: "kvarh"
      state_class: total_increasing
      accuracy_decimals: 3
      icon: mdi:sine-wave
    - code: "1-0:1.7.0"
      name: "Active power import"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:flash
    - code: "1-0:2.7.0"
      name: "Active power export"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:flash-outline
    - code: "1-0:3.7.0"
      name: "Reactive power import"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:4.7.0"
      name: "Reactive power export"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:21.7.0"
      name: "Active power L1"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash
    - code: "1-0:22.7.0"
      name: "Active power export L1"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash-outline
    - code: "1-0:41.7.0"
      name: "Active power L2"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash
    - code: "1-0:42.7.0"
      name: "Active power export L2"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash-outline
    - code: "1-0:61.7.0"
      name: "Active power L3"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash
    - code: "1-0:62.7.0"
      name: "Active power export L3"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash-outline
    - code: "1-0:23.7.0"
      name: "Reactive power import L1"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:24.7.0"
      name: "Reactive power export L1"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:43.7.0"
      name: "Reactive power import L2"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:44.7.0"
      name: "Reactive power export L2"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:63.7.0"
      name: "Reactive power import L3"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:64.7.0"
      name: "Reactive power export L3"
      type: sensor
      unit_of_measurement: "kvar"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:32.7.0"
      name: "Phase voltage L1"
      type: sensor
      unit_of_measurement: "V"
      device_class: voltage
      accuracy_decimals: 1
      state_class: measurement
      icon: mdi:lightning-bolt
    - code: "1-0:52.7.0"
      name: "Phase voltage L2"
      type: sensor
      unit_of_measurement: "V"
      device_class: voltage
      accuracy_decimals: 1
      state_class: measurement
      icon: mdi:lightning-bolt
    - code: "1-0:72.7.0"
      name: "Phase voltage L3"
      type: sensor
      unit_of_measurement: "V"
      device_class: voltage
      accuracy_decimals: 1
      state_class: measurement
      icon: mdi:lightning-bolt
    - code: "1-0:31.7.0"
      name: "Phase current L1"
      type: sensor
      unit_of_measurement: "A"
      device_class: current
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:current-ac
    - code: "1-0:51.7.0"
      name: "Phase current L2"
      type: sensor
      unit_of_measurement: "A"
      device_class: current
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:current-ac
    - code: "1-0:71.7.0"
      name: "Phase current L3"
      type: sensor
      unit_of_measurement: "A"
      device_class: current
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:current-ac
    - code: "1-0:14.7.0"
      name: "Frequency"
      type: sensor
      unit_of_measurement: "Hz"
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:sine-wave
    - code: "1-0:13.7.0"
      name: "Signed current L1"
      type: sensor
      unit_of_measurement: "A"
      device_class: current
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:current-ac
    - code: "1-0:33.7.0"
      name: "Signed current L2"
      type: sensor
      unit_of_measurement: "A"
      device_class: current
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:current-ac
    - code: "1-0:53.7.0"
      name: "Signed current L3"
      type: sensor
      unit_of_measurement: "A"
      device_class: current
      accuracy_decimals: 2
      state_class: measurement
      icon: mdi:current-ac
    - code: "1-0:1.8.1"
      name: "Energy import tariff 1"
      type: sensor
      unit_of_measurement: "kWh"
      state_class: total_increasing
      device_class: energy
      accuracy_decimals: 3
      icon: mdi:counter
    - code: "1-0:1.8.2"
      name: "Energy import tariff 2"
      type: sensor
      unit_of_measurement: "kWh"
      state_class: total_increasing
      device_class: energy
      accuracy_decimals: 3
      icon: mdi:counter
    - code: "1-0:2.8.1"
      name: "Energy export tariff 1"
      type: sensor
      unit_of_measurement: "kWh"
      state_class: total_increasing
      device_class: energy
      accuracy_decimals: 3
      icon: mdi:counter
    - code: "1-0:2.8.2"
      name: "Energy export tariff 2"
      type: sensor
      unit_of_measurement: "kWh"
      state_class: total_increasing
      device_class: energy
      accuracy_decimals: 3
      icon: mdi:counter
    - code: "1-0:1.6.0"
      name: "Max power demand period 1"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash-triangle
    - code: "1-0:2.6.0"
      name: "Max power demand period 2"
      type: sensor
      unit_of_measurement: "kW"
      device_class: power
      accuracy_decimals: 3
      state_class: measurement
      icon: mdi:flash-triangle
    - code: "0-0:96.7.21"
      name: "Electricity failures"
      type: sensor
      icon: mdi:alert
      accuracy_decimals: 0
    - code: "0-0:96.7.19"
      name: "Long electricity failures"
      type: sensor
      icon: mdi:alert-outline
      accuracy_decimals: 0
    - code: "0-0:1.0.0"
      name: "Date and time stamp"
      type: text_sensor
      icon: mdi:clock-time-eight
    - code: "0-0:96.1.0"
      name: "Meter serial number"
      type: text_sensor
      icon: mdi:identifier
    - code: "0-0:96.1.1"
      name: "Meter type"
      type: text_sensor
      icon: mdi:meter-electric
    - code: "0-0:42.0.0"
      name: "Meter ID"
      type: text_sensor
      icon: mdi:card-account-details
    - code: "1-0:0.2.0"
      name: "Firmware version"
      type: text_sensor
      icon: mdi:chip
    - code: "0-0:96.13.0"
      name: "Consumer message"
      type: text_sensor
      icon: mdi:message-text

files in custom component, real frankstein of files from everywhere as I did not find all in esphome git:

Thank you to all who gave me boost to make a c++ component! Discussion continues on