Skip to content

Commit fd8c665

Browse files
authored
Merge pull request #81 from AndrewDemsDS/hil/38-fault-validated
feat(diag): hardware-validate the f_e_* fault decode (closes #38)
2 parents 936e628 + a9afe0e commit fd8c665

5 files changed

Lines changed: 25 additions & 13 deletions

File tree

firmware/esp32-matter/main/diag_console.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ static int cmd_faults(int, char **)
252252
HISENSE_FAULT_BYTE_MODULE, f.raw_module,
253253
HISENSE_FAULT_BYTE_OUTDOOR, f.raw_outdoor,
254254
HISENSE_FAULT_BYTE_PROTECT, f.raw_protect,
255-
f.any ? "FAULT(S) PRESENT (decode confirmed; unseen vs a real fault -- cross-check `raw`)" : "all clear");
255+
f.any ? "FAULT(S) PRESENT (decode confirmed vs real faults 2026-07-22 -- cross-check `raw`)" : "all clear");
256256
if (!f.any) {
257257
printf(" (all-zero is expected on a healthy unit and does NOT validate the map)\r\n");
258258
return 0;

firmware/src/rs485-driver/hisense_rs485.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -831,11 +831,10 @@ bool hisense_get_faults(HisenseFaults *out);
831831
// bytes and returns how many. 0 if nothing has been received yet.
832832
//
833833
// This exists because the fault map's byte/bit positions are CONFIRMED by disassembly (docs/10
834-
// 7.5: the extractor reads the same 66/00 status frame, and the firmware's own compiled-in
835-
// fault names map 1:1 onto bytes 39/40/64/66), but no bit has yet been seen SET against a unit
836-
// actually reporting a fault. A healthy unit reads all-zero, which confirms nothing on its own,
837-
// so being able to read the bytes directly is what makes the last (semantic) step falsifiable.
838-
// The fault-injection runbook is docs/10 7.6.
834+
// 7.5) AND validated against real faults (2026-07-22, docs/10 7.6): f_e_incom (byte 39 bit 0) and
835+
// f_e_intemp (byte 39 bit 7) each fired on their induced fault, named correctly, and cleared on
836+
// undo, with the other unit staying all-clear. The raw snapshot stays for bench inspection and to
837+
// map the remaining bytes (40/64/66) the same way if wanted.
839838
#define HISENSE_RAW_SNAPSHOT_LEN 160
840839
size_t hisense_get_last_status_frame(uint8_t *out, size_t cap);
841840

firmware/src/sdk-edits/hisense_diag_console.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ static void diag_cmd_faults(int sock)
154154
HISENSE_FAULT_BYTE_MODULE, f.raw_module,
155155
HISENSE_FAULT_BYTE_OUTDOOR, f.raw_outdoor,
156156
HISENSE_FAULT_BYTE_PROTECT, f.raw_protect,
157-
f.any ? "FAULT(S) PRESENT (decode confirmed; unseen vs a real fault -- cross-check `raw`)" : "all clear");
157+
f.any ? "FAULT(S) PRESENT (decode confirmed vs real faults 2026-07-22 -- cross-check `raw`)" : "all clear");
158158
diag_say(sock, b);
159159
if (!f.any) {
160160
diag_say(sock, " (all-zero is expected on a healthy unit and does NOT validate the map)\r\n");
@@ -303,7 +303,7 @@ static void diag_handle_line(int sock, char *line)
303303
"commands: features | poll | faults | raw | link | sys | version | help | quit\r\n"
304304
" features cached 0x66/40 ProductType capability flags for THIS unit\r\n"
305305
" poll last decoded A/C status frame\r\n"
306-
" faults decoded f_e_* fault bits (#38; decode confirmed, unseen vs a real fault)\r\n"
306+
" faults decoded f_e_* fault bits (#38; decode confirmed vs real faults)\r\n"
307307
" raw hexdump the last status frame (what falsifies the map)\r\n"
308308
" sys downlink hand-off counter + free heap (why Matter attrs go stale)\r\n");
309309
return;

firmware/src/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.3.19
1+
1.3.20

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -694,11 +694,24 @@ CONFIRMED by two independent lines of disassembly (control-flow + offset math, a
694694
fault-name strings). What remains unproven is purely *semantic*: whether a genuinely faulted unit
695695
asserts these bits on the wire. That is the one gate left, and it needs hardware. See §7.6.
696696
697-
### 7.6 Remaining gate: hardware fault-injection runbook
697+
### 7.6 Hardware validation [CONFIRMED 2026-07-22] and the fault-injection runbook
698698
699-
Static RE cannot go further: a healthy unit reads all-clear, so "no faults" proves nothing about
700-
whether a real fault sets its predicted bit. Confirm one bit end-to-end on a debug-flavour unit
701-
(so the `:2323` console is reachable), cheapest step first:
699+
**Validated on real hardware, 2026-07-22** (office AmebaZ2, v1.3.19). Two faults were induced; each
700+
set exactly its predicted bit, named it correctly, stayed isolated to the affected unit, and cleared
701+
on undo:
702+
703+
| fault | induced by | observed |
704+
|---|---|---|
705+
| `f_e_incom` | cut the indoor-to-outdoor comms line | byte 39 -> `0x01` (bit 0), `com=1`; back to `0x00` on reconnect |
706+
| `f_e_intemp` | unplug the indoor temp thermistor | byte 39 -> `0x80` (bit 7), `temp=1`; back to `0x00` on reconnect |
707+
708+
The kitchen unit read all-clear the whole time (no false positive). Bit 0 and bit 7 are the two ends
709+
of byte 39, so both the byte offset and the within-byte bit decode are confirmed against live faults.
710+
That closes #38. Bytes 40/64/66 rest on the same extractor + compiled-name-string evidence (§7.5) but
711+
have not each been seen firing; the runbook below drives that if wanted.
712+
713+
Confirm a bit end-to-end on a debug-flavour unit (so the `:2323` console is reachable), cheapest step
714+
first:
702715
703716
1. **Baseline (captured live 2026-07-22).** `nc <unit> 2323`, then `faults` (expect `no faults`)
704717
and `raw` (hexdump). On a normal healthy unit bytes 39/40/64/66 read `00 00 00 00` (confirmed on

0 commit comments

Comments
 (0)