Skip to content

Commit 5c38b02

Browse files
committed
fix: EVC register segmentation, firmware-version gating, doc-aligned 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.
1 parent 1b83570 commit 5c38b02

15 files changed

Lines changed: 1728 additions & 296 deletions

File tree

custom_components/solax_modbus/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,15 @@
9999
from .const import (
100100
CONF_ENERGY_DASHBOARD_DEVICE as CONF_ENERGY_DASHBOARD_DEVICE,
101101
)
102+
from .const import (
103+
CONF_READ_DATAHUB as CONF_READ_DATAHUB,
104+
)
102105
from .const import (
103106
CONF_READ_DCB as CONF_READ_DCB,
104107
)
108+
from .const import (
109+
CONF_READ_EMS as CONF_READ_EMS,
110+
)
105111
from .const import (
106112
CONF_READ_EPS as CONF_READ_EPS,
107113
)
@@ -123,9 +129,15 @@
123129
from .const import (
124130
DEFAULT_PLUGIN as DEFAULT_PLUGIN,
125131
)
132+
from .const import (
133+
DEFAULT_READ_DATAHUB as DEFAULT_READ_DATAHUB,
134+
)
126135
from .const import (
127136
DEFAULT_READ_DCB as DEFAULT_READ_DCB,
128137
)
138+
from .const import (
139+
DEFAULT_READ_EMS as DEFAULT_READ_EMS,
140+
)
129141
from .const import (
130142
DEFAULT_READ_EPS as DEFAULT_READ_EPS,
131143
)
@@ -447,11 +459,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
447459
DEVICE_GROUP_NAMES: dict[str, str] = {
448460
"eps": "EPS",
449461
"pm": "Parallel",
462+
"ems": "EMS∕V2G",
463+
"datahub": "Datahub",
450464
}
451465

452466
GATED_DEVICE_GROUPS: dict[str, tuple[str, bool]] = {
453467
"eps": (CONF_READ_EPS, DEFAULT_READ_EPS),
454468
"pm": (CONF_READ_PM, DEFAULT_READ_PM),
469+
"ems": (CONF_READ_EMS, DEFAULT_READ_EMS),
470+
"datahub": (CONF_READ_DATAHUB, DEFAULT_READ_DATAHUB),
455471
"ENERGY_DASHBOARD": (CONF_ENERGY_DASHBOARD_DEVICE, DEFAULT_ENERGY_DASHBOARD_DEVICE),
456472
}
457473

