Skip to content

Commit c922959

Browse files
Bl00d-B0bclaude
andcommitted
fix: EVC energy word order, unreachable protocol gates, reactive power, MIC-G2 sensors
Six hardware-verified fixes (raw Modbus TCP reads cross-checked against the official protocol documents): 1. EVC Charge Added - Cumulative read 19660.8 kWh instead of 0.3 kWh: the charger sends EQ_Total (0x10, u32) high-word-first. Adds the per-sensor order32 override field (read path already honored it) and sets order32="big" on charge_added_cum. 2. modbus_min=101/102 gates were unreachable: no device reports more than 100 from register 0x82 (the values conflated the protocol register with the document revision). Relaxed to 100 after raw-read verification; misleading dataclass comments corrected; peakshaving complementary pairs re-split at the documented boundary (unscaled max=99, X1/X3-scaled min=100). 3. Grid Reactive Power L1/L2 showed ~26 Mvar: 0xC0/0xC2-as-S32 merged two S16 values. L1/L2 now use the documented S32 registers 0xDE/0xE0 for protocol >=100 (raw-verified, phases sum to total) and the legacy S16 quad for <=99; the V1.00-derived 0xC0/0xC2 S32 defs are removed (doc error confirmed, resolves the open question in wills106#2099). 4. MIC pv_total_power removed: exact computed duplicate of pv_power_total (same value_function, same device). 5. MIC-G2 measured power: doc V4.0 marks the whole 0x700-0x70A block as EVC-only and its PowerToEV row proves the parenthetical addresses carry a systematic -1 typo. The _alt sensors (l1/l2/l3), built on that typo and reading Int32 at overlapping odd offsets, are removed; the primaries (0x704/0x706/0x708, Int32 per the authoritative address column) and measured_power_2 stay default-disabled - a meter-attached test shows the firmware never serves per-phase values there (handler echoes one buffer at every offset). 6. MIC-G2 Q Curve select always showed unknown: readback 0x347 is byte-packed (mode in low byte, Pf in high byte) but was decoded as full u16. Now REGISTER_U8L. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2178c86 commit c922959

3 files changed

Lines changed: 41 additions & 109 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: 27 additions & 97 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
),
@@ -8042,20 +8042,6 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80428042
modbus_max=99,
80438043
entity_registry_enabled_default=False,
80448044
),
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,
8056-
modbus_max=101,
8057-
entity_registry_enabled_default=False,
8058-
),
80598045
SolaXModbusSensorEntityDescription(
80608046
name="Grid Reactive Power L1",
80618047
key="grid_reactive_power_l1",
@@ -8066,7 +8052,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80668052
register_type=REG_INPUT,
80678053
register_data_type=REGISTER_S32,
80688054
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8069-
modbus_min=102,
8055+
modbus_min=100, # V1.02 registers respond at protocol 100 (raw-read verified: 397/572 var)
80708056
entity_registry_enabled_default=False,
80718057
),
80728058
SolaXModbusSensorEntityDescription(
@@ -8082,20 +8068,6 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
80828068
modbus_max=99,
80838069
entity_registry_enabled_default=False,
80848070
),
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,
8096-
modbus_max=101,
8097-
entity_registry_enabled_default=False,
8098-
),
80998071
SolaXModbusSensorEntityDescription(
81008072
name="Grid Reactive Power L2",
81018073
key="grid_reactive_power_l2",
@@ -8106,7 +8078,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
81068078
register_type=REG_INPUT,
81078079
register_data_type=REGISTER_S32,
81088080
allowedtypes=AC | HYBRID | GEN4 | GEN5 | X3,
8109-
modbus_min=102,
8081+
modbus_min=100, # V1.02 registers respond at protocol 100 (raw-read verified: 397/572 var)
81108082
entity_registry_enabled_default=False,
81118083
),
81128084
SolaXModbusSensorEntityDescription(
@@ -9600,6 +9572,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
96009572
SolaXModbusSensorEntityDescription(
96019573
key="q-curve",
96029574
register=0x347,
9575+
register_data_type=REGISTER_U8L, # packed register: QCurve_SetMode in low byte, bQCurve_SetPf in high byte
96039576
scale={
96049577
0: "Off",
96059578
1: "Over Excited",
@@ -9958,17 +9931,6 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
99589931
allowedtypes=MIC | GEN | GEN2,
99599932
icon="mdi:solar-power-variant",
99609933
),
9961-
SolaXModbusSensorEntityDescription(
9962-
name="PV Total Power",
9963-
key="pv_total_power",
9964-
value_function=value_function_pv_power_total,
9965-
native_unit_of_measurement=UnitOfPower.WATT,
9966-
device_class=SensorDeviceClass.POWER,
9967-
state_class=SensorStateClass.MEASUREMENT,
9968-
entity_registry_enabled_default=False,
9969-
allowedtypes=MIC | GEN | GEN2,
9970-
icon="mdi:solar-power-variant",
9971-
),
99729934
SolaXModbusSensorEntityDescription(
99739935
name="Total Yield",
99749936
key="total_yield",
@@ -10178,83 +10140,51 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
1017810140
icon="mdi:home-import-outline",
1017910141
),
1018010142
SolaXModbusSensorEntityDescription(
10181-
name="Measured Power 2",
10143+
name="Feed-in Power L1",
1018210144
key="measured_power_2",
1018310145
native_unit_of_measurement=UnitOfPower.WATT,
1018410146
device_class=SensorDeviceClass.POWER,
1018510147
state_class=SensorStateClass.MEASUREMENT,
1018610148
register=0x704,
1018710149
register_type=REG_INPUT,
1018810150
register_data_type=REGISTER_S32,
10151+
entity_registry_enabled_default=False, # doc V4.0 marks the 0x700-0x70A block as EVC-only; not served as meter data (meter-attached test)
1018910152
allowedtypes=MIC | GEN2 | X1,
1019010153
),
1019110154
SolaXModbusSensorEntityDescription(
10192-
name="Measured Power L1",
10155+
name="Feed-in Power L1",
1019310156
key="measured_power_l1",
1019410157
native_unit_of_measurement=UnitOfPower.WATT,
1019510158
device_class=SensorDeviceClass.POWER,
1019610159
state_class=SensorStateClass.MEASUREMENT,
1019710160
register=0x704,
1019810161
register_type=REG_INPUT,
1019910162
register_data_type=REGISTER_S32,
10163+
entity_registry_enabled_default=False, # doc V4.0 marks the 0x700-0x70A block as EVC-only; not served as meter data (meter-attached test)
1020010164
allowedtypes=MIC | GEN2 | X3,
1020110165
),
1020210166
SolaXModbusSensorEntityDescription(
10203-
name="Measured Power L2",
10167+
name="Feed-in Power L2",
1020410168
key="measured_power_l2",
1020510169
native_unit_of_measurement=UnitOfPower.WATT,
1020610170
device_class=SensorDeviceClass.POWER,
1020710171
state_class=SensorStateClass.MEASUREMENT,
1020810172
register=0x706,
1020910173
register_type=REG_INPUT,
1021010174
register_data_type=REGISTER_S32,
10175+
entity_registry_enabled_default=False, # doc V4.0 marks the 0x700-0x70A block as EVC-only; not served as meter data (meter-attached test)
1021110176
allowedtypes=MIC | GEN2 | X3,
1021210177
),
1021310178
SolaXModbusSensorEntityDescription(
10214-
name="Measured Power L3",
10179+
name="Feed-in Power L3",
1021510180
key="measured_power_l3",
1021610181
native_unit_of_measurement=UnitOfPower.WATT,
1021710182
device_class=SensorDeviceClass.POWER,
1021810183
state_class=SensorStateClass.MEASUREMENT,
1021910184
register=0x708,
1022010185
register_type=REG_INPUT,
1022110186
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,
10243-
register_type=REG_INPUT,
10244-
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,
10187+
entity_registry_enabled_default=False, # doc V4.0 marks the 0x700-0x70A block as EVC-only; not served as meter data (meter-attached test)
1025810188
allowedtypes=MIC | GEN2 | X3,
1025910189
),
1026010190
#####

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)