Skip to content

EVC: fix segmented-register reads (vendor-confirmed); firmware-version gating; doc-aligned entities - #2275

Draft
Bl00d-B0b wants to merge 2 commits into
wills106:mainfrom
Bl00d-B0b:fix/evc-max-current-documented-register
Draft

EVC: fix segmented-register reads (vendor-confirmed); firmware-version gating; doc-aligned entities#2275
Bl00d-B0b wants to merge 2 commits into
wills106:mainfrom
Bl00d-B0b:fix/evc-max-current-documented-register

Conversation

@Bl00d-B0b

@Bl00d-B0b Bl00d-B0b commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2279 (sub-device infrastructure), which the EV Parallel sub-device uses. Review the last commit only — the diff includes the parent until it merges.

SolaX support (ticket 703158, replying to my bug report about MaxChargeCurrent reading 0) disclosed that the EVC holding registers are internally segmented: 0x600-0x650 is one segment and 0x660-0x66F another (used for internal master-slave parallel operation). A function 0x03 read spanning the boundary silently returns 0 for second-segment registers.

Reproduced on an X3-EVC-11kW Gen1 (serial C31103I9612443, ARM V1.18):

Read Value at 0x668
single register 0x668 1600 (16.00 A)
0x660 count 16 (within segment) 1600
0x640 count 48 (crosses boundary) 0
0x64C count 32 (crosses boundary) 0

Everything below was verified on that charger over Modbus TCP, and the register semantics were confirmed with SolaX support.

