Skip to content

Commit d92ac79

Browse files
Bl00d-B0bclaude
andcommitted
fix: EVC cumulative energy word order + unreachable protocol gates + reactive power L1/L2
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. modbus_min=101 gates made a dozen 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, peakshaving discharge limits, EV charger / AdapterBox communication states). Gates relaxed to 100; misleading dataclass comments corrected. 3. Grid Reactive Power L1/L2 displayed ~26 Mvar on protocol-100 devices: they were read as S32 from 0xC0/0xC2, but raw reads show protocol 100 serves independent per-phase S16 values at 0xC0..0xC3 - the S32 read merged total+L1 (and L2+L3) into one absurd number. Meanwhile the V1.02-documented registers 0xDE/0xE0 respond correctly at protocol 100 (raw-verified: 397/572 var) but were gated modbus_min=102, unreachable. L1/L2 now follow the same layout as L3/Total (documented S32 >= 100, legacy S16 <= 99). The V3.36-derived 0xC0/0xC2 S32 definitions are NOT deleted - they are re-gated to protocol 101 only, with a comment; if proto-101 hardware ever appears their overlap with 0xDE/0xE0 must be resolved with that hardware in hand. Verified live on X3 EVC 11kW and X3-Hybrid-G4 15kW. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2178c86 commit d92ac79

3 files changed

Lines changed: 38 additions & 32 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: 24 additions & 20 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",
@@ -2498,7 +2498,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
24982498
native_unit_of_measurement=UnitOfPower.WATT,
24992499
device_class=NumberDeviceClass.POWER,
25002500
allowedtypes=HYBRID | GEN4 | GEN5,
2501-
modbus_max=100,
2501+
modbus_max=99,
25022502
),
25032503
SolaxModbusNumberEntityDescription(
25042504
name="PeakShaving Discharge Limit 1",
@@ -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",
@@ -2538,7 +2538,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
25382538
native_unit_of_measurement=UnitOfPower.WATT,
25392539
device_class=NumberDeviceClass.POWER,
25402540
allowedtypes=HYBRID | GEN4 | GEN5,
2541-
modbus_max=100,
2541+
modbus_max=99,
25422542
),
25432543
SolaxModbusNumberEntityDescription(
25442544
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(
@@ -5223,44 +5223,44 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
52235223
key="peakshaving_discharge_limit_1",
52245224
register=0x153,
52255225
allowedtypes=AC | HYBRID | GEN4 | GEN5,
5226-
modbus_max=100,
5226+
modbus_max=99,
52275227
internal=True,
52285228
),
52295229
SolaXModbusSensorEntityDescription(
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(
52455245
key="peakshaving_discharge_limit_2",
52465246
register=0x154,
52475247
allowedtypes=AC | HYBRID | GEN4 | GEN5,
5248-
modbus_max=100,
5248+
modbus_max=99,
52495249
internal=True,
52505250
),
52515251
SolaXModbusSensorEntityDescription(
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
),
@@ -8052,7 +8052,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80528052
register_type=REG_INPUT,
80538053
register_data_type=REGISTER_S32,
80548054
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8055-
modbus_min=100,
8055+
modbus_min=101, # V3.36-derived layout; proto 100 serves S16 here (raw-verified) - see 0xDE/0xE0
80568056
modbus_max=101,
80578057
entity_registry_enabled_default=False,
80588058
),
@@ -8066,7 +8066,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80668066
register_type=REG_INPUT,
80678067
register_data_type=REGISTER_S32,
80688068
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8069-
modbus_min=102,
8069+
modbus_min=100, # V1.02 registers respond at protocol 100 (raw-read verified: 397/572 var)
80708070
entity_registry_enabled_default=False,
80718071
),
80728072
SolaXModbusSensorEntityDescription(
@@ -8092,7 +8092,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80928092
register_type=REG_INPUT,
80938093
register_data_type=REGISTER_S32,
80948094
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8095-
modbus_min=100,
8095+
modbus_min=101, # V3.36-derived layout; proto 100 serves S16 here (raw-verified) - see 0xDE/0xE0
80968096
modbus_max=101,
80978097
entity_registry_enabled_default=False,
80988098
),
@@ -8106,7 +8106,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
81068106
register_type=REG_INPUT,
81078107
register_data_type=REGISTER_S32,
81088108
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8109-
modbus_min=102,
8109+
modbus_min=100, # V1.02 registers respond at protocol 100 (raw-read verified: 397/572 var)
81108110
entity_registry_enabled_default=False,
81118111
),
81128112
SolaXModbusSensorEntityDescription(
@@ -10186,6 +10186,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1018610186
register=0x704,
1018710187
register_type=REG_INPUT,
1018810188
register_data_type=REGISTER_S32,
10189+
entity_registry_enabled_default=False, # meaningless without a meter/CT attached (region returns junk)
1018910190
allowedtypes=MIC | GEN2 | X1,
1019010191
),
1019110192
SolaXModbusSensorEntityDescription(
@@ -10197,6 +10198,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1019710198
register=0x704,
1019810199
register_type=REG_INPUT,
1019910200
register_data_type=REGISTER_S32,
10201+
entity_registry_enabled_default=False, # meaningless without a meter/CT attached (region returns junk)
1020010202
allowedtypes=MIC | GEN2 | X3,
1020110203
),
1020210204
SolaXModbusSensorEntityDescription(
@@ -10208,6 +10210,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1020810210
register=0x706,
1020910211
register_type=REG_INPUT,
1021010212
register_data_type=REGISTER_S32,
10213+
entity_registry_enabled_default=False, # meaningless without a meter/CT attached (region returns junk)
1021110214
allowedtypes=MIC | GEN2 | X3,
1021210215
),
1021310216
SolaXModbusSensorEntityDescription(
@@ -10219,6 +10222,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1021910222
register=0x708,
1022010223
register_type=REG_INPUT,
1022110224
register_data_type=REGISTER_S32,
10225+
entity_registry_enabled_default=False, # meaningless without a meter/CT attached (region returns junk)
1022210226
allowedtypes=MIC | GEN2 | X3,
1022310227
),
1022410228
SolaXModbusSensorEntityDescription(

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)