Conversation
…o method for India backend Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… conversion Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve conflict in india.py: purely additive, both charging-current/ duration helpers (ours) and GPS-position mapping (upstream) kept. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ata1 basicVehicleStatus.extendedData1 has been confirmed (issue #318, and cross-checked against a second vehicle's own logs) to independently track HV battery SoC as a truncated whole percent, matching bmsPackSOCDsp/10 closely. The State of Charge sensor already fell back from bmsPackSOCDsp to extendedData1 when the charging field was missing or the -128 sentinel, but did not treat an exact 0 as suspect. In practice 0 on this raw field has only been observed as a stale/unpopulated reading, not a genuine 0% SoC, so it is now added to the reject list alongside -128, triggering the existing extendedData1 fallback. Adds regression tests for: charging SoC = 0 falls back to extendedData1, and charging SoC = None (missing field) falls back to extendedData1. Existing India tests confirm 0 is still accepted as a legitimate value on the fallback field itself.
nerlu's full logs (issue #318) show the MG3 Hybrid+ reports as vehicle_type=HEV, not PHEV. It's a self-charging hybrid with no charge port, so it correctly has no charging-endpoint data — but that also meant it fell outside the State of Charge sensor's eligibility gate entirely, which only covered BEV/PHEV. So the SOC sensor was never created for this vehicle, independent of the bmsPackSOCDsp=0 fallback fixed in the previous commit. Across nerlu's two log snippets, basicVehicleStatus.extendedData1 dropped from 78 to 73 while driving (currentJourneyDistance=50), consistent with genuine HV battery drain during hybrid operation rather than a static/sentinel value. HEV is now included in the SOC sensor's eligibility list, gated on Feature.CHARGING_DATA support (same requirement as PHEV) rather than added unconditionally. This keeps the sensor off the India backend: India's extendedData1 is repurposed to carry fuel_level rather than battery SoC, and INDIA_FEATURES does not advertise CHARGING_DATA, so the gate excludes it there. On the global backend, the sensor will have no charging data to read for a genuine HEV and will fall straight through to the extendedData1 fallback added previously. Adds regression tests: global HEV gets a SOC sensor sourced from extendedData1 with no charging data present; India HEV still gets no SOC sensor (fuel level only). 187 tests green.
Adds a Vehicle Profiles row for ZP22 EU (MG3 Hybrid+) explaining the self-charging HEV / extendedData1 SOC source added for #318, and qualifies the State of Charge sensor listing to note HEV availability on self-charging hybrids with no charge port. Bumps manifest version 1.2.6 -> 1.2.7-beta1 for the beta release.
…on-zero Fix/soc extended data fallback on zero
…#301) Field reports from SteveMSJ and joaommarques (MG4) on issue #301: - The since-charge counters (Mileage/Power Usage Since Last Charge) reset spuriously without an actual charge on some cars (already mitigated by #315's fallback), and are permanently 'Unknown' on others (e.g. some MGS5s). - More significantly: SteveMSJ compared the counter-derived energy against a SOC-drop x capacity calculation on a real 212-mile trip with no reset involved, and found the counter reads ~17% HIGH (59.1 kWh vs 50.3 kWh SOC- based; the car's own dash efficiency matched the SOC-based figure). joaommarques independently sees the same pattern on his MG4. Rather than picking a winner, this exposes both independently so users can compare across their own cars and trips before we decide whether to change the default. No behaviour change to any existing primary (unprefixed) value. 1. Last Trip Distance / Last Trip Efficiency: compute_completed_trip now always computes the counter-derived and odometer/SOC-derived figures independently (not just as a sequential fallback), and exposes full parallel attribute sets: - distance_km_counter / distance_mi_counter (raw counter delta, shown even when counter_reset_detected discarded it from the primary figure -- seeing the bogus value is itself useful) - distance_km_odometer / distance_mi_odometer (always available) - energy_kWh_counter + its 4 derived _counter efficiency/consumption figures (counter distance + counter energy, self-consistent) - energy_kWh_soc + its 4 derived _soc figures (odometer distance + SOC energy, self-consistent -- mirrors how Steve/Joao are already doing their own comparisons by hand) The existing primary distance_km/energy_kWh/efficiency_*/consumption_* keys are unchanged (still counter-preferred with odometer/SOC fallback), so nothing breaks for existing dashboards/automations. 2. New sensor: Efficiency Since Charge (SOC) -- an SOC/odometer-only alternative to Efficiency Since Last Charge, entirely independent of the mileageSinceLastCharge/powerUsageSinceLastCharge fields. Available on every BEV/PHEV regardless of whether those fields are reliable (MG4) or populated at all (some MGS5s) on a given car -- directly answers both users' reports. Its epoch boundary is 'battery % last seen to rise while parked' (a charge), tracked via new TripStatsManager.note_soc_reset_baseline, deliberately independent of the since-charge counter's own (unreliable) reset detection, and only evaluated while parked so a mid-drive regen SOC uptick can never be mistaken for a charge. New pure function compute_soc_since_reset_efficiency(); new persisted state trip_stats.soc_reset_baseline. Tests: parallel-figure computation (incl. Steve's exact 212mi numbers), raw-counter-value-shown-through-a-reset, missing-SOC-data, the new pure function (4 cases), and the new baseline-tracking method (3 cases). 197 tests green (was 187 before this change -- 10 new, 0 changed/removed). README updated. Bumps manifest to 1.2.7-beta2. Per discussion in #301: deliberately NOT changing which figure is primary -- just gathering comparable data. That decision is still open.
…rison feat(trip-stats): expose counter vs SOC/odometer figures side-by-side…
Pre-existing gap, unrelated to this branch's new keys: SAICMGLastTripSensor takes a with_attributes flag controlling whether it exposes the trip's full attribute dict at all. Last Trip Efficiency and Last Trip Fuel Economy were both instantiated with with_attributes=True; Last Trip Distance was not, so it has never carried ANY trip attributes (not just the new _counter/_soc ones added in this branch, but duration_s, start_ts/end_ts, everything). Found live: after this branch's changes, Last Trip Efficiency correctly showed the new distance_km_counter/_soc etc., but Last Trip Distance showed none of them -- same trip, so not a staleness issue, a genuine gap. One-line fix: with_attributes=True on the Last Trip Distance instantiation. 197 tests still green (no test previously covered this, none needed changing).
…rison Feat/soc based efficiency comparison
Beta's #322 gates PHEV/HEV SOC on CHARGING_DATA, relying on a comment that India does not advertise it. India does now — charging telemetry landed earlier in this branch — and India repurposes basicVehicleStatus.extendedData1 to carry fuel level, so a PHEV/HEV there would have shown litres of petrol as battery percent. Three of beta's own India tests caught it. STATE_OF_CHARGE_NON_BEV carries that meaning on its own, leaving CHARGING_DATA to mean only "has a charging endpoint". India omits the new feature, global keeps it, global behaviour unchanged. India BEVs also gain a Total Battery Capacity entity, gated on CHARGING_DATA like every other charging sensor. The charging frame omits totalBatteryCapacity in every capture so far, so it reads unknown rather than a fabricated number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Swap the string-matched "not available after polling" check for the typed ChargingStatusUnavailable the client now exports (john-lazarus/mg-ismart-india-client@511884c). An exhausted poll budget is still translated to None; every other MgIndiaApiError now propagates instead of being caught by an incidental message match. The India test stubs gain ChargingStatusUnavailable, or india.py fails to import under them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unprofiled P12L (MG IM5) was falling to DEFAULT_VEHICLE_PROFILE's fan_speed scheme, which maps remoteClimateStatus=2 (the car's actual cooling status) to fan_only -- the same #277 (MGS5) failure signature. Reported by tabannis: iSmart app showed AC on/20C/Auto (owner-confirmed in the car), while HA's Climate Ctrl card showed Mode 'Fan only' at 22C with an irrelevant Fan mode 'Medium' slider. Adds a P12L profile mirroring MIS3E's mode_select scheme: - climate_mode_cool / climate_status_cool = 2, CONFIRMED via the screenshot + logs (status-2-derived misread while genuinely cooling) - fan_only/heat/defrost/max_cool values inherited from MIS3E as best-effort, unconfirmed on this car - temperature range left at DEFAULT's 16-28 pending confirmation Deliberately does NOT set battery_capacity_kwh or charging_capacity_correction. The log shows the same bogus totalBatteryCapacity=725 placeholder seen on EC32/AS33P/S12L, and bmsPackVol suggests an 800V (100 kWh) pack, but the IM5 ships in three variants (75 kWh Standard Range/400V, 100 kWh Long Range/800V, 100 kWh Performance/800V) that 'P12L' alone can't distinguish -- same unresolved ambiguity as S12L/IM6 Premium vs Platinum (#53). Asked the reporter to confirm variant before adding a capacity/energy fix. Adds tests/test_vehicle_profiles.py::TestP12LClimate (profile resolution, mode_select scheme, status-2-is-cool regression guard, MIS3E-mirroring of unconfirmed values, and the deliberate absence of a capacity override). 165 tests green (2 pre-existing, unrelated import-ordering failures on beta HEAD reproduced before this change). Updates the Vehicle Profiles table in README.md.
feat(P12L): mode-select climate profile for MG IM5 (#326)
tabannis confirmed (#326 comments) their IM5 is the Long Range variant -- the 100 kWh NCM/800V pack -- resolving the earlier variant ambiguity that blocked a capacity fix in #327. Also confirmed: - no fan speed control in the app (mode_select scheme was correct) - 16-28C temperature range (matches what was already set) - 'Low'/'High' buttons are one-tap max-cool/max-heat presets, not a distinct wire status -- climate_mode_max_cool stays unconfirmed - AC-gates-heat quirk mirrors the MGS6 (James, same thread) -- supporting, not wire-confirmed, evidence for the inherited values Changes: - battery_capacity_kwh: None -> 100.0, mirroring S12L's handling of the same bogus totalBatteryCapacity=725 placeholder. No charging_capacity_correction (display-only override, same as S12L). - Added fan_speed_low/medium/high (1/2/3) for consistency with other mode_select profiles (MIS3E/MZS3E) -- unused under mode_select (no FAN_MODE feature exposed) but kept as a safe, explicit default rather than relying on the coordinator's .get() fallback. - README row updated: capacity corrected, caveat narrowed to the still-untested 75kWh Standard Range. Still unconfirmed and NOT changed here: fan_only/heat/defrost/max_cool status codes. These need a debug log captured with the AC confirmed on, which tabannis is sending separately -- this branch is left open for that follow-up commit. Tests: extended TestP12LClimate with capacity assertions mirroring TestIM6BatteryCapacity, plus a full-profile diff test (only the deliberately-changed fields may differ from DEFAULT) which caught the missing fan_speed_* keys above. 137 tests total (2 pre-existing, unrelated import-ordering failures on beta HEAD, same as before).
… correction; add Last Charge Energy (#262) Three issues reported by @HarryFlatter on an MG HS PHEV (AS33P), two of which reproduce on every car. 1. Efficiency Since Charge (SOC) was permanently Unknown — on all models, not just PHEVs (the entity is created for BEV and PHEV alike). Two stacked faults: the sensor passed the top-level status object into _extract_soc_pct/_extract_odometer_km, which expect basicVehicleStatus; SOC survived via its charging-data fallback, the odometer did not. That fallback then looked for `mileage` on chrgMgmtData, which has no such field — it lives on rvsChargeStatus — so it could never succeed. With the odometer always None the sensor returned no value at all. The soc_reset_baseline tracking itself was correct. 2. Power Usage Since Last Charge kept showing the raw ~3x figure on affected models. The charging_capacity_correction added in #310 sat inside the _NOT_CHARGING_ZERO_FIELDS branch, which powerUsageSinceLastCharge never enters (only lastChargeEndingPower is in that set), so it fell through to the generic numeric branch and was never applied. Hoisted into _apply_energy_correction(), called from both branches. The coordinator path and Efficiency Since Last Charge were already applying it correctly — only the standalone sensor was wrong. 3. New Last Charge Energy sensor: how much energy the last charge put INTO the battery. There is no lastChargeStartingPower in the API, so the session is measured across its boundaries, reporting the SOC-based figure as the headline value and the car's own pack-energy delta alongside it for comparison. A charging-data dropout is never treated as the end of a charge, since on some cars the charging endpoint goes quiet the instant a session completes. Fires mg_saic_charge_completed.
1.2.7-beta4 was already tagged and published on 27 Aug, from a commit whose manifest still read 1.2.7-beta3 — so the manifest on beta was one behind the released tag, and this branch's bump to beta4 would have created a second, different beta4. Skip to beta5.
…t-charge fix(sensors): unbreak Efficiency Since Charge (SOC) + the 3x energy correction, add Last Charge Energy (#262)
#262) Both of the bugs fixed in #330 shared a cause: rules that mattered were buried in code that no test could reach. The energy correction sat in one branch of a sensor's numeric handler, so it could be added to a branch the field never took and silently do nothing (#310). The odometer fallback lived inside a coordinator staticmethod, so it could point at a field that does not exist on that object and never fire, for as long as nobody noticed the sensor was blank. Moving both into logic.py — the existing pure, HA-free module — makes each rule directly testable and gives them a single home, so the coordinator's charge-session maths and the charging sensors can no longer drift apart. No behaviour change. 13 regression tests added covering the field list, the passthrough cases, the source preference and the fallback order, including an explicit test that chrgMgmtData carries no odometer.
…lpers-to-logic refactor: move the energy correction and odometer lookup into logic.py (#262)
feat(india): expose EV charging status sensors
… third tier The precedence has always been documented as user override > our profile > the API's own totalBatteryCapacity, and the Total Battery Capacity sensor implemented all three. But known_battery_capacity_kwh — which the charge-session maths, the SOC efficiency sensor and the SOC trip stats all read — only ever saw the first two. On a car with no profile entry that left a populated capacity sensor sitting next to three blank sensors derived from it. India made this visible (#302), but it was never an India problem: any unprofiled series behaved the same way. Capacity is now resolved in one place, logic.resolve_battery_capacity, returning both the value and its source. All four consumers read it, so the displayed pack size and the energy figures derived from it can no longer disagree. The API tier is guarded. 725 (-> 72.5 kWh) is a documented placeholder seen identically across EC32/AS33P/S12L, and it is plausible enough that a range check alone would pass it, so it is rejected by value; anything outside a wide plausibility band is rejected too. A rejected value yields no capacity rather than a fabricated one. This does change what an unprofiled car emitting the placeholder displays: 72.5 kWh becomes blank. That is the point — it was never a real pack size, and it was feeding energy figures. capacity_source is now reported rather than inferred. It previously guessed "profile" from known_battery_capacity_kwh being set, which would have mislabelled every API-derived value the moment that attribute gained an API tier — the exact confusion #301 added the attribute to prevent. 8 tests for the resolver. The India SOC test's stub coordinator now models the resolution the way the real one does.
…esolution feat(capacity): resolve battery capacity once, with the API as a real third tier
@HarryFlatter asked why Added Electric Range never populates. It exposes the API's chrgngAddedElecRng, which sits in chrgMgmtData alongside chrgngRmnngTime and chrgngSpdngTime — the live during-session block — so it is a counter that runs while charging and resets afterwards, not a record of the last charge. On the cars seen so far it reads 0 even mid-charge: verified against a five-hour AC charge that took a BEV from 36.9% to 80% with the sensor flat at 0.0 throughout. It reads 0 rather than unknown because the field is in _NOT_CHARGING_ZERO_FIELDS, which is correct for a live counter but hides the fact that it never carries data. Rather than resuscitate a field the car does not fill, the charge session now records the electric range at each boundary and reports the difference, from fuelRangeElec — which demonstrably works, since it drives the Electric Range sensor. Exposed as range_added_km on Last Charge Energy, with range_start_km and range_end_km alongside, and carried on the mg_saic_charge_completed event. A negative delta is dropped rather than reported (range can fall across a charge when a cold pack re-estimates) while the endpoints are kept, so the attributes still show what happened. 10 tests: range extraction and its -128 sentinel handling in test_logic, the session delta and storage round-trip in test_charge_stats.
Conflicts were all append-at-end collisions from the two branches adding different helpers to the same files: - logic.py: kept both resolve_battery_capacity (#332) and electric_range_km (#333) - tests/test_logic.py: kept both new test classes - manifest.json: kept 1.2.7-beta8, since beta7 is already released No logic reconciled — the two features are independent. The charge session now takes its capacity from #332's resolver and its range from #333's extractor, which is the intended combination.
…Range #332 shipped in beta7 without README changes. Three gaps: - capacity_source didn't mention that the figure can now be absent, which is what a car reporting the 725 placeholder will show. - The P12L/IM5 row said capacity was 'not yet corrected', implying 72.5 kWh is displayed. Since the guard landed it reads blank instead, which is a visible change for IM5 owners and needs saying. - Added Electric Range was listed with no indication that it is a live session counter that most cars never populate (#262).
Brings the battery-capacity half of #328 onto this branch so it ships in one release; #328 stays open for the climate codes still awaiting a debug log from tabannis. Changed from #328 as merged: 96.5 kWh, not 100.0. Profiles store USABLE capacity, not nominal pack size — AS33P is 23.2 against a 24.7 nominal for exactly this reason, and every energy figure derived from capacity is energy the owner can actually use. Published usable figures for the IM5 are 73.5 (75 nominal, Standard Range) and 96.5 (100 nominal, Long Range and Performance). Using 100.0 would overstate every derived figure by ~3.5%. Standard Range owners still land on the same P12L series code and will now get a capacity that is 31% too high rather than none at all, so the README row directs them to the capacity override until the variants can be distinguished. Also carried over from #328: the missing fan_speed_low/medium/high keys and the full-profile-diff test that caught them.
…, #53) Range added is now a first-class sensor, not only an attribute. Home Assistant converts sensor states to the user's unit system but never converts attribute values, so a UK user reading range_added_km got kilometres on a dashboard where every other range figure shows miles. The sensor declares DISTANCE in km and lets HA present it correctly; the attributes stay for templating and are documented as always-km. S12L (IM6) moves from 100.0 to 96.5, the usable figure for the 100 kWh NMC pack, matching the convention every other profile follows (AS33P is 23.2 usable against 24.7 nominal) and the P12L value set earlier on this branch. UK/EU sources put all IM6 variants on that pack; the Australian spec sheet's 75 kWh LFP Premium is noted in the profile comment, and if one ever reports S12L it needs 73.5 and its own split.
UK sources confirm the IM6 is sold on the 100 kWh NMC pack only, in Long Range and Performance — so S12L is unambiguous in these markets and the inherited trim caveat overstated the risk. Kept, scoped to other markets, since the Australian spec sheet does list a 75 kWh LFP Premium. Both IM rows in the README now state usable vs nominal explicitly, and the P12L row leads with the Standard Range warning rather than burying it.
feat(charge): report the range a charge added (#262)
…e After Charging 1.2.7-beta8 raises NameError on every poll for BEV/PHEV owners: _charge_snapshot calls electric_range_km, but the import was never added. The edit that should have added it was a string replacement against an import block that did not yet have the shape it assumed, so it silently matched nothing. py_compile passes — a missing global is a runtime error — and no unit test reaches the coordinator's poll path, so the whole suite stayed green over a broken integration. Estimated Range After Charging moves from bmsEstdElecRng to imcuChrgngEstdElecRng. bmsEstdElecRng does not track a projected range: on an MGS6 at 57% SOC showing 285 km with an 80% target it reported 761 km, against a ~400 km projection and more than the car manages from empty. imcuChrgngEstdElecRng read 410 against a 398 km projection on the same car, and imcuVehElecRng matches Electric Range exactly, confirming these fields are whole kilometres like the existing factor assumes. Values are now gated on the companion validity flag: a mid-charge capture with current flowing showed V=0 on chrgngRmnngTime while it reported a healthy 300 minutes, so 0 is the valid state. A non-zero flag holds the last good reading rather than publishing a stale one. A CI step running pyflakes to fail the build on undefined names is supplied separately — the token used here lacks workflow scope.
Added a step to check for undefined names using pyflakes before running unit tests.
…mated-range fix: NameError on every poll in beta8, and repoint Estimated Range After Charging
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 1.2.7 — charging, energy and battery capacity
Merges
betaintomainfor the 1.2.7 stable release. Consolidates nine betas: 26 non-merge commits since1.2.6.What's in it
New sensors
mg_saic_charge_completed.New backend capability
CHARGING_DATAand populate the shared charging sensors from the TAP protocol, fetched within the existing status poll.Fixes to sensors that never worked
Noneon every car: the sensor passed the top-level status object to extractors expectingbasicVehicleStatus, and_extract_odometer_km's charging fallback readchrgMgmtData, which carries nomileagefield at all._NOT_CHARGING_ZERO_FIELDSbranch, which that field never enters.bmsEstdElecRng, which doesn't track a projected range — 761 km reported against a ~400 km projection. Repointed toimcuChrgngEstdElecRngand gated on its validity flag.bmsPackSOCDsp=0fallback.Structural
logic.resolve_battery_capacity, returning(value, source), read by all four consumers. The API tier is now live — previously only the Total Battery Capacity sensor honoured it — with the725placeholder and implausible magnitudes rejected.apply_energy_correctionandodometer_kmmoved intologic.py. Both General Integration Questions #262 bugs shared a cause: the rule that mattered sat in code no test could reach.Profiles
P12L(IM5): climate profile ([ISSUE] MG IM5 Climate Control Mappings #326), capacity 96.5 kWh usable.S12L(IM6): capacity 100.0 → 96.5 kWh usable.User-visible changes to flag in the release post
725placeholder. Deliberate — it was never a real pack size and now feeds energy maths — but it's a number disappearing from people's dashboards.P12Lwith the Long Range. Override to 73.5 until the variants can be split.Testing
258 tests pass. Note that
test_backends.pyandtest_setup_and_config_flow.py(74 tests) only import whenmg-ismart-india-clientis installed, which CI does.Carried into stable as known limitations
chrgngAddedElecRngis unpopulated, andchrgngAddedElecRngV=0alongside a healthychrgngRmnngTimeconfirms 0 means valid — so the zero is genuine, not suppressed. Kept in case another model populates it.Follow-ups worth tracking after release
bmsPackVolreads ~1615 (404 V) on a 400 V car and 3032 (758 V) on the IM5 Long Range. 400 V vs 800 V is an architectural difference with enormous margin, so a threshold around 2400 would separate Standard Range from Long Range automatically and remove the override workaround.imcuChrgngEstdElecRngsiblings.imcuDschrgngEstdElecRngandimcuVehElecRngare unexposed and look useful.