Skip to content

Commit 2acc875

Browse files
committed
docs(re): compatibility framing for per-unit capability flags; tighten prose
A ProductType flag reading 0 describes THIS unit, not the model range. The earlier wording ("drop the endpoint", "not worth chasing") invited deleting code paths that other Hisense units need: ac_ai, ac_swing_direction_8, ac_humidity and ac_purify are all stock verbs, so some unit ships each one. Design rule now stated in doc 11 §5.1: keep the code path, gate it at runtime on hisense_get_features(). A capability this bench unit reports 0 for is a deprioritised capture target, not a dead one. Deleting it breaks compatibility for owners whose unit has the feature. Also applied the house prose rules to today's additions (docs 10 §4.1/§4.5/ §5a and all of doc 11): no em dashes, no adverbs, active voice, no throat-clearing. Verbatim console captures left byte-exact. No behaviour change; host suite still green. Assisted-by: AI
1 parent e1570cd commit 2acc875

2 files changed

Lines changed: 106 additions & 95 deletions

File tree

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

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,13 @@ Runs as `uart_ctl_process_main` (`0x9b6f8dd8`) — **called repeatedly** by the
184184

185185
**S4 — MODEL FINALISE**: allocates + copies the model capability struct, keyed on device-type. Full map, struct layout, and the per-model feature tables: **[`11-model-capability-map.md`](11-model-capability-map.md)**. In brief: `{0,1,0x36,0x37}` → TEMPLATE_A `0x9b807e20` + `malloc(0x33c)→[0x1000b8a8]` (A/C); `0x15` → TEMPLATE_B `0x9b808144` + `malloc(0xe4)→[0x1000b8b4]` (**dehumidifier**); anything else allocates **nothing** (`0x9b6f9638`). Next pass short-circuits S3 via `[0x1000bcc2]` → steady state = repeated verified num30/0x66 polls.
186186

