fix: EVC energy word order, unreachable protocol gates, reactive power, MIC-G2 sensors - #2178
Conversation
e7d8fb7 to
490aa70
Compare
e3ef3c8 to
07eaf1b
Compare
07eaf1b to
ffe2ea8
Compare
10ba8a0 to
d92ac79
Compare
|
@TCWORLD — this PR directly answers the open question you left in #2099:
That test has now been done — raw Modbus TCP reads on a real protocol-100 X3-Hybrid-G4, same instant:
Both layouts carry the same values simultaneously at protocol 100, and the V1.02 addresses (0xD2/0xD4/0xDE/0xE0) decode correctly — your documentation-error hypothesis is confirmed: the V1.00 doc's 0xC0/0xC2-as-S32 claim was the error; the registers never moved. Reading 0xC0 as S32 merges total+L1 into the ~26 Mvar values users see. Hence this PR gates the V1.02 S32 registers at |
d92ac79 to
0c91471
Compare
|
@Bl00d-B0b I figured it was most likely a documentation error. I have no problem removing the problematic entries rather than leaving them behind with a 101/101 gating. I think looking at the PR changes, they have been removed entirely which is good. |
0c91471 to
1d7f275
Compare
d71bd20 to
6eda3ac
Compare
741e3fe to
273bc25
Compare
24b2868 to
ead87ee
Compare
…r, 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 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; 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>
ead87ee to
e8b4b38
Compare
|
Does this PR require further work as it's still in draft? |
This seems to be problematic, please see issue #2230 whereby this switch (which goes against the documentation) seems to have broken the setup for other users. I wonder if there is either a firmware bug in your system, or the change was applied too generally and may only be applicable to specific chargers. Can you confirm what model number the EVC charger you tested with is, and what firmware version it is running. |
|
@Bl00d-B0b could you confirm your charger reports as being GEN1? |
|
@TCWORLD confirming: my charger is an X3-EVC-11kW, serial prefix You're right that the change was applied too generally. The original evidence was solid but unit-specific: at the same instant, input Fix in #2235: the |
The order32="big" override on charge_added_cum (from wills106#2178) was verified on an X3-EVC-11kW Gen1 (serial C311*, ARM v1.18): at the same instant the input copy 0x10-0x11 read [0, 3] (high-word-first) while its holding twin 0x619-0x61A read [3, 0] and decoded to the correct 0.3 kWh little-endian. Issue wills106#2230 shows the opposite population: an X3-HAC 22kW Gen2 (ARM v2.03) follows the documented little order, so the global override inflated its lifetime total by a factor of 65536 (322.6 kWh -> 21,141,913.6). The definition is now split per generation: GEN1 keeps order32="big" (hardware-verified), GEN2 returns to the documented default order. Fixes wills106#2230 (word-order part).
Summary
Six fixes, each verified on real hardware with raw Modbus TCP reads cross-checked against the official protocol documents (X3 EVC 11kW, X3-Hybrid-G4 15kW at protocol 100, X3-MIC-G2). No entity renames, no unique_id changes; everything else explored during this work was deliberately left at upstream state.
1. EV charger cumulative energy — wrong word order
Charge Added - Cumulative(input0x0010EQ_Total, u32) displayed 19,660.8 kWh on a charger with 0.3 kWh lifetime energy.19660.8= raw3 × 65536: the charger sends 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 at0x0619(which read 0.3 kWh correctly all along).Fix: adds an optional per-sensor
order32override toBaseModbusSensorEntityDescription— the read path already honored it viagetattr; only the dataclass field was missing — and setsorder32="big"oncharge_added_cum. → now reads 0.3 kWh, agreeing with its twin.2.
modbus_min=101gates — unreachable on real hardwareRegister
0x82reports the protocol version (100= V001.00), and no known device returns more than 100 — the101gate values conflated the protocol register with the document revision (see the version discussion on #2064). Entities gatedmodbus_min=101could therefore never be created on any known inverter.Raw register reads on a real protocol-100 X3-Hybrid-G4 confirm every affected register responds with sensible data:
400, 0— see pairing note below1— correctly reports the actually-connected EV charger0Fix: these gates relaxed to
modbus_min=100; the misleading dataclass comments ("document version, e.g. 102 for V001.02") corrected to reference the register-0x82 value. Entities keep their plugin enable/disable defaults.Peakshaving pairing note: the discharge limits existed as complementary variant pairs — an unscaled variant gated
modbus_max=100and X1/X3-scaled variants (X1: 1 / X3: 0.1, per the V1.02 gain column) gatedmodbus_min=101. Since V1.02 corresponds to protocol 100, the doc-scaled variants were unreachable and protocol-100 X3 devices used the unscaled variant — a 10× write-scale error per the doc. The pairs are re-split at the documented boundary: unscaled →modbus_max=99, X1/X3-scaled →modbus_min=100(V1.02: "Peak shaving mode discharging period power limit, W, gain X1: 1, X3: 0.1", registers 0x00EE/0x0153 and 0x00EF/0x0154).3. Grid Reactive Power L1/L2 — merged S16 pairs + unreachable documented registers
On protocol-100 devices
grid_reactive_power_l1/l2displayed ~26 Mvar: they were read as S32 from0xC0/0xC2, but raw register reads show protocol 100 serves independent per-phase S16 values at0xC0..0xC3— the S32 read merged total+L1 (and L2+L3) into one absurd number. Meanwhile the V1.02-documented registers0xDE/0xE0respond correctly at protocol 100 (raw-verified: 397/572 var) but were gatedmodbus_min=102— unreachable, since no device returns more than 100 from register 0x82.Fix: L1/L2 now follow the exact same layout L3 and Total already used — documented S32 (
0xDE/0xE0) for protocol ≥ 100, legacy S16 (0xC1/0xC2) for ≤ 99. Live result, with the built-in consistency proof:L1+L2+L3 = 1,114 ≈ Total — the phases now sum to the total.
The V1.00-derived
0xC0/0xC2S32 definitions are removed: the protocol-100 hardware test above confirms the documentation-error hypothesis from #2099 (the registers never moved; the V1.00 doc's S32-at-0xC0 claim was the error), so they cannot be correct on any protocol version. After this PR the gate census contains only the two real-world protocol populations:modbus_max=99(legacy firmware reporting 5/7) andmodbus_min=100(V001.00+, all current devices).4. "PV Power Total" duplicate removed (MIC GEN2 only)
pv_total_power("PV Total Power", disabled by default) computed the exact samevalue_function_pv_power_totalas the existingpv_power_total("PV Power Total") - users saw two entities with identical values. The redundantpv_total_poweris removed.Correction (review feedback): an earlier revision of this PR also removed the HYBRID GEN4/5/6 register-based
pv_power_total(input0x32), misreading it as a same-key duplicate of the computed variant. That was wrong - the two definitions are a complementary pair (computed fallback gatedmodbus_max=99, documented PvPowerSum register gatedmodbus_min=100) and never coexist at runtime. The definition is restored untouched; its disabled-by-default flag is being fixed separately in #2181. Verified on protocol-100 hardware that the register works: input0x32(U32) = 6858 W vs PV1+PV2 = 6874 W read moments apart.5. MIC GEN2 meter phase powers — not served by the firmware (default-disabled)
measured_power_l2displayed 149 MW on an 8 kW inverter. Two rounds of hardware evidence:Doc analysis: the MIC-G2 V3.1 register table is internally inconsistent for the
0x70xblock — the address column places FeedinPower R/S/T at0x704-5 / 0x706-7 / 0x708-9(the current primary sensors) while the description parentheticals point one register higher (the_altsensors), and the PowerToEV row's parenthetical points at a different variable entirely. The doc alone cannot settle the addresses.Meter-attached test (decisive): the DTSU meter was physically moved from the X3-Hybrid to the MIC's RS485 (auto-detected, no reconfiguration). With a live meter, the region never serves per-phase power at any offset:
The block has no address decoder at all — the firmware streams whatever internal frame is current, regardless of the requested offset or length:
measured_power0x43B exactly, tracked -0.4 to -2.3 kW live).Without a meter the same window echoes stale samples, which is where the giant readings came from: the observed
146,278,598 Wis the real ~2.2 kW total echoed into both halves of one S32 (2232 * 65537 = 146,278,584). Function 0x03 (holding) reads of the block are rejected entirely.Update — MIC-G2 protocol doc V4.0 (2025-03, firmware 1.33) settles both open questions:
0x700-0x70Ablock now carries the annotation "These registers are only used by EVC" — it is the inverter-to-EV-charger coordination interface (RefPowerToEV / PowerToEV / PvRef / FeedinPower phases), not a general meter-data area. That is why it serves garbage in normal operation.0x701-0x702, parenthetical claiming0x700= RefPowerToEV) proves the parentheticals carry a systematic -1 typo, making the address column authoritative: FeedinPower R/S/T = Int32 at0x704-5 / 0x706-7 / 0x708-9.Fix:
_altsensors (measured_power_l1/l2/l3_alt) are removed — they read Int32 at the typo offsets (0x705/0x707/0x709), overlapping the real register pairs; a 32-bit register cannot have a valid twin one address over, and live testing shows both alignments return the identical echoed buffer anyway.0x704/0x706/0x708, Int32 per the authoritative address column) and, together withmeasured_power_2, stayentity_registry_enabled_default=False: a meter-attached hardware test shows this firmware never serves per-phase values in the block (the handler echoes one internal sample at every offset — the source of the 149 MW readings). The meter values that do work on MIC-G2 — all verified live with the meter attached — aremeasured_power(0x43B, doc "Feed In Power", Int32) and the grid counters (0x43D "Feed In Energy" / 0x43F "Consume Energy", Uint32 0.01 kWh). These three are now enabled by default (same reasoning as PV Power Total was disabled by default #2181: correct, useful sensors should not hide) — without a meter they read clean zeros, not garbage. All 32-bit registers in the MIC map were cross-checked against V4.0 signedness: everyREGISTER_S32/REGISTER_U32matches the documented Int32/Uint32. Whether any firmware serves the per-phase block is pending with SolaX support; if they confirm it is EVC-exclusive, the primaries can be removed in a follow-up.Feed-in Power L1/L2/L3, from V4.0's FeedinPower_x_phase; R/S/T mapped to the integration-wide L1/L2/L3 convention; the X1 single-phase variant becomesFeed-in Power L1) so they are no longer confusable with the working meter totals. Keys are unchanged, so existing registry entries and unique_ids are unaffected.6. MIC GEN2 "Q Curve" select always showed unknown
The Q Curve select's readback sensor (holding
0x347) was decoded as a plain Uint16, but the MIC-G2 V3.1 doc defines this register as byte-packed:QCurve_SetMode(0–5) in the low byte,bQCurve_SetPf(gain 0.01) in the high byte. A real X3-MIC-G2 returns25600 = 0x6400(mode 0 = Off in the low byte, Pf 1.00 in the high byte); the full word never matches the 0–5 option map, so the select displayed unknown on every MIC-G2 regardless of the actual mode.Fix: the readback now uses
REGISTER_U8L(low byte), verified on hardware to decode to the correct mode.