custom_components/solax_modbus/config_flow.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
CONF_MODBUS_ADDR,
4545
CONF_PLUGIN,
4646
CONF_READ_BATTERY,
47+
CONF_READ_DATAHUB,
4748
CONF_READ_DCB,
49+
CONF_READ_EMS,
4850
CONF_READ_EPS,
4951
CONF_READ_PM,
5052
CONF_SCAN_INTERVAL_FAST,
@@ -62,7 +64,9 @@
6264
DEFAULT_PLUGIN,
6365
DEFAULT_PORT,
6466
DEFAULT_READ_BATTERY,
67+
DEFAULT_READ_DATAHUB,
6568
DEFAULT_READ_DCB,
69+
DEFAULT_READ_EMS,
6670
DEFAULT_READ_EPS,
6771
DEFAULT_READ_PM,
6872
DEFAULT_SCAN_INTERVAL,
@@ -156,6 +160,8 @@ def _configured_hub_names(handler: SchemaCommonFlowHandler) -> set[str]:
156160
vol.Optional(CONF_READ_EPS, default=DEFAULT_READ_EPS): bool,
157161
vol.Optional(CONF_READ_DCB, default=DEFAULT_READ_DCB): bool,
158162
vol.Optional(CONF_READ_PM, default=DEFAULT_READ_PM): bool,
163+
vol.Optional(CONF_READ_EMS, default=DEFAULT_READ_EMS): bool,
164+
vol.Optional(CONF_READ_DATAHUB, default=DEFAULT_READ_DATAHUB): bool,
159165
vol.Optional(CONF_TIME_OUT, default=DEFAULT_TIME_OUT): int,
160166
}
161167
)
@@ -178,6 +184,8 @@ def _configured_hub_names(handler: SchemaCommonFlowHandler) -> set[str]:
178184
vol.Optional(CONF_READ_EPS, default=DEFAULT_READ_EPS): bool,
179185
vol.Optional(CONF_READ_DCB, default=DEFAULT_READ_DCB): bool,
180186
vol.Optional(CONF_READ_PM, default=DEFAULT_READ_PM): bool,
187+
vol.Optional(CONF_READ_EMS, default=DEFAULT_READ_EMS): bool,
188+
vol.Optional(CONF_READ_DATAHUB, default=DEFAULT_READ_DATAHUB): bool,
181189
vol.Optional(CONF_TIME_OUT, default=DEFAULT_TIME_OUT): int,
182190
}
183191
)
@@ -381,6 +389,10 @@ async def _option_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
381389
for option, flag_name in ((CONF_READ_EPS, "EPS"), (CONF_READ_PM, "PM")):
382390
if not _plugin_uses_feature_flag(plugin_name, flag_name):
383391
hidden.add(option)
392+
if not _plugin_supports_device_group(plugin_name, "ems"):
393+
hidden.add(CONF_READ_EMS)
394+
if not _plugin_supports_device_group(plugin_name, "datahub"):
395+
hidden.add(CONF_READ_DATAHUB)
384396
if not hidden:
385397
return OPTION_SCHEMA
386398
return vol.Schema({marker: value for marker, value in OPTION_SCHEMA.schema.items() if getattr(marker, "schema", None) not in hidden})

custom_components/solax_modbus/const.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class UnitOfReactivePower(StrEnum): # type: ignore[no-redef]
5151
CONF_READ_EPS = "read_eps"
5252
CONF_READ_DCB = "read_dcb"
5353
CONF_READ_PM = "read_pm"
54+
CONF_READ_EMS = "read_ems"
55+
CONF_READ_DATAHUB = "read_datahub"
5456
CONF_MODBUS_ADDR = "read_modbus_addr"
5557
CONF_INTERFACE = "interface"
5658
CONF_SERIAL_PORT = "read_serial_port"
@@ -67,6 +69,8 @@ class UnitOfReactivePower(StrEnum): # type: ignore[no-redef]
6769
DEFAULT_READ_EPS = False
6870
DEFAULT_READ_DCB = False
6971
DEFAULT_READ_PM = False
72+
DEFAULT_READ_EMS = False
73+
DEFAULT_READ_DATAHUB = False
7074
DEFAULT_BAUDRATE = "19200"
7175
DEFAULT_PLUGIN = "solax"
7276
DEFAULT_READ_BATTERY = False

custom_components/solax_modbus/number.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ async def async_set_native_value(self, value: float) -> None:
290290
f"writing {self._platform_name} {self._key} number register {self._register} value {pl} after div by readscale {self.entity_description.read_scale} scale {self._attr_scale} with mode {self._write_method}"
291291
)
292292
await self._hub.async_write_registers_multi(unit=self._modbus_addr, address=self._register, payload=pl)
293+
if self._write_method in (WRITE_MULTISINGLE_MODBUS, WRITE_SINGLE_MODBUS, WRITE_MULTI_MODBUS):
294+
# Publish the written value locally: the readback register only catches up on a
295+
# later poll, so the entity would otherwise keep reporting the previous value.
296+
self._hub.data[self._key] = value
293297
elif self._write_method == WRITE_DATA_LOCAL:
294298
_LOGGER.info(f"*** local data written {self._key}: {payload}")
295299
# corresponding_sensor = self._hub.preventSensors.get(self.entity_description.key, None)

0 commit comments

Comments
 (0)