docs(series4): the schedule file is DECODED -- two records, five confirmations

The operator supplied the ground truth: entry 1 is "start monitoring TEST1" at
07:30, entry 2 is "Auto Call Home" at 19:30.  That decodes the file completely.

The body is two 260-byte records plus four zero bytes = 524 exactly, and every
non-zero byte falls inside them:

  [0]     Action       (0 = Auto Call Home, 3 = start monitoring with setup)
  [1:4]   padding
  [4]     1/2h         half-hour slot, 0-47
  [5]     Day
  [6]     ??           the one unidentified field
  [7]     name length
  [8:260] setup name, null-padded

  record @  0: Action=3  1/2h=15 -> 07:30  Day=3  [6]=2   namelen=9  "TEST1.mmb"
  record @260: Action=0  1/2h=39 -> 19:30  Day=3  [6]=16  namelen=0  (no setup)

Five independent confirmations, no fitting:
  1. Slots 15 and 39 match the stated 07:30 and 19:30 on a 30-minute grid, and
     39-15 = 24 slots = exactly 12 hours.
  2. The length byte reads 9 for TEST1.mmb, 40 for the long name in the read
     capture, and 0 for the Auto Call Home entry.
  3. Auto Call Home carries NO setup name -- direct proof that a schedule entry
     can exist with no setup attached, which is what the earlier
     DUTYCYCLE_START_MONITOR finding predicted from the firmware side.
  4. The 260-byte stride lands record 2's 1/2h exactly at [264].
  5. 2 x 260 + 4 = 524, the whole body, nothing left over.

CORRECTION: `27 03 10` at [264] was recorded in the previous commit as a possible
trailer.  It is record 2's 1/2h, Day and [6] fields.  I had assumed the file held
one record and read the second one as padding -- the non-zero bytes were sitting
there the whole time.

