|
| 1 | +# Exposing all decoded diagnostics in Home Assistant (design) |
| 2 | + |
| 3 | +Goal: surface every decoded diagnostic (compressor Hz, capability flags, link health, per-fault |
| 4 | +detail) in Home Assistant, zero cloud. This supersedes the open-ended notes in `docs/01` and the |
| 5 | +"telemetry gaps" tracked in issues #38/#39. Verified against the live Pi stack (HA 2026.6.4 + |
| 6 | +`python-matter-server` 8.1.0), 2026-07-22. |
| 7 | + |
| 8 | +## The four diagnostics and where they stand today |
| 9 | + |
| 10 | +| diagnostic | decoded at | exposed today | count | |
| 11 | +|---|---|---|---| |
| 12 | +| compressor Hz | `hisense_rs485.cpp:714` (`buf[42]`) | only coarsely via `ThermostatRunningState` -> `hvac_action`; raw Hz nowhere | 1 scalar | |
| 13 | +| capability flags | `hisense_rs485.cpp:246` (`HisenseFeatures`) | `:2323` console only | **15** fields | |
| 14 | +| per-fault detail | `HisenseFaults` (bytes 39/40/64/66) | aggregate `BooleanState` on ep10 + `:2323` | **18** named bits | |
| 15 | +| link health (#56) | `on_link()` liveness nulling | standard liveness attrs go `unavailable` in HA | 1 bool | |
| 16 | + |
| 17 | +Note the corrected counts: **18** fault bits (not 19) and **15** capability fields (not 14), per a |
| 18 | +direct read of `hisense_rs485.h`. Size any packed bitmap off these. |
| 19 | + |
| 20 | +## The crux: why the "obvious" path is a dead end here |
| 21 | + |
| 22 | +The natural idea, register the mfg cluster `0xFFF1FC00` in `python-matter-server`'s |
| 23 | +`custom_clusters.py` and let HA render it, does **not** work on this deployment, confirmed both by |
| 24 | +source and by live inspection: |
| 25 | + |
| 26 | +1. **HA no longer uses `python-matter-server` as its client.** HA 2026.6.4's `matter` integration |
| 27 | + pins `matter-python-client==0.7.1` (from the separate `matter-js/matterjs-server` project), |
| 28 | + which ships its own diverged `custom_clusters.py` (7 vendor clusters, none ours). The Pi's |
| 29 | + Matter controller is still the `python-matter-server` 8.1.0 docker container. These are two |
| 30 | + independently maintained files in two upstream projects. Patching the `matter-server` |
| 31 | + container's copy has zero effect on what HA renders. |
| 32 | +2. **HA's native rendering needs a hardcoded schema.** `homeassistant/components/matter` creates |
| 33 | + entities only from hardcoded `MatterDiscoverySchema` objects referencing specific typed |
| 34 | + cluster/attribute classes. `discovery.py` has **no** generic "unknown custom cluster -> |
| 35 | + diagnostic entity" fallback. A vendor cluster with no matching schema yields zero entities. |
| 36 | +3. So **native** HA entities from `0xFFF1FC00` require two out-of-repo PRs (a cluster class in |
| 37 | + `matter-python-client` **and** a `MatterDiscoverySchema` in `home-assistant/core`). Not |
| 38 | + shippable from this repo. The "free" standard-cluster alternatives were checked against the |
| 39 | + *installed* `matter/sensor.py` and are also dead ends: `ElectricalPowerMeasurement.Frequency`, |
| 40 | + `GeneralDiagnostics.Active*Faults`, and `OperationalError.errorStateLabel` all have **no** |
| 41 | + discovery schema (or map only 4 spec error IDs). Do not re-tread these. |
| 42 | + |
| 43 | +## The clean path (no upstream PR, empirically proven) |
| 44 | + |
| 45 | +matter-server stores **any** device-reported attribute keyed by a plain numeric path, |
| 46 | +`"<endpoint>/<cluster_id_dec>/<attr_id>"`, with **no** cluster registration required. Verified |
| 47 | +live: nodes 14/35/62 already carry the Hisense cluster's Eco/Turbo/Mute/SleepProfile at |
| 48 | +`"<ep>/4294048768/{0,1,2,3}"` (`0xFFF1FC00` = `4294048768`) in the matter-server node store, |
| 49 | +despite that cluster never being registered anywhere. `CompressorHz` (16) / `OutdoorTemp` (17) are |
| 50 | +confirmed **absent** from every node's `attributeList` (declared in the XML, not compiled into the |
| 51 | +`.zap`). |
| 52 | + |
| 53 | +So the answer: **compile the attributes into the existing cluster, then read them raw from |
| 54 | +matter-server's WebSocket API in our own HACS integration.** This bypasses both the Pi-side and |
| 55 | +HA-side custom-cluster machinery entirely. |
| 56 | + |
| 57 | +## Recommended architecture (phased) |
| 58 | + |
| 59 | +**Phase 0 (done):** outdoor temp (ep2), coil temp (ep8), aggregate fault (ep10), ActivePower |
| 60 | +already ship via standard clusters. Keep as-is. |
| 61 | + |
| 62 | +**Phase 1 (cheapest, ship first, ~zero firmware/GUI):** |
| 63 | +- **link health** -> an HA Template Binary Sensor (or one small class in `hisense-unified-ac`) |
| 64 | + watching an existing entity's state `not in (unavailable, unknown)`. The #56 nulling already |
| 65 | + drives the availability transition. No firmware, no GUI, no Pi change. |
| 66 | +- **`Thermostat.FeatureMap` cool/heat** -> `Set()` the `kHeating`/`kCooling` bits from |
| 67 | + `cool_heat` in `matter_drivers.cpp`. The FeatureMap is already `included`/RAM-backed in the |
| 68 | + `.zap` (no GUI change), and it is a genuine native `hvac_modes` improvement. |
| 69 | + |
| 70 | +**Phase 2 (firmware + ZAP GUI, one rebuild/flash pass, NO new endpoint):** on the **existing** ep1 |
| 71 | +`HisenseAircon` cluster (`0xFFF1FC00`, already attached): |
| 72 | +- tick `included` on the already-declared `CompressorHz` (`0x0010`); |
| 73 | +- add two new `bitmap32` attributes: `Features1` (`0x0012`, the 15 capability fields) and |
| 74 | + `Faults1` (`0x0013`, the 18 fault bits); |
| 75 | +- mirror in the `HisenseAircon-ClusterId.h` / `zzz_generated` edit; wire the writes in |
| 76 | + `matter_drivers.cpp` at the existing decode sites (features `:246`, compressor Hz `:714`, faults |
| 77 | + `~1602`). Because these attach to an already-attached cluster, the contiguous-endpoint boot-crash |
| 78 | + rule does not newly apply; a standard full rebuild + boot-check on **both** flavours (AmebaZ2 + |
| 79 | + ESP32) still applies. Verify the raw values land in the matter-server node store before writing |
| 80 | + any HA-facing code. |
| 81 | + |
| 82 | +**Phase 3 (HACS integration, parallelizable once Phase 2's `.zap` lands):** in |
| 83 | +`integrations/hisense-unified-ac`: |
| 84 | +- add `matter-python-client` as a `manifest.json` requirement (the same client HA already runs |
| 85 | + against this matter-server, so it is proven interoperable); |
| 86 | +- new `config_flow` fields (`matter_ws_url`, `node_id` per device); |
| 87 | +- a coordinator: `read_attribute()` once + `subscribe_events(ATTRIBUTE_UPDATED)` thereafter for the |
| 88 | + raw paths (no polling needed, firmware reports on change via the existing wildcard subscription); |
| 89 | +- `sensor.py` (compressor Hz, unit Hz, `state_class=measurement`) and `binary_sensor.py` (18 |
| 90 | + fault-bit entities `device_class=problem`, plus optional capability entities), decoding `Faults1` |
| 91 | + / `Features1` client-side. Keep the bit map in `const.py` in lockstep with `hisense_rs485.h`, with |
| 92 | + a host-side test asserting agreement (precedent: the HisenseFeatures naming-bug guard). |
| 93 | + |
| 94 | +**Phase 4 (optional, only if native `matter`-integration entities are specifically wanted):** file |
| 95 | +a `matter-python-client` cluster-class PR + a matching `home-assistant/core` `MatterDiscoverySchema` |
| 96 | +PR. Not required; Phases 1-3 already cover all four diagnostics end to end with full fidelity. |
| 97 | + |
| 98 | +## Task split (who does what) |
| 99 | + |
| 100 | +**ZAP GUI (manual):** open `room-air-conditioner-app.zap` (and the ESP32 esp-matter app for |
| 101 | +parity) via the `run_zaptool.sh` recipe in CLAUDE.md; on the **existing** ep1 `Hisense Aircon` |
| 102 | +cluster, tick `CompressorHz` (`0x0010`) included and add the two `bitmap32` attributes `Features1` |
| 103 | +(`0x0012`) + `Faults1` (`0x0013`), matching the storage pattern of Eco/Turbo/Mute/SleepProfile. No |
| 104 | +new endpoints. Re-capture into `firmware/src/sdk-edits/` per `sdk-edits/README.md`. |
| 105 | + |
| 106 | +**Firmware:** the `matter_drivers.cpp` write glue for the three new attributes + the Phase-1 |
| 107 | +FeatureMap `Set()`; build/package/flash both flavours; re-interview both nodes. |
| 108 | + |
| 109 | +**Pi side:** **no** matter-server or HA-core change. Only the standard post-OTA re-interview so |
| 110 | +matter-server's descriptor read picks up the new attributes, plus a normal HACS install of the |
| 111 | +updated integration into the bind-mounted `custom_components/`. Do not edit any container's |
| 112 | +`site-packages` (not bind-mounted, wiped on image update). |
| 113 | + |
| 114 | +## Hard blockers (documented so they are not re-attempted) |
| 115 | + |
| 116 | +- Native `homeassistant/components/matter` rendering of any `0xFFF1FC00` attribute = two upstream |
| 117 | + PRs (`matter-python-client` + `home-assistant/core`). Out of scope for Phases 1-3. |
| 118 | +- `ElectricalPowerMeasurement.Frequency`, `GeneralDiagnostics.Active*Faults`, and |
| 119 | + `OperationalError.errorStateLabel` have no discovery schema on the installed HA -> no cheaper |
| 120 | + native route than the mfg-cluster path. |
| 121 | +- One-`BooleanState`-endpoint-per-fault-bit would be up to 18 new endpoints (GUI + rebuild + |
| 122 | + reflash + re-commission x18, each forced into a semantically-wrong device class). The single |
| 123 | + `Faults1` bitmap + HACS decode gets full per-bit fidelity for zero new endpoints instead. |
0 commit comments