Segmentation fix

  • newblock=True on the Max Charge Current readback pins the read-block boundary at 0x668, so no read can cross the segment boundary.
  • block_size returns from 32 to the default 100 - the 32 was a blind workaround for this same symptom (fix(solax): unify firmware version display and key naming across all inverter types #2064), added before the segmentation was known. Both resulting holding blocks and the input blocks were verified against single-register reads.

Firmware-version gating

The plugin now reads its firmware version once at startup (input 0x25, e.g. V1.18 -> 118) and publishes it as hub.modbus_protocol_version, activating the existing modbus_min/modbus_max machinery for EV chargers. Thresholds come from the protocol document's revision history and its protocol-to-firmware matching table.

Every register that moved across firmware versions is now one entity identity whose backing register follows the firmware - the Alt-suffixed duplicates are gone:

Entity Legacy register Current register
Charge Power / L1/L2/L3 0x08-0x0A (<= V1.11) 0x100-0x102 (>= V1.12)
Run Mode 0x1D (<= V1.11) 0x106 (>= V1.12)
Charge Phase (select) 0x625 (<= V1.10) 0x63B (>= V1.11)
Charge Mode 0x104 sensor (V1.12-V1.13) 0x641 select (>= V1.14); 0x669 select on GEN2

Other dated entities (control_command, charge_current, the boost family, modbus_address, the 0x100 input block) carry matching modbus_min gates. Unknown firmware falls back to the legacy definitions.

Max charge current

Support suggested using the documented 0x64F (limit_current_allmode) for the maximum current, and clarified its behaviour: 0x64F is the Modbus-dedicated current setting - writes only take effect while a vehicle is connected, and it reverts to the charger's rated maximum when the gun is unplugged, whereas the value written from SolaXCloud is the persistent setpoint in 0x668. (Our earlier write tests to 0x64F appeared to be ignored because they were all performed with no vehicle connected.) Support also confirmed that the charger caps the actual charging current at its rated value regardless of what is written to 0x668.

Accordingly:

  • Max Charge Current stays on 0x668, the persistent setpoint SolaXCloud itself writes;
  • a new optional max_key field on number descriptions dynamically overrides native_max_value from a sensor value. It is fed by the charger's rated current, derived from TypePower (0x21) and TypePhase (0x22) - 11 kW three-phase gives 16 A - so the slider ceiling matches the hardware and never follows a dynamic setpoint. It falls back to the static maximum if those registers cannot be read.

Parallel entities

SolaX support provided the Gen1 meaning of 0x0642, which the document still describes as an on/off enable: 0 = Master, 1 = Slave, 2 = Parallel operation with Gen2 chargers. It is exposed as a **Parallel Role is a read-only diagnostic sensor: live testing showed writes are accepted but an internal sync re-asserts the stored value after 10–15 seconds, so a writable control would silently revert.

Both appear only on chargers that actually support parallel operation: the type detection probes 0x0107 at startup and sets a PARALLEL_TYPE feature flag, the same pattern the plugin already uses for OCPP detection via 0x0023.

Other doc-based entities and alignments

New (disabled by default unless noted): Unbalanced Power 0x63C + Unbalanced Switch 0x63D (X3, >= V1.11), Mode Button 0x63E and Min Charge Current 0x63F (>= V1.12), Datahub Charge Power 0x643 and Green 30s Delay switch 0x644 (>= V1.14), and the string diagnostics Serial Number 0x600, WiFi S/N 0x607, Customized S/N 0x629 (standard REGISTER_STR sensors; the startup serial read used for model detection is unchanged).

Alignments: Overload Limit renamed Overvoltage Limit (doc: OverVoltSet, 1 V); Charger Type name unified across GEN1/GEN2 (options stay per-generation); RSSI renamed WiFi RSSI; per-phase Charge Current sensors and Datahub Charge Current consistently disabled by default; charge_added_cum second definition narrowed from GEN2 | GEN3 | GEN4 to GEN2 (no GEN3/GEN4 EVC exists or is detected); the X1 measured-current diagnostic (input 0x4) renamed key charge_current -> charge_current_measured, which previously collided with the setpoint readback and made the Charge Current number flicker between setpoint and measurement.

Input 0x2D0x30 (Lock State, Main Breaker Limit State, Random Delay State, Ban State) are gated to second-generation chargers: SolaX support confirmed these registers do not exist on Gen1 and will be removed from the Gen1 protocol document.

Platform fixes

Modbus switches and numbers now publish the accepted value locally after a successful write, instead of waiting for the next readback poll. Without this the entity keeps reporting the previous value until the register is polled again, which shows up as a switch flipping back or a slider snapping back for one cycle.

@Bl00d-B0b Bl00d-B0b changed the title Fix EVC Max Charge Current: use documented register 0x64F (vendor-confirmed segmentation) Fix EVC Max Charge Current bulk-read zeros: pin read block at vendor-confirmed segment boundary 0x660 Aug 11, 2026
@Bl00d-B0b
Bl00d-B0b force-pushed the fix/evc-max-current-documented-register branch 3 times, most recently from e2f9c3b to 182894c Compare August 11, 2026 07:26
@Bl00d-B0b Bl00d-B0b changed the title Fix EVC Max Charge Current bulk-read zeros: pin read block at vendor-confirmed segment boundary 0x660 Fix EVC Max Charge Current bulk-read zeros (vendor-confirmed register segmentation); cap slider from device limit 0x64F Aug 11, 2026
@Bl00d-B0b
Bl00d-B0b force-pushed the fix/evc-max-current-documented-register branch 25 times, most recently from 9252618 to 7ad6795 Compare August 11, 2026 13:28
@Bl00d-B0b
Bl00d-B0b force-pushed the fix/evc-max-current-documented-register branch 2 times, most recently from 017344a to 8b4a6fa Compare August 12, 2026 11:49
@Bl00d-B0b
Bl00d-B0b marked this pull request as ready for review August 12, 2026 14:23
@Bl00d-B0b
Bl00d-B0b force-pushed the fix/evc-max-current-documented-register branch from 8b4a6fa to 5c697da Compare August 13, 2026 10:05
@Bl00d-B0b
Bl00d-B0b marked this pull request as draft August 13, 2026 10:12
@Bl00d-B0b
Bl00d-B0b force-pushed the fix/evc-max-current-documented-register branch 7 times, most recently from ebdcf9f to a9d72f3 Compare August 13, 2026 11:16
@Bl00d-B0b
Bl00d-B0b force-pushed the fix/evc-max-current-documented-register branch 15 times, most recently from 83107eb to 6ff3b77 Compare August 13, 2026 14:19
…ptions dialog

Energy Dashboard: the refresh only marked sensors inactive when a feature
switch was turned off - the entity stayed alive and its registry entry was
never removed, so the device accumulated sensors stuck at unavailable
(grid-to-battery, home-consumption and PV-variant sensors). They are now
removed and their registry entries purged.

Generic sub-device infrastructure, used by follow-up PRs that move the
EPS, Parallel, External Generator and Dry Contact families into their own
devices: entity descriptions gain device_group (assigns the entity to a
named sub-device linked to the inverter with via_device, the same pattern
as the battery pack devices) and active_when ({sensor_key: allowed values}
evaluated against polled hub.data; keys not polled on a model never block
availability). The hub tracks gated entities and adds or removes them as
conditions change, with a two-poll hysteresis so a stale readback right
after a write cannot make entities flicker. A sub-device whose option is
switched off is removed from the registry instead of lingering. Switch and
number writes publish the written value immediately, as selects and times
already did. Numbers gain max_key, letting a sensor value override
native_max_value. The number, select, switch, time and sensor platforms
honour all of it. No entities move in this PR.

The options dialog no longer shows feature switches the selected plugin
does not implement, and all feature options now default to off for new
entries, including the Energy Dashboard virtual device.
…entities

SolaX support (ticket 703158) confirmed the EVC holding registers are
internally segmented: 0x600-0x650 is one segment, 0x660-0x66F another
(used for master-slave parallel operation). A function 0x03 read that
spans the boundary silently returns 0 for second-segment registers.
Reproduced on an X3-EVC-11kW Gen1 (ARM V1.18). Fixed by starting a new
read block at 0x668 and restoring the default block_size 100 (the
block_size=32 workaround predating this knowledge is no longer needed).

The plugin now publishes its firmware version (input 0x25, e.g.
V1.18 -> 118) as hub.modbus_protocol_version, enabling modbus_min/
modbus_max gating for EV chargers. Entities on registers dated by the
protocol document's revision history are gated accordingly, and every
register pair that changed location across firmware versions is exposed
as a single entity identity whose backing register follows the
firmware: Charge Power 0x08-0x0A -> 0x100-0x102, Run Mode 0x1D ->
0x106, Charge Phase 0x625 -> 0x63B, Charge Mode (0x104 sensor on
V1.12-13, 0x641 select from V1.14, 0x669 select on GEN2). The
Alt-suffixed duplicate entities are gone.

Support suggested using the documented 0x64F (limit_current_allmode)
for the maximum current. Live testing shows it is the persistent
device limit rather than the setpoint (SolaXCloud itself writes 0x668;
external writes to 0x64F are acknowledged but ignored), so Max Charge
Current stays on 0x668 and a new optional max_key field on number
descriptions caps the Max/Min Charge Current sliders dynamically from
the 0x64F readback.

New doc-based entities (disabled by default unless noted): Unbalanced
Power 0x63C and Unbalanced Switch 0x63D (X3), Mode Button 0x63E, Min
Charge Current 0x63F, Datahub Charge Power 0x643, Green 30s Delay
0x644 (switch), Parallel Support 0x107, WiFi S/N 0x607, Customized S/N
0x629. Alignments: Overload Limit renamed Overvoltage Limit (doc
OverVoltSet), Charger Type name unified across GEN1/GEN2, RSSI renamed
WiFi RSSI, per-phase Charge Current sensors consistently disabled by
default, Datahub Charge Current disabled by default, charge_added_cum
GEN2 scope narrowed from GEN2|GEN3|GEN4. Input 0x2D/0x2E are marked in
code as conflicting between the Gen1 V3.5 and GEN2 documents
(clarification requested from SolaX). Register 0x642 Parallel_enable
is deliberately not exposed: the device returns values outside the
documented 0/1 range and SolaXCloud's parallel toggle does not use it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant