Coerce numeric wallbox fields at the coordinator boundary (v0.0.8)#11
Merged
Conversation
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.
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.
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_KEYSmap in the coordinator and casts at the data-layer boundary:AMP,VOLT1–3,TEMP,CHARGINGPWR,HOUSEPWR,SOLARPWR,TOTALCHARGED,DAYHOUSE,DAYSOLARfloatPILOTLIMITint(semantically a discrete amp selector; Select entity matchesstr(value)against["6","7",…,"32"])Bad values log a warning and keep their original value instead of failing the whole refresh.
Updates
EXPECTED_STATESin 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. TheTODO(v0.0.8)note is now obsolete.Bumps
manifest.jsonversion to0.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.0cast paths but not the"13.5"→13.5path for active charging. A follow-up capture of an active-charging fixture would close that gap.Test plan
Tests,Validate with hassfest,Validate with HACS).pytestgreen locally.non-numeric statewarnings in HA logs.