Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Alfen + SlimmeLezer Pro: external Modbus TCP meter for Active Load Balancing

An ESPHome component that turns a Zuidwijk SlimmeLezer Pro (P1 smart meter reader, ESP32-C3-based) into a Modbus TCP meter that an Alfen Eve Single Plus charging station can read for Active Load Balancing (ALB) — no separate physical meter or gateway device needed.

Why this exists

Alfen's ALB feature can read current/power from an external Modbus TCP meter to know how much headroom is left before allowing the car to draw more current. The obvious approach — emulating a real commercial meter's 32-bit register format — doesn't work reliably against Alfen's own Modbus client. This component works around two real bugs found on Alfen's side (32-bit values get misassembled; a field mapped to register 0 is silently skipped) by serving plain, single-register SIGNED16 values instead.

Requirements

  • An ESP32-based Zuidwijk SlimmeLezer — Pro or Mini (both are ESP32-C3 running ESP-IDF). The example config in this repo targets the Pro's Ethernet setup; the Mini should work too but needs its own WiFi-only config, not included here. This will not work on the original/Classic SlimmeLezer (ESP8266-based): the component talks directly to raw POSIX sockets (socket()/bind()/accept()/...), which only exist because ESP-IDF provides them — and ESP-IDF is Espressif's SDK for the ESP32 family only, it was never built for the ESP8266.
  • Ethernet connection recommended (more reliable for something feeding a charger's load-balancing input than WiFi, though WiFi should work too).
  • An Alfen charging station with Active Load Balancing licensed and enabled, configured for an external Modbus TCP/IP meter (Data Source = "Meter", not "Energy Management System").

Quick start

  1. Copy example/slimmelezer_pro_alfen_meter.yaml and example/secrets.yaml.examplesecrets.yaml into your ESPHome config folder, filling in your own WiFi credentials.
  2. In the YAML's external_components: block, replace <your-github-username> with wherever you've actually published this.
  3. Flash it (see ESPHome's docs if you're new to this).
  4. Configure Alfen — see the register table below.
  5. Verify — see Testing, below. Don't skip this; confirm the firmware is serving correct data before trusting what Alfen's screen shows.

Alfen-side configuration

ACE Service Installer → Load Balancing → Active balancing → Data Source = Meter → Protocol = Modbus TCP/IP → register mapping dialog → select "generic modbus meter" → enter exactly this table:

Field Register Data type Scale
Current L1 1 SIGNED16 x0.01
Current L2 2 SIGNED16 x0.01
Current L3 3 SIGNED16 x0.01
Real Power L1 4 SIGNED16 x1
Real Power L2 5 SIGNED16 x1
Real Power L3 6 SIGNED16 x1

Then enter the SlimmeLezer's IP address and port 502 as the meter address.

Enter one field at a time and save between each, if the installer allows it. Values outside the expected range have been observed to require an ACE Service Installer reboot to recover from.

Configuration reference

simple_meter: component

simple_meter:
  current_l1: current_l1      # required -- ESPHome sensor ids
  current_l2: current_l2
  current_l3: current_l3
  power_l1: net_power_l1      # optional
  power_l2: net_power_l2
  power_l3: net_power_l3
  port: 502
  current_scale: 100.0
  power_scale: 1.0
  data_timeout: 30s
Option Required Default Meaning
current_l1 / l2 / l3 Yes ESPHome sensor id for each phase's current (amps). Typically the DSMR current_l1/l2/l3 sensors.
power_l1 / l2 / l3 No ESPHome sensor id for each phase's net power (positive = import, negative = export). See "Net power" below — you almost certainly want a computed sensor here, not a raw DSMR one.
port No 502 Modbus TCP listen port. Alfen expects 502; no reason to change this normally.
current_scale No 100.0 Register value = round(amps × current_scale). Default gives a resolution of 0.01 A and a range of ±327.67 A. Must match the reciprocal of whatever scale you enter in Alfen (default → Alfen scale x0.01).
power_scale No 1.0 Register value = round(watts × power_scale). Default gives whole-watt resolution and ±32,767 W range. Must match Alfen's scale setting (default → Alfen scale x1).
data_timeout No 30s If no sensor has produced a valid (non-NaN) reading for this long, the Modbus TCP server stops accepting/serving connections entirely, rather than let Alfen read frozen values. Set to 0s to disable.

Registers served: 0 (always 0, deliberately unused), 1-3 = current L1-L3, 4-6 = power L1-L3. Any other address returns 0. Supports Modbus function codes 03/04 (read), 06/16 (write -- accepted and acknowledged, but ignored; this is a read-only meter).

Net power ("why not just use the raw DSMR sensor")

DSMR gives two always-non-negative fields per phase -- power_delivered (import) and power_returned (export) -- never a signed value directly. The example config computes net power itself:

sensor:
  - platform: template
    id: net_power_l1
    lambda: return id(power_delivered_l1).state - id(power_returned_l1).state;

...and -- importantly -- wires it to recompute immediately when either underlying value changes (via on_value: - component.update: net_power_l1 on both power_delivered_l1 and power_returned_l1), rather than on a fixed timer. This keeps the served power value as fresh as the underlying ~1 Hz DSMR telegram rate, with no added polling lag.

P1 Telegram Age sensor

sensor:
  - platform: template
    name: "P1 Telegram Age"
    id: p1_telegram_age
    unit_of_measurement: "s"
    update_interval: 1s
    lambda: |-
      return (millis() - id(last_telegram_ms)) / 1000.0;

Tracks milliseconds since the last real DSMR telegram was received (a globals.set on the telegram: text sensor's on_value records the timestamp). Expect this to sawtooth between roughly 0 and ~1.2s under normal operation. If it climbs steadily or stops resetting, the P1 connection itself has stalled -- independent of anything Alfen-related.

Testing

Don't rely on Alfen's test screen alone -- verify the firmware independently first, so any mismatch you see later is unambiguous about which side it's on.

1. Check the firmware directly, bypassing Alfen:

python3 -m pip install pymodbus
python3 tools/check_registers.py <device-ip>

Compare the printed values against the SlimmeLezer's own web UI (http://<device-ip>/) at the same moment. Note: DSMR reports current in whole amps only (no decimal resolution in the telegram itself) -- small loads under ~0.5A will genuinely read as 0, which is correct behavior, not a bug.

2. Once the firmware is confirmed correct, check what Alfen actually requests. Set logging to capture every incoming Modbus request:

logger:
  level: VERBOSE
  logs:
    dsmr: DEBUG
    sensor: DEBUG
    text_sensor: DEBUG

(Narrowing dsmr/sensor/text_sensor back to DEBUG keeps their per-telegram/per-sensor noise from burying the sparser simple_meter lines -- and matters given the ESP32's log buffer is only 768 bytes.)

Watch the logs (or esphome logs your-config.yaml) while Alfen polls. You're looking for lines like:

[V][simple_meter]: FC03 start=0x0001 count=6

This tells you, with certainty: which function code Alfen uses, the exact starting address, and how many registers it reads in one request. If a field isn't behaving as expected, check whether its configured register number is actually present in what Alfen requests -- if it's missing entirely (not misread, just absent), that's the register-0-style bug described above, not a data problem.

3. Cross-check the two. Compare the register-check script's output against Alfen's smart meter test screen at the same moment. Matching (accounting for Alfen's own display conventions -- e.g. it shows power in kW, not W) confirms the whole chain end to end.

Revert logger: back to level: DEBUG once done -- VERBOSE has a real performance cost and ESPHome will remind you of this in the logs.

Known limitations

  • SIGNED16 range: current up to ±327.67 A, power up to ±32,767 W per phase at the default scales -- comfortable margin for any residential/ light-commercial setup, but adjust current_scale/power_scale if you need different resolution or Alfen's scale dropdown doesn't offer x0.01/x1.
  • Voltage is not served. Alfen's ALB doesn't request it for current limiting, so it was left out. Straightforward to add if needed elsewhere.
  • Built and tested against one specific Alfen ACE Service Installer / firmware version. Alfen's Modbus client behavior may differ across versions -- if this doesn't work for you, the verbose-logging technique above is the fastest way to see what's actually happening on your setup.
  • The register-mapping dialog has been observed to require an installer reboot after certain out-of-range inputs. Cause not fully isolated.

Credits / prior art

The Modbus TCP server itself -- connection handling, request framing, non-blocking accept loop, and the stale-data watchdog -- is substantially reused, near line-for-line, from RemCom/victron-grid-meter-esphome (built for Victron Cerbo GX EM24 emulation). That project's networking implementation is solid and this one didn't need to reinvent it.

License

MIT -- see LICENSE.

About

ESPHome Modbus TCP meter (SlimmeLezer Pro/Mini) for Alfen Active Load Balancing

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages