Skip to content

Commit e3ef3c8

Browse files
Bl00d-B0bclaude
andcommitted
fix: correct 32-bit register decoding and unreachable protocol gates
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 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 phases. The primaries now use the documented addresses. The "_alt" sensors (which already read the correct addresses) are kept unchanged for compatibility; they now simply mirror the primaries. Primaries are default-disabled: the 0x70x region returns meaningless data on units without a meter/CT attached. 3. modbus_min=101 gates made these entities unreachable: register 0x82 reports the protocol version (100 = V001.00) and no known device returns more than 100 - the 101/102 values conflated the protocol register with the document revision (see the discussion on #2064). Raw register reads on a real protocol-100 device confirm all affected registers respond with sensible data (bias_power 0x5D, peakshaving discharge limits 0xEE/0xEF + readbacks 0x153/0x154, EV charger / AdapterBox communication states 0xA0/0xA1 - the latter correctly reporting an actually-connected EV charger). Gates relaxed to 100 and the misleading dataclass comments corrected. The reactive-power registers (0xDE/0xE0, modbus_min=102) are intentionally NOT touched: relaxing them requires resolving their overlap with the 0xC0/0xC2 S32 variants, which needs hardware coverage this PR does not have. 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 e3ef3c8

3 files changed

Lines changed: 32 additions & 27 deletions

File tree

custom_components/solax_modbus/const.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,9 @@ class BaseModbusSensorEntityDescription(SensorEntityDescription):
204204
"""Base class for modbus sensor declarations."""
205205

206206
allowedtypes: int = 0 # overload with ALLDEFAULT from plugin
207-
modbus_min: int | None = None # Minimum supported Modbus protocol document version, e.g. 102 for V001.02.
208-
modbus_max: int | None = None # Maximum supported Modbus protocol document version.
207+
order32: str | None = None # per-sensor 32-bit word order override ("big"/"little"); None = plugin default
208+
modbus_min: int | None = None # Minimum protocol version as reported by register 0x82 (e.g. 100 for V001.00); not the document revision.
209+
modbus_max: int | None = None # Maximum protocol version as reported by register 0x82.
209210
scale: float | dict[Any, Any] | Callable[[Any, Any, dict[str, Any]], Any] = (
210211
1 # can be float, dictionary or callable function(initval, descr, datadict)
211212
)
@@ -252,8 +253,8 @@ class BaseModbusButtonEntityDescription(ButtonEntityDescription):
252253
"""Base class for modbus button declarations."""
253254

254255
allowedtypes: int = 0 # overload with ALLDEFAULT from plugin
255-
modbus_min: int | None = None # Minimum supported Modbus protocol document version, e.g. 102 for V001.02.
256-
modbus_max: int | None = None # Maximum supported Modbus protocol document version.
256+
modbus_min: int | None = None # Minimum protocol version as reported by register 0x82 (e.g. 100 for V001.00); not the document revision.
257+
modbus_max: int | None = None # Maximum protocol version as reported by register 0x82.
257258
register: int | None = None
258259
command: int | None = None
259260
blacklist: list[str] | None = None # none or list of serial number prefixes
@@ -268,8 +269,8 @@ class BaseModbusSelectEntityDescription(SelectEntityDescription):
268269
"""Base class for modbus select declarations."""
269270

270271
allowedtypes: int = 0 # overload with ALLDEFAULT from plugin
271-
modbus_min: int | None = None # Minimum supported Modbus protocol document version, e.g. 102 for V001.02.
272-
modbus_max: int | None = None # Maximum supported Modbus protocol document version.
272+
modbus_min: int | None = None # Minimum protocol version as reported by register 0x82 (e.g. 100 for V001.00); not the document revision.
273+
modbus_max: int | None = None # Maximum protocol version as reported by register 0x82.
273274
register: int | None = None
274275
option_dict: dict[int, str] | None = None
275276
reverse_option_dict: dict[str, int] | None = None # autocomputed
@@ -288,8 +289,8 @@ class BaseModbusSwitchEntityDescription(SwitchEntityDescription):
288289
"""Base class for modbus switch declarations."""
289290

290291
allowedtypes: int = 0 # overload with ALLDEFAULT from plugin
291-
modbus_min: int | None = None # Minimum supported Modbus protocol document version, e.g. 102 for V001.02.
292-
modbus_max: int | None = None # Maximum supported Modbus protocol document version.
292+
modbus_min: int | None = None # Minimum protocol version as reported by register 0x82 (e.g. 100 for V001.00); not the document revision.
293+
modbus_max: int | None = None # Maximum protocol version as reported by register 0x82.
293294
register: int | None = None
294295
register_bit: int | None = None
295296
blacklist: list[str] | None = None # none or list of serial number prefixes
@@ -307,8 +308,8 @@ class BaseModbusTimeEntityDescription(TimeEntityDescription):
307308
"""Base class for modbus time declarations."""
308309

309310
allowedtypes: int = 0 # overload with ALLDEFAULT from plugin
310-
modbus_min: int | None = None # Minimum supported Modbus protocol document version, e.g. 102 for V001.02.
311-
modbus_max: int | None = None # Maximum supported Modbus protocol document version.
311+
modbus_min: int | None = None # Minimum protocol version as reported by register 0x82 (e.g. 100 for V001.00); not the document revision.
312+
modbus_max: int | None = None # Maximum protocol version as reported by register 0x82.
312313
scale: float | dict[Any, Any] | Callable[[Any, Any, dict[str, Any]], Any] = 1
313314
read_scale_exceptions: list[Any] | None = None
314315
read_scale: float = 1
@@ -336,8 +337,8 @@ class BaseModbusNumberEntityDescription(NumberEntityDescription):
336337
"""Base class for modbus number declarations."""
337338

338339
allowedtypes: int = 0 # overload with ALLDEFAULT from plugin
339-
modbus_min: int | None = None # Minimum supported Modbus protocol document version, e.g. 102 for V001.02.
340-
modbus_max: int | None = None # Maximum supported Modbus protocol document version.
340+
modbus_min: int | None = None # Minimum protocol version as reported by register 0x82 (e.g. 100 for V001.00); not the document revision.
341+
modbus_max: int | None = None # Maximum protocol version as reported by register 0x82.
341342
register: int | None = None
342343
read_scale_exceptions: list[Any] | None = None
343344
read_scale: float = 1

custom_components/solax_modbus/plugin_solax.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2283,7 +2283,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
22832283
native_unit_of_measurement=UnitOfPower.WATT,
22842284
device_class=NumberDeviceClass.POWER,
22852285
allowedtypes=AC | HYBRID | GEN4 | GEN5,
2286-
modbus_min=101,
2286+
modbus_min=100,
22872287
entity_category=EntityCategory.CONFIG,
22882288
entity_registry_enabled_default=False,
22892289
icon="mdi:tune-variant",
@@ -2511,7 +2511,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
25112511
native_unit_of_measurement=UnitOfPower.WATT,
25122512
device_class=NumberDeviceClass.POWER,
25132513
allowedtypes=HYBRID | GEN4 | GEN5 | X1,
2514-
modbus_min=101,
2514+
modbus_min=100,
25152515
),
25162516
SolaxModbusNumberEntityDescription(
25172517
name="PeakShaving Discharge Limit 1",
@@ -2525,7 +2525,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
25252525
device_class=NumberDeviceClass.POWER,
25262526
scale=0.1,
25272527
allowedtypes=HYBRID | GEN4 | GEN5 | X3,
2528-
modbus_min=101,
2528+
modbus_min=100,
25292529
),
25302530
SolaxModbusNumberEntityDescription(
25312531
name="PeakShaving Discharge Limit 2",
@@ -2551,7 +2551,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
25512551
native_unit_of_measurement=UnitOfPower.WATT,
25522552
device_class=NumberDeviceClass.POWER,
25532553
allowedtypes=HYBRID | GEN4 | GEN5 | X1,
2554-
modbus_min=101,
2554+
modbus_min=100,
25552555
),
25562556
SolaxModbusNumberEntityDescription(
25572557
name="PeakShaving Discharge Limit 2",
@@ -2565,7 +2565,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
25652565
device_class=NumberDeviceClass.POWER,
25662566
scale=0.1,
25672567
allowedtypes=HYBRID | GEN4 | GEN5 | X3,
2568-
modbus_min=101,
2568+
modbus_min=100,
25692569
),
25702570
SolaxModbusNumberEntityDescription(
25712571
name="PeakShaving Discharge Limit 2",
@@ -4421,7 +4421,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
44214421
native_unit_of_measurement=UnitOfPower.WATT,
44224422
device_class=SensorDeviceClass.POWER,
44234423
allowedtypes=AC | HYBRID | GEN4 | GEN5,
4424-
modbus_min=101,
4424+
modbus_min=100,
44254425
internal=True,
44264426
),
44274427
SolaXModbusSensorEntityDescription(
@@ -5230,15 +5230,15 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
52305230
key="peakshaving_discharge_limit_1",
52315231
register=0x153,
52325232
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X1,
5233-
modbus_min=101,
5233+
modbus_min=100,
52345234
internal=True,
52355235
),
52365236
SolaXModbusSensorEntityDescription(
52375237
key="peakshaving_discharge_limit_1",
52385238
register=0x153,
52395239
scale=0.1,
52405240
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
5241-
modbus_min=101,
5241+
modbus_min=100,
52425242
internal=True,
52435243
),
52445244
SolaXModbusSensorEntityDescription(
@@ -5252,15 +5252,15 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
52525252
key="peakshaving_discharge_limit_2",
52535253
register=0x154,
52545254
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X1,
5255-
modbus_min=101,
5255+
modbus_min=100,
52565256
internal=True,
52575257
),
52585258
SolaXModbusSensorEntityDescription(
52595259
key="peakshaving_discharge_limit_2",
52605260
register=0x154,
52615261
scale=0.1,
52625262
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
5263-
modbus_min=101,
5263+
modbus_min=100,
52645264
internal=True,
52655265
),
52665266
SolaXModbusSensorEntityDescription(
@@ -7692,7 +7692,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
76927692
1: "Connected",
76937693
},
76947694
allowedtypes=AC | HYBRID | GEN4 | GEN5 | GEN6,
7695-
modbus_min=101,
7695+
modbus_min=100,
76967696
entity_registry_enabled_default=False,
76977697
entity_category=EntityCategory.DIAGNOSTIC,
76987698
),
@@ -7706,7 +7706,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
77067706
1: "Connected",
77077707
},
77087708
allowedtypes=AC | HYBRID | GEN4 | GEN5 | GEN6,
7709-
modbus_min=101,
7709+
modbus_min=100,
77107710
entity_registry_enabled_default=False,
77117711
entity_category=EntityCategory.DIAGNOSTIC,
77127712
),
@@ -10194,32 +10194,35 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1019410194
native_unit_of_measurement=UnitOfPower.WATT,
1019510195
device_class=SensorDeviceClass.POWER,
1019610196
state_class=SensorStateClass.MEASUREMENT,
10197-
register=0x704,
10197+
register=0x705, # MIC-G2 doc: R-phase data words at 0x705/0x706
1019810198
register_type=REG_INPUT,
1019910199
register_data_type=REGISTER_S32,
1020010200
allowedtypes=MIC | GEN2 | X3,
10201+
entity_registry_enabled_default=False, # meaningless without a meter/CT attached
1020110202
),
1020210203
SolaXModbusSensorEntityDescription(
1020310204
name="Measured Power L2",
1020410205
key="measured_power_l2",
1020510206
native_unit_of_measurement=UnitOfPower.WATT,
1020610207
device_class=SensorDeviceClass.POWER,
1020710208
state_class=SensorStateClass.MEASUREMENT,
10208-
register=0x706,
10209+
register=0x707, # MIC-G2 doc: S-phase data words at 0x707/0x708
1020910210
register_type=REG_INPUT,
1021010211
register_data_type=REGISTER_S32,
1021110212
allowedtypes=MIC | GEN2 | X3,
10213+
entity_registry_enabled_default=False, # meaningless without a meter/CT attached
1021210214
),
1021310215
SolaXModbusSensorEntityDescription(
1021410216
name="Measured Power L3",
1021510217
key="measured_power_l3",
1021610218
native_unit_of_measurement=UnitOfPower.WATT,
1021710219
device_class=SensorDeviceClass.POWER,
1021810220
state_class=SensorStateClass.MEASUREMENT,
10219-
register=0x708,
10221+
register=0x709, # MIC-G2 doc: T-phase data words at 0x709/0x70A
1022010222
register_type=REG_INPUT,
1022110223
register_data_type=REGISTER_S32,
1022210224
allowedtypes=MIC | GEN2 | X3,
10225+
entity_registry_enabled_default=False, # meaningless without a meter/CT attached
1022310226
),
1022410227
SolaXModbusSensorEntityDescription(
1022510228
name="Measured Power L1 Alt",

custom_components/solax_modbus/plugin_solax_ev_charger.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ 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+
order32="big", # device sends this u32 high-word-first (see holding twin 0x619)
11381139
scale=0.1,
11391140
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
11401141
device_class=SensorDeviceClass.ENERGY,

0 commit comments

Comments
 (0)