Parse OCMF reading values serialized as JSON strings - #50
Merged
Conversation
The OCMF spec types RV as a JSON number but requires the exact numeric representation to survive parsing for signature verification, which is why some meter firmware emits it as a string. Coercing at the parse boundary accepts both representations.
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.
Zaptec-meterstanden waarvan het OCMF
RV-veld als JSON-string binnenkomt worden nu gewoon geparsed in plaats van de hele refresh van de charger te laten crashen.Root cause
Zaptec::MeterReading.build_readingdeelt hetRV-veld direct door de kWh-magnitude (lib/zaptec/meter_reading.rb:47). De parser neemt stilzwijgend aan datJSON.parsedaar een Numeric oplevert; komt het veld als string binnen, dan raistString#/eenNoMethodError. Er zit geen coercion op de parse-boundary.Waarom-keten
ChargePointRefreshJob? →build_readingroept/aan op een String (meter_reading.rb:47).RVeen String? → De charger serialiseert het reading-veld in zijn OCMFSignedMeterValueals JSON-string in plaats van number.RVals number, maar eist tegelijk dat de exacte numerieke representatie bewaard blijft voor handtekening-verificatie ("the representation must not be transformed by further handling methods (e.g. processing by JSON parser)"). Strings zijn daarvoor de robuuste uitweg die meter-firmware in de praktijk kiest. Dit is de grens van ons systeem: third-party data, wij bepalen de serialisatie niet.Waarom nu
De gem is sinds begin 2024 ongewijzigd op dit pad; de trigger is een runtime-verandering aan de charger-kant. Charge point 60487 (Zaptec Pro, in ons systeem sinds 2024-02-01) begon op 2026-08-11 om 10:35 UTC
RVals string te sturen — vrijwel zeker een OTA firmware/meter-update, die Zaptec zelf uitrolt. Sindsdien vuurt elke 5-minuten-poll vanChargePointRefreshJobhet event (579 events t/m 2026-08-13).Blast radius
Alle 579 Sentry-events dragen
charge_point_id: 60487— op dit moment precies één Zaptec Pro. Voor die charger faalt de volledige status-refresh (niet alleen de meterstand): geen sessie-updates, geen meter readings, geen online-status zolang de charger eenSignedMeterValuein deze vorm rapporteert. Naarmate Zaptec dezelfde firmware breder uitrolt, raakt dit elke API-connected Zaptec-charger; ook het archived-sessions-importpad (ArchivedSession#meter_readings) gebruikt dezelfdebuild_readingen zou op dezelfde payload breken.Gekozen oplossing
Float(reading.fetch(VALUE))inbuild_reading, zodat zowel numbers als string-representaties naar een Float coercen vóór de deling. Dit fixt why 4 — de diepste why die binnen ons systeem ligt (why 3 is vendor-gedrag dat de OCMF-spec zelf uitlokt).Float()blijft strikt: een niet-numerieke string raist alsnog, dus echte garbage blijft zichtbaar in plaats van stilletjes0.0te worden.Alternatieven overwogen
to_fop het veld: maakt van garbage stilletjes0.0en vannileen valide meterstand — verbergt echte datacorruptie.parse_all(zoals bijST != G): een string-RVis geen ongeldige reading maar een andere serialisatie van een valide waarde; skippen zou goede meterstanden weggooien.Reproductie
spec/zaptec/meter_reading_spec.rb— "supports reading values serialized as JSON strings" faalt vóór de fix met exact de productie-error (NoMethodError: undefined method '/' for an instance of Stringopmeter_reading.rb:47).Na merge is een
bundle update stekker_zaptecin StekkerWeb nodig om de fix naar productie te brengen; die bump-PR sluit stekker/backlog#2121.Sentry: https://sentry2.stekker.app/organizations/sentry/issues/2186/
Onderdeel van stekker/backlog#2121