Skip to content

Commit 936e628

Browse files
authored
Merge pull request #79 from AndrewDemsDS/docs/diag-followup
docs: HA diagnostics-exposure design (#39) + live-confirmed fault runbook (#38)
2 parents fb38748 + e77e7ac commit 936e628

2 files changed

Lines changed: 143 additions & 10 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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.

reverse-engineering/docs/10-stock-fw-init-and-comms.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -700,17 +700,27 @@ Static RE cannot go further: a healthy unit reads all-clear, so "no faults" prov
700700
whether a real fault sets its predicted bit. Confirm one bit end-to-end on a debug-flavour unit
701701
(so the `:2323` console is reachable), cheapest step first:
702702
703-
1. **Baseline.** `nc <unit> 2323`, then `faults` (expect `no faults`) and `raw` (hexdump). Record
704-
bytes 39/40/64/66 healthy: expect `00 00 00 80`. The `0x80` at byte 66 is the frost-guard mode
705-
flag, masked out of the `any` aggregate by `HISENSE_FAULT_NONFAULT_PROTECT`.
706-
2. **Induce the most reversible fault first**, always disturbing the indoor/outdoor side, never
707-
the module's own RS-485 bus (that kills our poll):
708-
- `f_e_incom` (byte 39, mask `0x01`): interrupt the indoor-to-outdoor comms link. Cleanest
709-
end-to-end test, because the ConnectLife app should log a Communications fault at the same
710-
moment our decode flips.
703+
1. **Baseline (captured live 2026-07-22).** `nc <unit> 2323`, then `faults` (expect `no faults`)
704+
and `raw` (hexdump). On a normal healthy unit bytes 39/40/64/66 read `00 00 00 00` (confirmed on
705+
the two debug-flavour AmebaZ2 nodes at v1.3.18). Byte 66 reads `0x80` ONLY while 8C frost-guard
706+
is engaged: that is the `HISENSE_FAULT_NONFAULT_PROTECT` mode-flag bit, masked out of the `any`
707+
aggregate, not a fault.
708+
2. **Induce two faults, ideally on two DIFFERENT fault bytes** (validates two byte offsets, not just
709+
two bits of one byte). Always disturb the indoor/outdoor side, never the module's own RS-485 bus
710+
(that kills our poll). Any predicted bit firing where and only where its fault is induced is the
711+
proof; two independent hits make it conclusive. Reversible, most accessible first:
712+
- `f_e_incom` (byte 39, mask `0x01`): interrupt the indoor-to-outdoor comms link. Cleanest, and
713+
the ConnectLife app should log a Communications fault at the same moment our decode flips.
711714
- `f_e_waterfull` (byte 39, mask `0x10`): trip the condensate-tray float switch.
712-
3. **Observe.** With the fault active, `faults` must name the predicted bit and the matching
713-
`raw_*` byte must show it set. Save the `raw` hexdump healthy vs faulted; the diff is the proof.
715+
- `f_e_intemp` (byte 39, mask `0x80`) / `f_e_incoiltemp` etc.: unplug the corresponding indoor
716+
thermistor (reversible).
717+
- For a SECOND byte: `f_e_outtemp` (byte 64, mask `0x08`) via the outdoor temp thermistor, or any
718+
byte-40 electronics fault, if reachable.
719+
3. **Observe (read-back is turnkey).** With each fault active, `faults` must name the predicted bit
720+
and the matching `raw_*` byte must show it set; clearing the fault must clear it. Capture the
721+
`raw` hexdump healthy vs faulted for each -- the diff is the proof. The bench tooling can read
722+
both nodes' `:2323` consoles automatically, so once a fault is active just say so and the
723+
before/after can be pulled and recorded here.
714724
4. **Cross-check, no bench fault needed.** The still-cloud "Master Bedroom AC" can run "Self
715725
diagnostics" in the app; compare its reported categories against our decode of that unit's
716726
status frame.

0 commit comments

Comments
 (0)