Skip to content

Coerce numeric wallbox fields at the coordinator boundary (v0.0.8)#11

Merged
nanomad merged 4 commits into
mainfrom
fix/v0.0.8-numeric-coercion
May 13, 2026
Merged

Coerce numeric wallbox fields at the coordinator boundary (v0.0.8)#11
nanomad merged 4 commits into
mainfrom
fix/v0.0.8-numeric-coercion

Conversation

@nanomad

@nanomad nanomad commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

The wallbox API returns power/current/energy fields as JSON strings in some states (CHARGINGPWR: "0.00" at rest, AMP: "13.5" while charging). Passing them through unchanged left HA's recorder coercing strings to floats every cycle — with a per-cycle warning — and made Energy dashboard math silently depend on that coercion succeeding for every reading.

This PR adds a typed _NUMERIC_KEYS map in the coordinator and casts at the data-layer boundary:

Key Cast
AMP, VOLT13, TEMP, CHARGINGPWR, HOUSEPWR, SOLARPWR, TOTALCHARGED, DAYHOUSE, DAYSOLAR float
PILOTLIMIT int (semantically a discrete amp selector; Select entity matches str(value) against ["6","7",…,"32"])

Bad values log a warning and keep their original value instead of failing the whole refresh.

Updates EXPECTED_STATES in the contract test: int "0""0.0" for AMP/VOLT*/DAYSOLAR, string "0.00""0.0" for CHARGINGPWR/TOTALCHARGED. HOUSEPWR/SOLARPWR/TEMP/DAYHOUSE state values don't visibly change but the underlying type is now uniformly float. The TODO(v0.0.8) note is now obsolete.

Bumps manifest.json version to 0.0.8.

Known coverage gap

The committed fixture captures the wallbox at rest (STATUS: "SCHEDULE", all power values at 0). It exercises the int→float and "0.00"0.0 cast paths but not the "13.5"13.5 path for active charging. A follow-up capture of an active-charging fixture would close that gap.

Test plan

  • CI green (Tests, Validate with hassfest, Validate with HACS).
  • pytest green locally.
  • After install on a live wallbox: no non-numeric state warnings in HA logs.
  • Energy dashboard: Charged kWh, Daily House Wh, Daily Solar Wh continue to record stats and the chart still draws.
  • Select entity "Select Chargepoint Power AMPS" current option matches the wallbox's reported limit.

nanomad added 4 commits May 13, 2026 20:59
The wallbox API returns power/current/energy fields as JSON strings in
some states (CHARGINGPWR "0.00" at rest, AMP "13.5" while charging).
Passing them through unchanged left HA's recorder coercing strings to
floats every cycle (with a per-cycle warning) and made Energy
dashboard math depend on that coercion succeeding for every reading.

Adds a typed numeric-key map in the coordinator: power/current/voltage/
energy/temperature fields cast to float, PILOTLIMIT cast to int (it's
a discrete amp selector that the Select entity matches via str(value)
against an integer-string options list). Bad values log a warning and
keep their original value instead of failing the whole update.

Updates EXPECTED_STATES in the contract test: int "0" -> "0.0" for AMP/
VOLT*/DAYSOLAR, string "0.00" -> "0.0" for CHARGINGPWR/TOTALCHARGED.
HOUSEPWR/SOLARPWR/TEMP/DAYHOUSE state values are unchanged but their
underlying type is now uniformly float. PILOTLIMIT stays at "25" since
it's an int. The TODO(v0.0.8) note is now obsolete.

Bumps manifest version to 0.0.8.
- PILOTLIMIT caster goes through float() so a stringified "25.0" from
  the wallbox truncates to 25 instead of raising ValueError and
  silently failing the Select's str-comparison lookup.
- _coerce_numeric now returns a new dict instead of mutating the input
  and returning it.
- Add tests/test_coordinator.py with direct unit tests for
  _coerce_numeric (string-and-int cast, stringified-float for
  PILOTLIMIT, bad-value warning, no input mutation, None skip,
  non-numeric passthrough). Decouples the cast contract from the HA
  setup pipeline and closes the active-charging coverage gap by
  exercising the "13.5" -> 13.5 path directly.
Dict equality treats 0 == 0.0 as True, so the previous assertions
verified the value but silently accepted the wrong type. isinstance
locks the actual int/float contract — catches a regression where a
future "just to be safe" change wraps the cast in str() or drops the
int -> float lift.
Putting back the target-type field in the warning so a debugger sees
both the offending key/value and what we tried to cast to. _to_int
sets __name__ = "int" so the log reads "to int" rather than the
internal helper name.

Tightens the warning test to verify key, value, and target type all
appear in caplog so future drift in the message format fails fast.
@nanomad nanomad merged commit 3ae8eac into main May 13, 2026
3 checks passed
@nanomad nanomad deleted the fix/v0.0.8-numeric-coercion branch May 13, 2026 19:12
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.

1 participant