Skip to content

Commit e7d8fb7

Browse files
Bl00d-B0bclaude
andcommitted
fix: correct 32-bit register decoding errors (EVC EQ_Total, MIC GEN2 meter power, hybrid reactive power)
Three independent 32-bit decoding bugs, all producing absurd values, all verified against protocol documents and real hardware: 1. EV charger cumulative energy (0x0010 EQ_Total, input u32): the charger transmits this register high-word-first, contradicting the doc (EVC_Modbus_V3.5: 0x10=Lo/0x11=Hi) and the same counter's little-ordered holding copy at 0x0619. Raw 3 (0.3 kWh) displayed as 19660.8 kWh. Adds an optional per-sensor order32 override to BaseModbusSensorEntityDescription (the read path already honored it via getattr; only the dataclass field was missing) and sets order32="big" on charge_added_cum. 2. MIC GEN2 X3 meter phase powers were read one register early: the MIC-G2 V3.1 doc explicitly places the data words at R=(0x705:LSB,0x706:MSB), S=(0x707,0x708), T=(0x709,0x70A), while measured_power_l1/l2/l3 read 0x704/0x706/0x708 - straddling two different phases (149 MW on an 8 kW inverter). The "_alt" sensors at 0x705/0x707/0x709 were pointing at the correct addresses all along; the primaries now use those addresses and the redundant _alt entities are removed. 3. Hybrid per-phase meter reactive power: the S32 variants at 0xC0/0xC2 gated modbus_min=100..101 are undocumented (V1.02 only documents 0xDE/0xE0) and contradicted by a real protocol-100 device, which still serves the legacy per-phase S16 layout there (the S32 read merged two neighbouring S16s into 26 Mvar). Legacy S16 definitions now cover protocol <= 101; the documented S32 registers (0xDE/0xE0/0xD4) start at protocol 102. Verified live on X3 EVC 11kW, X3-MIC/PRO-G2 and X3-Hybrid-G4 15kW. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2178c86 commit e7d8fb7

3 files changed

Lines changed: 11 additions & 69 deletions

File tree

custom_components/solax_modbus/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ class BaseModbusSensorEntityDescription(SensorEntityDescription):
204204
"""Base class for modbus sensor declarations."""
205205

206206
allowedtypes: int = 0 # overload with ALLDEFAULT from plugin
207+
order32: str | None = None # per-sensor 32-bit word order override ("big"/"little"); None = plugin default
207208
modbus_min: int | None = None # Minimum supported Modbus protocol document version, e.g. 102 for V001.02.
208209
modbus_max: int | None = None # Maximum supported Modbus protocol document version.
209210
scale: float | dict[Any, Any] | Callable[[Any, Any, dict[str, Any]], Any] = (

custom_components/solax_modbus/plugin_solax.py

Lines changed: 5 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8039,20 +8039,6 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80398039
register_type=REG_INPUT,
80408040
register_data_type=REGISTER_S16,
80418041
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8042-
modbus_max=99,
8043-
entity_registry_enabled_default=False,
8044-
),
8045-
SolaXModbusSensorEntityDescription(
8046-
name="Grid Reactive Power L1",
8047-
key="grid_reactive_power_l1",
8048-
native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,
8049-
device_class=SensorDeviceClass.REACTIVE_POWER,
8050-
state_class=SensorStateClass.MEASUREMENT,
8051-
register=0xC0,
8052-
register_type=REG_INPUT,
8053-
register_data_type=REGISTER_S32,
8054-
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8055-
modbus_min=100,
80568042
modbus_max=101,
80578043
entity_registry_enabled_default=False,
80588044
),
@@ -8079,20 +8065,6 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80798065
register_type=REG_INPUT,
80808066
register_data_type=REGISTER_S16,
80818067
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8082-
modbus_max=99,
8083-
entity_registry_enabled_default=False,
8084-
),
8085-
SolaXModbusSensorEntityDescription(
8086-
name="Grid Reactive Power L2",
8087-
key="grid_reactive_power_l2",
8088-
native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,
8089-
device_class=SensorDeviceClass.REACTIVE_POWER,
8090-
state_class=SensorStateClass.MEASUREMENT,
8091-
register=0xC2,
8092-
register_type=REG_INPUT,
8093-
register_data_type=REGISTER_S32,
8094-
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8095-
modbus_min=100,
80968068
modbus_max=101,
80978069
entity_registry_enabled_default=False,
80988070
),
@@ -8119,7 +8091,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
81198091
register_type=REG_INPUT,
81208092
register_data_type=REGISTER_S16,
81218093
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8122-
modbus_max=99,
8094+
modbus_max=101,
81238095
entity_registry_enabled_default=False,
81248096
),
81258097
SolaXModbusSensorEntityDescription(
@@ -8132,7 +8104,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
81328104
register_type=REG_INPUT,
81338105
register_data_type=REGISTER_S32,
81348106
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8135-
modbus_min=100,
8107+
modbus_min=102,
81368108
entity_registry_enabled_default=False,
81378109
),
81388110
SolaXModbusSensorEntityDescription(
@@ -10194,7 +10166,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1019410166
native_unit_of_measurement=UnitOfPower.WATT,
1019510167
device_class=SensorDeviceClass.POWER,
1019610168
state_class=SensorStateClass.MEASUREMENT,
10197-
register=0x704,
10169+
register=0x705, # MIC-G2 V3.1: data words at LSB/MSB addresses given in doc (off-by-one vs table row)
1019810170
register_type=REG_INPUT,
1019910171
register_data_type=REGISTER_S32,
1020010172
allowedtypes=MIC | GEN2 | X3,
@@ -10205,7 +10177,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1020510177
native_unit_of_measurement=UnitOfPower.WATT,
1020610178
device_class=SensorDeviceClass.POWER,
1020710179
state_class=SensorStateClass.MEASUREMENT,
10208-
register=0x706,
10180+
register=0x707, # MIC-G2 V3.1: data words at LSB/MSB addresses given in doc (off-by-one vs table row)
1020910181
register_type=REG_INPUT,
1021010182
register_data_type=REGISTER_S32,
1021110183
allowedtypes=MIC | GEN2 | X3,
@@ -10216,45 +10188,9 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1021610188
native_unit_of_measurement=UnitOfPower.WATT,
1021710189
device_class=SensorDeviceClass.POWER,
1021810190
state_class=SensorStateClass.MEASUREMENT,
10219-
register=0x708,
10220-
register_type=REG_INPUT,
10221-
register_data_type=REGISTER_S32,
10222-
allowedtypes=MIC | GEN2 | X3,
10223-
),
10224-
SolaXModbusSensorEntityDescription(
10225-
name="Measured Power L1 Alt",
10226-
key="measured_power_l1_alt",
10227-
native_unit_of_measurement=UnitOfPower.WATT,
10228-
device_class=SensorDeviceClass.POWER,
10229-
state_class=SensorStateClass.MEASUREMENT,
10230-
register=0x705,
10231-
register_type=REG_INPUT,
10232-
register_data_type=REGISTER_S32,
10233-
entity_registry_enabled_default=False,
10234-
allowedtypes=MIC | GEN2 | X3,
10235-
),
10236-
SolaXModbusSensorEntityDescription(
10237-
name="Measured Power L2 Alt",
10238-
key="measured_power_l2_alt",
10239-
native_unit_of_measurement=UnitOfPower.WATT,
10240-
device_class=SensorDeviceClass.POWER,
10241-
state_class=SensorStateClass.MEASUREMENT,
10242-
register=0x707,
10191+
register=0x709, # MIC-G2 V3.1: data words at LSB/MSB addresses given in doc (off-by-one vs table row)
1024310192
register_type=REG_INPUT,
1024410193
register_data_type=REGISTER_S32,
10245-
entity_registry_enabled_default=False,
10246-
allowedtypes=MIC | GEN2 | X3,
10247-
),
10248-
SolaXModbusSensorEntityDescription(
10249-
name="Measured Power L3 Alt",
10250-
key="measured_power_l3_alt",
10251-
native_unit_of_measurement=UnitOfPower.WATT,
10252-
device_class=SensorDeviceClass.POWER,
10253-
state_class=SensorStateClass.MEASUREMENT,
10254-
register=0x709,
10255-
register_type=REG_INPUT,
10256-
register_data_type=REGISTER_S32,
10257-
entity_registry_enabled_default=False,
1025810194
allowedtypes=MIC | GEN2 | X3,
1025910195
),
1026010196
#####

custom_components/solax_modbus/plugin_solax_ev_charger.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,11 @@ def value_function_sync_rtc_evc(initval: Any, descr: Any, datadict: dict[str, An
11351135
register=0x10,
11361136
register_type=REG_INPUT,
11371137
register_data_type=REGISTER_U32,
1138+
# Firmware sends this u32 high-word-first, contradicting the doc (EVC_Modbus_V3.5
1139+
# declares 0x10=Lo/0x11=Hi) and the holding-register twin 0x619 (EQ_Total), which
1140+
# is little-ordered. Without this override the value reads as N*65536
1141+
# (e.g. raw 3 -> 19660.8 kWh instead of 0.3 kWh).
1142+
order32="big",
11381143
scale=0.1,
11391144
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
11401145
device_class=SensorDeviceClass.ENERGY,

0 commit comments

Comments
 (0)