WIP: 🐛 Fix device_id key type mismatch that broke ALL stream routing (streaming, critical) #62

Manually merged
mat merged 0 commits from fix/device-id-key-type into main 2026-07-14 17:11:21 +00:00
Owner

Critical fix found by a live test deploy on real hardware (HAOS, real Comwatt account). Stacked on #61.

The bug

_fold_capacity_map stored str(device_id) in capacity_map, but coordinator.data["devices"] / ["switches"] and _energy_state are keyed by the RAW device["id"] (INT, from the real API). So streamed measurements routed via capacity_map produced str keys ('23593') that never matched the int-keyed dicts → the WebSocket stream silently updated NOTHING (power, energy, switch). Only the polling path (consistent int keys) worked, which is why power showed values but energy never rose from the stream.

Why unit tests missed it

Tests used STRING device ids everywhere, self-consistently — str(129443) == "129443" matched the string-keyed test data by accident. The real API uses int ids on both get_devices and get_connected_objects, so the accidental match never happens in production.

Fix (2 lines)

  • _fold_capacity_map: str(device_id)device_id (raw, matches leaf["id"]).
  • annotation: dict[str, tuple[str, str, bool]]dict[str, tuple[Any, str, bool]].

Tests

  • New regression test_stream_updates_device_with_integer_id: int device_id=23600 end-to-end (async_setup → stream push → assert data["devices"][23600]["power"] == 161.0). RED before (asserts None==161), GREEN after.
  • Realigned 4 tests to INT device ids (reflecting the real API): capacity_map content assertions, SWITCH_DEVICE_ID, two consumer end-to-end tests. Pure-function routing tests left as-is (self-consistent).
  • 74/74 passing, ruff + mypy clean.

Live verification

Deployed to a real HAOS box: after this fix, streamed power/energy/switch update in real time and HA values match the Comwatt live stream exactly (solaire 1802 W, injection 925 W, soutirage 932 W, clim 696 W… all identical), energy accumulates via ∫W·dt (solaire +7.3 Wh in 15 s @ 1786 W = correct).

Note

Root cause is in #56 (_fold_capacity_map); the one-line fix could be squashed down into #56 when finalizing the stack. Kept as a dedicated PR here for visibility of the bug + regression test.

**Critical fix found by a live test deploy on real hardware** (HAOS, real Comwatt account). Stacked on #61. ## The bug `_fold_capacity_map` stored `str(device_id)` in `capacity_map`, but `coordinator.data["devices"]` / `["switches"]` and `_energy_state` are keyed by the RAW `device["id"]` (INT, from the real API). So streamed measurements routed via `capacity_map` produced str keys (`'23593'`) that never matched the int-keyed dicts → **the WebSocket stream silently updated NOTHING** (power, energy, switch). Only the polling path (consistent int keys) worked, which is why power showed values but energy never rose from the stream. ## Why unit tests missed it Tests used STRING device ids everywhere, self-consistently — `str(129443) == "129443"` matched the string-keyed test data by accident. The real API uses int ids on both `get_devices` and `get_connected_objects`, so the accidental match never happens in production. ## Fix (2 lines) - `_fold_capacity_map`: `str(device_id)` → `device_id` (raw, matches `leaf["id"]`). - annotation: `dict[str, tuple[str, str, bool]]` → `dict[str, tuple[Any, str, bool]]`. ## Tests - New regression `test_stream_updates_device_with_integer_id`: int `device_id=23600` end-to-end (async_setup → stream push → assert `data["devices"][23600]["power"] == 161.0`). RED before (asserts None==161), GREEN after. - Realigned 4 tests to INT device ids (reflecting the real API): capacity_map content assertions, `SWITCH_DEVICE_ID`, two consumer end-to-end tests. Pure-function routing tests left as-is (self-consistent). - 74/74 passing, ruff + mypy clean. ## Live verification Deployed to a real HAOS box: after this fix, streamed power/energy/switch update in real time and HA values match the Comwatt live stream **exactly** (solaire 1802 W, injection 925 W, soutirage 932 W, clim 696 W… all identical), energy accumulates via ∫W·dt (solaire +7.3 Wh in 15 s @ 1786 W = correct). ## Note Root cause is in #56 (`_fold_capacity_map`); the one-line fix could be squashed down into #56 when finalizing the stack. Kept as a dedicated PR here for visibility of the bug + regression test.
🐛 Fix device_id key type mismatch that broke all stream routing
All checks were successful
Validate / lint-ruff (pull_request) Successful in 7s
Validate / test-pytest (pull_request) Successful in 3m6s
Validate / type-check-mypy (pull_request) Successful in 3m8s
Validate / lint-ruff (push) Successful in 7s
Validate / test-pytest (push) Successful in 3m3s
Validate / type-check-mypy (push) Successful in 3m8s
9d016f7d02
_fold_capacity_map was storing str(device_id) in capacity_map while
data["devices"], data["switches"], and _energy_state are all keyed by
the raw leaf["id"] value (int from the real Comwatt API). The str/int
mismatch caused every streamed measurement lookup to return None, so
the WebSocket stream silently updated nothing on real hardware.

Fix: drop the str() wrapper — store device_id raw.
Annotation: dict[str, tuple[str, str, bool]] → dict[str, tuple[Any, str, bool]].

Tests: add regression test (test_stream_updates_device_with_integer_id)
that was RED on str() code and GREEN after. Realign three end-to-end
tests that crossed str get_devices id with int get_connected_objects
deviceId (accidental match via str(int) == str): flip solar_device["id"]
and SWITCH_DEVICE_ID to int so both sides of the lookup agree.
mat changed target branch from fix/stream-robustness to main 2026-07-14 17:09:58 +00:00
mat manually merged commit 9d016f7d02 into main 2026-07-14 17:11:21 +00:00
mat referenced this pull request from a commit 2026-07-25 16:37:12 +00:00
mat referenced this pull request from a commit 2026-07-25 16:40:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mat/homeassistant-comwatt!62
No description provided.