docs(series4): event download, PER-EVENT delete, and the ACH config write

Three captures with operator ground truth (Thor screenshots of the event list and
the weekly schedule).  All Thor-originated.

DAY AND SCHEDULE TYPE, both settled by a weekly schedule.  Thor's screen showed
"Start Monitoring 8:00 AM every day, alternating TEST1/test2, Repeat Weekly
disabled".  The file is 7 x 260 + 4 = 1824 bytes and every record matches row for
row:

  Day = 0 Sunday .. 6 Saturday

and [0] on record 0 read 4, against 2 and 3 in the daily schedules, so it carries
schedule TYPE as well as Repeat:  2 daily, 3 daily+repeat, 4 weekly,
5 weekly+repeat (predicted, unobserved).  Bit 0 is Repeat; 2 and 4 are the bases.
Same bitmask style as [6].

In daily schedules Day reads 3 everywhere and is presumably ignored -- inference,
and the value 3 is unexplained.

EVENT DOWNLOAD.  SUB 0x93 -> 0x6C arms each event before 1E/1F, with empty params
and an all-zero ack -- the Series IV analogue of Series III's 1E(token=0xFE), and
simpler.  Event keys are a plain sequential counter (055D4A81..86 for six events)
at data[11:15], with the event size at data[17:19].  SUB 0x0A walks the list as
30-byte timestamped records; the dates match Thor's event list exactly.

DELETE IS PER-EVENT -- and this is the last piece a homebrew ACH receiver was
missing:

    0xA8  params[0:4] = <event key>  -> ack 0x57
    0xAA  params = zeros             -> ack 0x55

The operator deleted the top row of Thor's list (the newest event) and 0xA8
carried 055D4A86, the highest key from the walk.  Confirmed end to end.

Strictly safer than Series III, which can only erase everything: a receiver can
delete exactly what it has confirmed it stored.  Different opcodes -- do not reach
for 0xA3/0xA2.  Noted that SUB 0x06 read identically before and after, so it is
not a way to confirm a deletion landed.

ACH CONFIG.  0x2C / 0x7E / 0x7F with acks 0xD3 / 0x81 / 0x80 -- identical to
Series III.  126-byte write payload, offset 0x007E; the 0x2C read returns the same
bytes behind an 11-byte prefix.  The enable flag is write[5]: 0x05 enabled, 0x04
disabled.  Bit 0 is the flag, bit 2 set in both states -- do NOT test for
0x01/0x00 as Series III does.  Dial string at write[6:] ("RADIO RING").

Flagged: write[118:120] changed on its own between the two sessions (43 23 ->
2e 5e) with nothing touched, and Thor writes back whatever it read.  Round-trip
that field, never synthesise it.  Beyond the enable byte and dial string the field
map is NOT established -- only one setting was varied, and Series III's offsets
are a hypothesis, not a transfer.

