Skip to content

v0.4.1 — RFID wait model (event-driven, no timeout)

Choose a tag to compare

@cryptotomte cryptotomte released this 25 May 06:40
5a7e88c

Hotfix for the v0.4.0 RFID grace timer

v0.4.0 introduced a 5-second RFID grace timer that committed user=Unknown if no blip arrived within the deadline. Real-world validation on 2026-05-24 showed the actual go-e trx propagation latency can reach 7 seconds, exceeding the timer and recreating the user=Unknown failure the grace timer was designed to prevent.

v0.4.1 replaces the timer with an event-driven wait that resolves on one of three triggers — trx arrives, power begins flowing, or plug is removed — with no timeout. The user can blip whenever they like; the session is attributed correctly regardless of how long it took.

What's new

  • Plug-then-blip is now timeout-free. Plug in the cable, blip the RFID tag at any time (5 seconds, 30 seconds, 5 minutes — doesn't matter), and the session is attributed to the matched user. No Unknown fallback unless the user actually unplugs without blipping.
  • Two new visible sub-states on the Status sensor for the pre-session waiting periods:
    • waiting_for_rfid (Swedish: "Väntar på RFID", English: "Waiting for RFID") — cable plugged in, awaiting RFID blip.
    • waiting_for_plug (sv: "Väntar på kabel", en: "Waiting for cable") — RFID blipped, awaiting cable insertion.
  • Open-access charger inference. If a charger configured for open access begins delivering power without an RFID blip, the integration records the session as Unknown with reason open_access_inferred so no energy is lost. (Existing trx=0 fast path also preserved.)
  • Renamed waiting sub-state → initializing (sv: "Initierar laddning", en: "Initializing") to avoid name collision with the two new waiting states.

Internal improvements

  • UnknownReason StrEnum replaces six bare-string UNKNOWN_REASON_* constants — type-safe diagnostic reasons for Unknown sessions (trx_was_null, trx_was_zero, rfid_unmapped, rfid_inactive, rfid_type_error, open_access_inferred).
  • _RfidWaitState dataclass is now frozen=True with three documented invariants.
  • _async_start_session body wrapped in try/except with persistent-notification on error — engine no longer fastnar in a ghost TRACKING state with no session if Session() construction raises.
  • _dispatch_update() now fires on every trx-change event (not only on session start), so the new waiting_for_plug sub-state reaches the dashboard immediately.
  • Race-condition decision documented in code (research §R3 first-fire-wins for trx vs. power triggers).
  • IC-5 state-machine asymmetry (waiting_for_rfid ⇒ TRACKING; waiting_for_plug ⇒ IDLE) documented in get_status_sub_state with rationale.
  • All WAITING enum references renamed to INITIALIZING; all RFID grace comments updated to RFID wait.
  • 7 PR-22/PR-23 test fixtures cleaned up (removed CONF_RFID_GRACE_SECONDS: 0 opt-outs; adapted to event-driven semantics).

Configuration changes

  • Removed rfid_grace_seconds option (no longer needed; event-driven model has no configurable duration).
  • HA tolerates stale rfid_grace_seconds: N keys in existing entry.options — no migration required, no warning.

Multi-agent review

Five specialized review agents flagged 25 findings (8 blocking + 9 should-fix + 8 nice-to-have); all resolved across four fixer-agent passes plus a final code-simplifier polish. Final verification confirmed PROCEED TO COMMIT.

Migration

None required.

  • Session-store schema unchanged (v1.2 — PR-23 FR-029 still applies).
  • entry.options keys are tolerated on load; obsolete rfid_grace_seconds keys are silently ignored.
  • No new dependencies.

Test coverage

547 unit tests passing (was 535 on v0.4.0). Net change:

  • +14 in new tests/test_session_engine_rfid_wait.py (event-driven scenarios)
  • −7 deleted from tests/test_session_engine_rfid_grace.py (PR-23 grace timer)
  • +6 in new tests/test_status_sensor_sub_states.py (sub-state visibility)
  • +4 across edited files (charging-duration unavailable, race no-op, restart-mid-wait, etc.)
  • Net +17 new tests; net +12 after the −7 deletion and minor consolidations.

Scope

Affects only the goe_gemini charger profile. The legacy generic profile keeps its SessionEngine (no waiting sub-state support). Migration to plug-anchored model for generic profile is explicitly out of scope.

Known follow-up

When pre-restart sub-state was charged (charging completed, cable still in) and HA restarts followed by unplug without resuming charging, charging_duration_s becomes 0 because the window-tracker doesn't reconstruct closed windows from the persisted session.windows[] snapshot. PR-23's synthetic-window guard prevents the previous inflation bug (Elvis 9.5 h scenario); full preservation needs a follow-up to load persisted windows into the tracker at recovery. Carried over from PR-23 review.