Still open: [6] (2 on the start entry, 16 on the ACH entry -- a Repeat or Day/Week
capture would isolate it), the four remaining action codes, and whether the file
can hold unused record slots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
This commit is contained in:
2026-09-24 21:40:05 -04:00
co-authored by Claude Opus 5
parent 508448e2dd
commit 4a1cccf3f5
+57 -30
View File
@@ -1011,10 +1011,10 @@ the record mode" works: an entry says *at this time, load this setup*. It also
means the scheduler and the setup list are coupled — deleting a setup that a means the scheduler and the setup list are coupled — deleting a setup that a
schedule references is a foot-gun worth checking before we ever expose either. schedule references is a foot-gun worth checking before we ever expose either.
⚠ **The entry internals are NOT decoded.** One entry, one capture, no ✅ **The entry internals ARE decoded** — see *The schedule record format* below.
variation to diff against. `03 00 00 00 0f 03 02` and `27 03 10` are recorded The file turned out to hold **two** 260-byte records, not one: `27 03 10` is the
as observed bytes, nothing more. Decoding needs schedules that differ in a second record's time/day fields, not a trailer. `1/2h` is a half-hour slot and
known way — two entries, or one entry at a different time of day. both times check out against what the operator entered.
### `SUB 0x47` — the scheduler enable, probably ### `SUB 0x47` — the scheduler enable, probably
@@ -1490,42 +1490,69 @@ other schedules — or the operator's own saved work — depend on. Nothing in
protocol or the ack reports that this happened. Validating the reference protocol or the ack reports that this happened. Validating the reference
instead of rewriting the referent avoids the whole class of problem. instead of rewriting the referent avoids the whole class of problem.
### The schedule record format, from the firmware's own debug printf ### ✅ The schedule record format — DECODED (2026-09-24)
A debug printf in the scheduler names the fields outright:
``` ```
SCHEDULER : _ReadRecord(%d) -> %s (Action=%u, 1/2h=%u, Day=%u, Setup="%s") [WDAY=%d] SCHEDULER : _ReadRecord(%d) -> %s (Action=%u, 1/2h=%u, Day=%u, Setup="%s") [WDAY=%d]
``` ```
That names the fields outright, and they fit the captured record: The captured file is **two 260-byte records plus four zero bytes** — 524 bytes
exactly. Every non-zero byte in the file falls inside those two records.
| offset | field | read capture | write capture | ```
|---|---|---|---| offset field size notes
| `[0]` | **Action** | `03` | `03` | [0] Action 1 (or uint32 LE at [0:4] — both are zero-extended here)
| `[1:4]` | zero — padding, or `Action` is a uint32 | `00 00 00` | `00 00 00` | [1:4] padding 3 zero in both records
| `[4]` | **1/2h** — half-hour slot | `0f` = 15 | `0f` = 15 | [4] 1/2h 1 half-hour slot, 0–47
| `[5]` | **Day** | `03` | `03` | [5] Day 1 3 in both records
| `[6]` | unidentified (`WDAY`?) | `02` | `02` | [6] ?? 1 the one unidentified field
| `[7]` | **name length** | `0x28` = **40** | `0x09` = **9** | [7] name length 1
| `[8:]` | **Setup** name | 40-char name | `TEST1.mmb` | [8:260] Setup name 252 null-padded
| `[264]` | trailer | `27 03 10` | `27 03 10` | ```
**The length byte is what anchors this** — it reads 40 for the 40-character name Decoded against the operator's own description of what they entered —
and 9 for `TEST1.mmb`, in the same position, in both directions. The layout is *"start monitoring TEST1 at 7:30 AM, Auto Call Home at 7:30 PM"*:
not a guess.
`1/2h` implies **30-minute resolution, 48 slots per day**. Slot 15 would be | | record @0 | record @260 |
07:30 if counted from midnight — plausible but ⚠ **unconfirmed**, since the |---|---|---|
schedule's actual time was not recorded alongside the capture. One question to | `Action` | **3** | **0** |
the operator settles it. | `1/2h` | 15 → **07:30** ✅ | 39 → **19:30** ✅ |
| `Day` | 3 | 3 |
| `[6]` | 2 | 16 |
| name length | 9 | **0** |
| Setup | `TEST1.mmb` | *(none)* ✅ |
⚠ Still unknown, and all answerable with **one schedule containing two entries**: **Five independent confirmations**, no fitting required:
- the **record stride** and how many records the file holds (one record was 1. Slot 15 = 07:30 and slot 39 = 19:30, both matching the operator's stated
captured; `27 03 10` at `[264]` may be a trailer or a second record's head) times, on a 30-minute grid — and 39 − 15 = 24 slots = exactly 12 hours.
- whether `Action` is one byte or a uint32 2. The name-length byte reads 9 for `TEST1.mmb`, 40 for the 40-character name in
- the **action code values** — `03` is presumably the read capture, and **0** for the Auto Call Home entry.
`DUTYCYCLE_START_MONITOR_WITH_SETUP` but the enum's base is unknown 3. **Auto Call Home carries no setup name** — which is what it should do, and
- `[6]`, and how `Day` and `WDAY` divide the work direct proof that a schedule entry can exist with no setup attached.
4. The 260-byte stride lands the second record's `1/2h` exactly at `[264]`.
5. `2 × 260 + 4 = 524`, the whole body, with nothing left over.
⚠ **Correction:** the `27 03 10` bytes at `[264]` were previously recorded here as
a possible trailer. They are **record 2's `1/2h`, `Day` and `[6]` fields**. Not
a trailer — a second record hiding behind an assumption that the file held one.
Action codes, so far:
| value | action |
|---|---|
| `0` | **Auto Call Home** |
| `3` | **Start monitoring, with setup** |
⚠ `[6]` is the one field still unidentified — `2` on the start entry, `16` on the
Auto Call Home entry. It is not the name length and not the time. A capture
that changes only the **Repeat** checkbox, or switches Day↔Week, would isolate it.
⚠ Also still unknown: whether the file can hold more than the records in use
(both captures had every used record contiguous from offset 0), and the remaining
four action codes.
### Six duty-cycle actions, not five ### Six duty-cycle actions, not five