Every command on the unsafe list is now observed.  None has been originated by
us, which is the line that still matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
This commit is contained in:
2026-09-25 01:26:42 -04:00
co-authored by Claude Opus 5
parent f839541d08
commit 8e37803d40
+154 -4
View File
@@ -449,6 +449,13 @@ memory 15,000,000 total and free (no events stored).
Series III has one config and nothing to enumerate.
15. **`SUB 0x1C` carries the device clock** (day/month/year/h/m/s at
`data[13:21]`). Nothing else read so far reports the unit's own time.
18. **Deletion is per-event** (`0xA8` + a key, then `0xAA`), where Series III can
only erase everything (`0xA3`/`0xA2`).
19. **`SUB 0x93` arms each event** before `1E`/`1F`, replacing Series III's
`1E(token=0xFE)` — no token, no params.
20. **Event keys are a sequential counter** (`055D4A81…86`), not flash addresses.
21. **The ACH enable is `0x05`/`0x04`**, not Series III's `0x01`/`0x00` — bit 0
is the flag, bit 2 is set in both states.
16. **There is a generic file transfer addressed by full path** — `0x94`/`0x48`
read, `0x8D`/`0x8E` write. Series III has nothing comparable; its config is
reachable only through dedicated commands.
@@ -1301,6 +1308,141 @@ Worth noting the histogram (`…81`) and the loudest waveform (`…84`) report
the histogram's single 1-minute interval spans the whole thumping session, so
its maximum should equal the loudest event in it.
## Event download, per-event delete, and ACH config (2026-09-25)
Three captures with operator-supplied ground truth, including Thor screenshots of
the event list and the schedule. All Thor-originated; we still send nothing.
### ✅ `Day` is the day of week — and `[0]` is the schedule type
A **weekly** schedule settles both fields. Thor's screen: *"multiday, Repeat
Weekly: Disabled, Start Monitoring at 8:00 AM every day, alternating
TEST1 / test2."* The file is **7 × 260 + 4 = 1,824 bytes**, and every record
matches:
| rec | `1/2h` | time | `Day` | | `[6]` | name |
|---|---|---|---|---|---|---|
| @0 | 16 | 08:00 | **0** | Sun | 2 | `TEST1.mmb` |
| @260 | 16 | 08:00 | **1** | Mon | 2 | `test2.mmb` |
| @520 | 16 | 08:00 | **2** | Tue | 2 | `TEST1.mmb` |
| @780 | 16 | 08:00 | **3** | Wed | 2 | `test2.mmb` |
| @1040 | 16 | 08:00 | **4** | Thu | 2 | `TEST1.mmb` |
| @1300 | 16 | 08:00 | **5** | Fri | 2 | `test2.mmb` |
| @1560 | 16 | 08:00 | **6** | Sat | 2 | `TEST1.mmb` |
**`Day` = 0 Sunday … 6 Saturday**, and the alternating setup names line up with
the screen row for row.
`[0]` on record 0 read **4** here, against `2` and `3` in the daily schedules —
so it carries the schedule *type* as well as Repeat:
| `[0]` | meaning |
|---|---|
| `2` | Daily, repeat off |
| `3` | Daily, repeat on |
| `4` | **Weekly, repeat off** |
| `5` | Weekly, repeat on — ⚠ predicted, not observed |
Bit 0 is Repeat; `2` and `4` are the Day/Week bases. Same bitmask style as
`[6]`.
⚠ In *daily* schedules `Day` reads `3` in every record of every capture. With
`[0]` already saying "daily", the field is presumably ignored there — but that is
inference, and `3` is unexplained.
### Event download — `SUB 0x93` arms each event
Thor's download of six events:
```
POLL 0x15 0x49 POLL 0x1C 0x15 0x49 0x06
0x93 0x1E 0x0C 0x5A×4 ← event 1
0x93 0x1F 0x0C 0x5A×11 ← event 2
0x93 0x1F 0x0C 0x5A×12 ← event 3
0x93 0x1F 0x0C 0x5A×14 ← event 4
0x93 0x1F 0x0C 0x5A×9 ← event 5
0x93 0x1F 0x0C 0x5A×6 ← event 6
0x93 0x06 0x93 0x1E 0x0A×9 ← re-walk the list
```
**`SUB 0x93` → ack `0x6C`** is sent before every event, with empty params and an
all-zero ack. It is the Series IV analogue of Series III's `1E(token=0xFE)` arm
step, and it is simpler: no token, no params.
**Event keys are a plain sequential counter.** The six events walked as
`055D4A81 … 055D4A86`, consecutive, at `data[11:15]` of the `0x1E`/`0x1F`
response, with the event's byte size at `data[17:19]`. Nothing like Series III's
flash addresses.
`SUB 0x0A` walks the event list returning **30-byte records** carrying start and
stop timestamps (`[11]` day, `[12]` month, `[13:15]` year BE …), terminating on
an all-zero record. The dates match Thor's event list exactly (five events on
09/23/2026, one on 09/24/2026).
### 🔑 Delete is PER-EVENT — `SUB 0xA8` + `0xAA`
```
0xA8 params[0:4] = <event key> → ack 0x57
0xAA params = zeros → ack 0x55
```
The operator deleted the **top row of Thor's list** — the newest event — and
`0xA8` carried `05 5d 4a 86`, the **highest** key from the walk. Thor lists
newest-first, so newest = highest key. Confirmed end to end.
**This is the last piece a homebrew ACH receiver was missing.** The manual
established that collection state is server-side bookkeeping and that "delete
events from unit" is a separate server-issued action; this is that action's
opcodes.
Two things worth noting against Series III:
- Series III erases **everything** (`0xA3 → 0x1C → 0x06 → 0xA2`). Series IV
deletes **one named event**, which is strictly safer — a receiver can delete
exactly what it has confirmed it stored.
- The opcodes are different. Do not reach for `0xA3`/`0xA2` here.
⚠ `SUB 0x06` (storage range) read **identically before and after** the delete in
this capture, so it is not a quick way to confirm a deletion landed. Re-walk the
event list instead.
### ✅ ACH config — `0x2C` / `0x7E` / `0x7F`, exactly Series III
Thor enabling then disabling Auto Call Home, twice through the same sequence:
```
POLL → 0x15 → 0x49 → POLL → 0x2C (read) → 0x7E (write, 126 B) → 0x7F (confirm)
```
Same SUBs and the same acks as Series III — `0x2C`→`0xD3`, `0x7E`→`0x81`,
`0x7F`→`0x80`. The write payload is **126 bytes** with `offset = 0x007E`, and
the `0x2C` read returns those same 126 bytes behind an **11-byte prefix**, so
`read[n + 11]` is `write[n]`.
**The enable flag is `write[5]`** (= `read[16]`):
| value | state |
|---|---|
| `0x05` | Auto Call Home **enabled** |
| `0x04` | **disabled** |
Bit 0 is the enable — the same bit position Series III uses at `raw[5]`, but with
bit 2 set as well in both states. Do not test for `0x01`/`0x00`.
`write[6:]` holds the dial string, null-padded — `"RADIO RING"` on this unit,
matching Series III's `raw[6:46]`.
⚠ **`write[118:120]` changed on its own between the two sessions** (`43 23` →
`2e 5e`) without the operator touching anything. Thor reads it and writes back
whatever it read. Its meaning is unknown — plausibly a counter, timer or
session value. **Round-trip this field; never synthesise it.** `write[117]` is
`0x03`, which lines up with Series III's `num_retries` of 3, and `write[120:122]`
is `00 3c` = 60.
⚠ Beyond the enable byte and the dial string, **the field map is not
established** — only one setting was varied. Series III's offsets are a starting
hypothesis, not a transfer.
## What the THOR manual settles about the ACH session (2026-09-24)
Source: `manuals/723U0201 THOR Operator Manual Rev 08.pdf` §6.2, §3.6.
@@ -1776,11 +1918,19 @@ because Thor performed it while we recorded. Observed is not the same as
exercised — **we have still never originated a write frame.** The wire format
is known; our encoder is unwritten and unproven.
- **Writes** (`0x68`–`0x83`) — format now known, never sent by us
- **Call-home write** (`0x7E` / `0x7F`) — not observed at all
- **Erase** (`0xA3` / `0xA2`) — **the only genuinely untouched destructive path**
- **Writes** (`0x68`–`0x83`) — format known, never sent by us
- **Call-home write** (`0x7E` / `0x7F`) — ✅ observed 2026-09-25, acks `0x81` /
`0x80`; enable flag isolated
- **Per-event delete** (`0xA8` / `0xAA`) — ✅ observed 2026-09-25, acks `0x57` /
`0x55`. **Destructive.** Series III's `0xA3`/`0xA2` erase-all has *not* been
seen on Series IV and may not exist
- **Start / stop monitoring** (`0x96` / `0x97`) — observed via Thor 2026-09-24,
acks `0x69` / `0x68`; still never originated by us
acks `0x69` / `0x68`
- **Scheduler enable** (`0x47`) — observed; `params[7]` semantics still unclear
Every one of these is now *observed*. **None has ever been originated by us**,
and that is the line that still matters: the wire format is known, our encoder is
unwritten and unproven.
- `0x1F` (advance event pointer) — non-destructive on Series III but it does
move device state, so it is parked with the rest