187-
> **Corrections to an earlier revision of this line (2026-07-16, all [PROVEN] see doc 11 §2):**
187+
> **Corrections to an earlier revision of this line (2026-07-16, all [PROVEN], see doc 11 §2):**
188188
> 1. **`0x9b6f9126` is NOT the dispatch head** — it is `bl 0x9b6f307c`, the devtype *getter*
189189
> (`ldrb r0,[0x10009687]; cmp r0,#1; it lo; movlo r0,#1` — maps 0→1).
190190
> 2. **`0x36`/`0x37` do NOT get a distinct branch.** `0x9b6f9130` *is* the TEMPLATE_A/`0x33c`
191191
> branch (`mov.w r0,#0x33c` @ `0x9b6f9130`), shared by all four A/C codes
192192
> (`beq.w #0x9b6f9130` @ `0x9b6f9728`/`0x9b6f9732`).
193-
> 3. **TEMPLATE_A is reached by an explicit `==1` test, not an "else."** The real else
193+
> 3. **TEMPLATE_A is reached by an explicit `==1` test rather than an "else."** The real else
194194
> (`0x9b6f9638`) allocates no struct at all.
195195
196196
(Off main path: `0x9b6f3480` enumerates slaves 1..8 via `0x9b6f3120`, transaction timeout **3000 ms, 5 retries**; `0x9b6f3418` resets 8×0x48-byte slave slots.)
@@ -239,21 +239,21 @@ Two source threads appeared to conflict; they describe different byte origins:
239239
> session tok envel [9]/[10]: 00 00 [captured] <- what v10207 stamped
240240
> ```
241241
>
242-
> The DevType reply's envelope bytes[9]/[10] is **`00 00`** — *not* `01 01`. The claim above
243-
> that "the A/C reply seeds the token to `01 01`" is therefore **wrong for the `0x0A` frame**:
244-
> that `01 01` comes only from `handle_devType_cmd_result`'s inner `[3]/[4]`. (A **`0x66` status**
245-
> reply *does* carry `01 01` at [9]/[10]measured separatelywhich is why an observe-only
246-
> capture that re-reads every reply always looked benign and seemed to confirm the token story.)
242+
> The DevType reply's envelope bytes[9]/[10] is **`00 00`**, not `01 01`. The claim above that
243+
> "the A/C reply seeds the token to `01 01`" is **wrong for the `0x0A` frame**: that `01 01` comes
244+
> only from `handle_devType_cmd_result`'s inner `[3]/[4]`. A **`0x66` status** reply does carry
245+
> `01 01` at [9]/[10], measured separately, which is why an observe-only capture that re-reads
246+
> every reply looked benign and seemed to confirm the token story.
247247
>
248-
> **This is the exact v10207 failure:** it sampled first-reply-only (stock's `globals==0` gate =
249-
> the `0x0A` reply), got `00 00`, rejected it as "not yet seeded", and so stamped its `00 00`
250-
> seed onto every post-handshake frame instead of `01 01`. The A/C rejected all of them.
248+
> **This is the v10207 failure.** It sampled first-reply-only (stock's `globals==0` gate = the
249+
> `0x0A` reply), read `00 00`, rejected that as "not yet seeded", and stamped its `00 00` seed onto
250+
> every post-handshake frame in place of `01 01`. The A/C rejected all of them.
251251
>
252-
> **Consequence:** bytes[7]/[8] post-handshake are the **device-type/sub-type**, a *static
253-
> per-model identifier* — the `01 01` in every captured stock DI frame is this model's device
254-
> type. Anything reading these bytes must take `handle_devType_cmd_result`'s inner `[3]/[4]`
255-
> from the class-`0x0A` reply (frame bytes `[16]/[17]`), **never** that reply's envelope
256-
> `[9]/[10]`; see `firmware/src/rs485-driver/hisense_rs485.cpp` (`hisense_devtype_from_reply`).
252+
> **Consequence:** bytes[7]/[8] post-handshake carry the **device-type/sub-type**, a *static
253+
> per-model identifier*. The `01 01` in the captured stock DI frames is this model's device type.
254+
> Read these bytes from `handle_devType_cmd_result`'s inner `[3]/[4]` on the class-`0x0A` reply
255+
> (frame bytes `[16]/[17]`), **never** from that reply's envelope `[9]/[10]`. See
256+
> `firmware/src/rs485-driver/hisense_rs485.cpp` (`hisense_devtype_from_reply`).
257257
258258
### 4.6 Timing / retry summary **[PROVEN]**
259259
- Per-transaction listen: **500 ms**. Timeout → status 7 → transaction returns 0.
@@ -293,21 +293,20 @@ Guard: `payload[0]==0x66` (`0x9b6f0c6a`) **and** `payload[1]==0x40` (`0x9b6f0c74
293293
294294
Also: `payload[3]` (frame 16) → raw global `0x100096c9` (bits1,3 feed a state machine); `payload[4]` (frame 17) bit3 → state machine; **`payload[5]` (frame 18) read *signed* (`ldrsb` @ `0x9b6f0c86`)** into `sp+0x64`, branched `≥0/<0` @ `0x9b6f0f10` — a signed selector byte, **not** a plain temperature. The partial overlap with ESPHome (frame16≈"fan", frame18≈"mode") is coincidental — semantics are **bit-flags, not byte values**. **[PROVEN]**
295295
296-
> **Correction (2026-07-16, [PROVEN] — see [doc 11](11-model-capability-map.md) §4):** an earlier
297-
> revision said *"Whole commit gated on `[0x10009687]==0x15`"*. **Wrong.** The `==0x15` test only
298-
> selects the **printf format string**; it does not gate the commit. The real gate is
299-
> **`devtype <= 1`** (`ldrb r3,[0x10009687]; cmp r3,#1; bhi.w #0x9b6f16c4` @ `0x9b6f0d84`) —
300-
> i.e. the commit runs for **our** unit (devtype 1), not only for the dehumidifier. What it
301-
> commits: `supported` bytes written into the S4-allocated capability struct at `[0x1000b8a8]`,
302-
> plus a 3-digit **profile code** strcpy'd to `0x100096dc` (getter `0x9b6f308c`).
303-
> `ac_trans_102_64` set → profile **`199`** (the generic/transparent profile,
304-
> `beq.w` @ `0x9b6f0ddc` → `'199'` @ `0x9b6f0de4`).
305-
306-
> ⚠️ **The table above is authoritative and our driver disagrees with it.**
307-
> `hisense_parse_features()` reads the right *bytes* but mislabels two: its `q_display` is
308-
> `[0x0A]&0x08` = **`ac_purify`**, and its `purify` is `[0x0D]&0x80` = **`ac_8heat`**. True
309-
> `ac_q_display` (`[0x1A]&0x40`) is not parsed at all. Independently confirmed against the stock
310-
> printf arg order. Tracked for a rename (behaviour is unchanged — the byte reads are correct).
296+
> **Correction (2026-07-16, [PROVEN], see [doc 11](11-model-capability-map.md) §4):** an earlier
297+
> revision said *"Whole commit gated on `[0x10009687]==0x15"*. That is wrong. The `==0x15` test
298+
> selects the **printf format string** and does not gate the commit. The real gate is
299+
> **`devtype <= 1`** (`ldrb r3,[0x10009687]; cmp r3,#1; bhi.w #0x9b6f16c4` @ `0x9b6f0d84`), so the
300+
> commit runs for **our** unit (devtype 1) and not for the dehumidifier alone. It commits
301+
> `supported` bytes into the S4-allocated capability struct at `[0x1000b8a8]`, plus a 3-digit
302+
> **profile code** strcpy'd to `0x100096dc` (getter `0x9b6f308c`). `ac_trans_102_64` set → profile
303+
> **`199`** (the generic/transparent profile, `beq.w` @ `0x9b6f0ddc` → `'199'` @ `0x9b6f0de4`).
304+
305+
> ⚠️ **The table above is authoritative. Our driver disagreed with it until 2026-07-16.**
306+
> `hisense_parse_features()` read the right *bytes* and mislabeled two: its `q_display` held
307+
> `[0x0A]&0x08` = **`ac_purify`**, and its `purify` held `[0x0D]&0x80` = **`ac_8heat`**. True
308+
> `ac_q_display` (`[0x1A]&0x40`) still goes unparsed. Confirmed against the stock printf arg order
309+
> and renamed (`heat_8c` + `purify`); the byte reads were correct, so behaviour did not change.
311310
312311
### 5b. Command `0x65` — `matter_pack_devType_cmd` `0x9b6f2c60` **[PROVEN]**
313312

0 commit comments

Comments
 (0)