Skip to content

Beta - #335

Merged
townsmcp merged 42 commits into
mainfrom
beta
Sep 1, 2026
Merged

Beta#335
townsmcp merged 42 commits into
mainfrom
beta

Conversation

@townsmcp

@townsmcp townsmcp commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Release 1.2.7 — charging, energy and battery capacity

Merges beta into main for the 1.2.7 stable release. Consolidates nine betas: 26 non-merge commits since 1.2.6.

What's in it

New sensors

  • Last Charge Energy (BEV/PHEV) — energy the last completed charge put into the battery, measured across the charging session, with SOC-derived and counter-derived figures side by side in the attributes. Fires mg_saic_charge_completed.
  • Last Charge Range Added (BEV/PHEV) — electric range the same charge put back. A first-class sensor rather than only an attribute, so HA unit conversion applies.
  • Efficiency Since Charge (SOC) — efficiency from battery percentage and odometer, independent of the car's since-charge counters.

New backend capability

Fixes to sensors that never worked

  • Efficiency Since Charge (SOC) returned None on every car: the sensor passed the top-level status object to extractors expecting basicVehicleStatus, and _extract_odometer_km's charging fallback read chrgMgmtData, which carries no mileage field at all.
  • Power Usage Since Last Charge ~3× high on AS33P: fix(phev): correct powerUsageSinceLastCharge inflation on HS PHEV (#262, #301) #310's correction sat inside the _NOT_CHARGING_ZERO_FIELDS branch, which that field never enters.
  • Estimated Range After Charging read bmsEstdElecRng, which doesn't track a projected range — 761 km reported against a ~400 km projection. Repointed to imcuChrgngEstdElecRng and gated on its validity flag.
  • Last Trip Distance exposed no attributes.
  • HEV State of Charge ([ISSUE] MG3 Hybrid+ – HV battery #318) and the stale bmsPackSOCDsp=0 fallback.
  • Non-BEV India SOC gated on its own feature rather than on charging-data support.

Structural

  • Battery capacity resolved once in 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 the 725 placeholder and implausible magnitudes rejected.
  • apply_energy_correction and odometer_km moved into logic.py. Both General Integration Questions #262 bugs shared a cause: the rule that mattered sat in code no test could reach.
  • CI now fails on undefined names via pyflakes.

Profiles

User-visible changes to flag in the release post

  • Total Battery Capacity goes blank on unprofiled cars reporting the 725 placeholder. Deliberate — it was never a real pack size and now feeds energy maths — but it's a number disappearing from people's dashboards.
  • IM5 Standard Range reads 96.5 kWh, about a third high, since it shares P12L with the Long Range. Override to 73.5 until the variants can be split.

Testing

258 tests pass. Note that test_backends.py and test_setup_and_config_flow.py (74 tests) only import when mg-ismart-india-client is installed, which CI does.

Carried into stable as known limitations

  • Added Electric Range reads 0 everywhere. chrgngAddedElecRng is unpopulated, and chrgngAddedElecRngV=0 alongside a healthy chrgngRmnngTime confirms 0 means valid — so the zero is genuine, not suppressed. Kept in case another model populates it.
  • Estimated Range After Charging is confirmed on one vehicle. A PHEV confirmation would be worth having.
  • India energy sensors stay blank — no capacity resolves for series codes absent from the profile table.
  • IM5 fan-only/heat/defrost unconfirmed; feat(P12L): confirm battery capacity 100kWh for Long Range (#326) #328 remains open for the climate codes pending a debug log.

Follow-ups worth tracking after release

  • Split IM5/IM6 variants by pack voltage. bmsPackVol reads ~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.
  • Expose imcuChrgngEstdElecRng siblings. imcuDschrgngEstdElecRng and imcuVehElecRng are unexposed and look useful.

Dr-Blank and others added 30 commits August 19, 2026 09:49
…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
townsmcp and others added 12 commits August 31, 2026 10:41
@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
@townsmcp
townsmcp merged commit 740ad91 into main Sep 1, 2026
7 checks passed
@townsmcp
townsmcp deleted the beta branch September 1, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants