Skip to content

Commit e8b4b38

Browse files
Bl00d-B0bclaude
andcommitted
fix: EVC energy word order, unreachable protocol gates, reactive power, MIC-G2 sensors
Seven 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 #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; live testing shows the block has no address decoder (any read returns the current internal frame) and no frame contains per-phase power. The _alt sensors, built on the doc's off-by-one parentheticals, are removed; the primaries are renamed to the doc's terminology (Feed-in Power L1/L2/L3, Int32 per the authoritative address column) and stay default-disabled. The verified-correct meter registers - Feed-in Power 0x43B (doc "Feed In Power", Int32) and the 0x43D/0x43F energy counters (Uint32) - are renamed per the doc and now enabled by default (they read clean zeros without a meter). All 32-bit MIC registers cross-checked for signedness. 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. 7. RTC sensors (hybrid 0x85, MIC 0x318) are now proper TIMESTAMP entities: value_function_rtc returns a timezone-aware datetime (host local zone attached - the device stores host-local time, verified live; Sync RTC writes datetime.now()), matching the EV charger's RTC implementation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2178c86 commit e8b4b38

4 files changed

Lines changed: 58 additions & 118 deletions

File tree

custom_components/solax_modbus/const.py

Lines changed: 22 additions & 14 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
@@ -585,8 +586,15 @@ def value_function_rtc(initval: Any, descr: Any, datadict: dict[str, Any]) -> da
585586
rtc_months,
586587
rtc_years,
587588
) = initval
588-
val = f"{rtc_days:02}/{rtc_months:02}/{rtc_years % 100:02} {rtc_hours:02}:{rtc_minutes:02}:{rtc_seconds:02}"
589-
return datetime.strptime(val, "%d/%m/%y %H:%M:%S") # ok since sensor.py has been adapted
589+
return datetime(
590+
2000 + rtc_years % 100,
591+
rtc_months,
592+
rtc_days,
593+
rtc_hours,
594+
rtc_minutes,
595+
rtc_seconds,
596+
tzinfo=datetime.now().astimezone().tzinfo,
597+
)
590598
except Exception:
591599
return None
592600

0 commit comments

Comments
 (0)