Skip to content

0.8.7

Choose a tag to compare

@Jarauvi Jarauvi released this 16 Mar 19:10
abd0fde

Changes Made by @pvandenh

Protocol Discovery

  • Reverse-engineered the 3-phase SEND_DLB packet structure — The 3-phase charger sends a 33-byte response (msg_len=0x21) versus the 1-phase charger's 17-byte response (msg_len=0x11). The extra 16 bytes are exactly 8 additional 16-bit fields: each of the four power values (solar, EV, house, grid) is split into three per-phase readings rather than a single total. Sensor totals are the sum of all three phases. Confirmed from BCP-AT1N-L hardware capture vs Z-Box ground truth:

    Field | Phase 1 | Phase 2 | Phase 3 | Sum ÷100 | Z-Box -- | -- | -- | -- | -- | -- solar_power | 0x006f | 0x0070 | 0x006f | 3.34 kW | 3.31 kW ✅ ev_power | 0x0000 | 0x0000 | 0x0000 | 0.00 kW | 0.00 kW ✅ house_power | 0x0002 | 0x0004 | 0x002a | 0.48 kW | 0.46 kW ✅ grid_power | 0xff93 | 0xff94 | 0xffbb | −2.86 kW | −2.85 kW ✅

New Message Definition

  • SEND_DLB_3P added to SERVER_MESSAGE in const.py — defines all 12 per-phase slice positions (solar_phase1–3, ev_phase1–3, house_phase1–3, grid_phase1–3) with full documentation of the confirmed byte layout and hardware evidence.

Routing Fix

  • get_message_type() in conversions.pymsg_int == 33 with message_type == '7B' now routes to SERVER_MESSAGE.SEND_DLB_3P. Previously this path fell into the generic SEND_DLB branch. The 1-phase path (msg_int == 17) is unchanged.

Decode Logic

  • read_message() in communication.py — new SEND_DLB_3P decode block sums the three phases for each field to produce the solar_power, ev_power, house_power, and grid_power keys the coordinator and sensor entities already expect — no changes required in coordinator.py or sensor.py.
    • Grid phases decoded as signed 16-bit two's complement (negative = exporting to grid), matching the existing 1-phase handling.
    • Sentinel logic (0xFF00+) applied per-phase on solar/EV/house: if any single phase returns a sentinel value the entire field is set to None for that cycle, allowing the coordinator to retain the last valid reading rather than producing a corrupt total.

Implementation Notes

  • No changes to coordinator.py, sensor.py, or any entity files — the fix is entirely in the protocol layer. The coordinator's existing sentinel-handling logic (skip None values, retain last valid) works correctly for 3-phase totals without modification.
  • 1-phase behaviour is completely unaffected. The SEND_DLB path in both conversions.py and communication.py is unchanged.
  • The SEND_DLB_3P sentinel check is conservative: a sentinel on any one phase of a given field (e.g. solar_phase2 = 0xFF10) marks that whole field None rather than attempting a partial sum. This matches the 1-phase policy and avoids underreporting during DLB module state transitions.

Testing

  • Hardware: Beny Charger BCP-AT1N-L (3-phase, firmware 1.27) with Solar DLB module
  • Protocol verification: PCAPdroid UDP capture on port 3333, decoded byte-for-byte against Z-Box app readings. Two consecutive packets verified; both match ground truth to within 0.05 kW on all four fields.
  • 1-phase regression: 1-phase SEND_DLB routing and decode verified unchanged against the original DLB.txt capture (solar=3.05 kW, house=1.95 kW, grid=−1.10 kW, EV=0.00 kW — all exact matches).

Breaking Changes

None. 1-phase chargers are entirely unaffected. 3-phase charger users will see their DLB sensors change from incorrect values to correct ones — this is the intended fix.