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
schedule references is a foot-gun worth checking before we ever expose either.
⚠ **The entry internals are NOT decoded.** One entry, one capture, no
variation to diff against. `03 00 00 00 0f 03 02` and `27 03 10` are recorded
as observed bytes, nothing more. Decoding needs schedules that differ in a
known way — two entries, or one entry at a different time of day.
✅ **The entry internals ARE decoded** — see *The schedule record format* below.
The file turned out to hold **two** 260-byte records, not one: `27 03 10` is the
second record's time/day fields, not a trailer. `1/2h` is a half-hour slot and
both times check out against what the operator entered.
### `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
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]
```
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 |
|---|---|---|---|
| `[0]` | **Action** | `03` | `03` |
| `[1:4]` | zero — padding, or `Action` is a uint32 | `00 00 00` | `00 00 00` |
| `[4]` | **1/2h** — half-hour slot | `0f` = 15 | `0f` = 15 |
| `[5]` | **Day** | `03` | `03` |
| `[6]` | unidentified (`WDAY`?) | `02` | `02` |
| `[7]` | **name length** | `0x28` = **40** | `0x09` = **9** |
| `[8:]` | **Setup** name | 40-char name | `TEST1.mmb` |
| `[264]` | trailer | `27 03 10` | `27 03 10` |
```
offset field size notes
[0] Action 1 (or uint32 LE at [0:4] — both are zero-extended here)
[1:4] padding 3 zero in both records
[4] 1/2h 1 half-hour slot, 0–47
[5] Day 1 3 in both records
[6] ?? 1 the one unidentified field
[7] name length 1
[8:260] Setup name 252 null-padded
```
**The length byte is what anchors this** — it reads 40 for the 40-character name
and 9 for `TEST1.mmb`, in the same position, in both directions. The layout is
not a guess.
Decoded against the operator's own description of what they entered —
*"start monitoring TEST1 at 7:30 AM, Auto Call Home at 7:30 PM"*:
`1/2h` implies **30-minute resolution, 48 slots per day**. Slot 15 would be
07:30 if counted from midnight — plausible but ⚠ **unconfirmed**, since the
schedule's actual time was not recorded alongside the capture. One question to
the operator settles it.
| | record @0 | record @260 |
|---|---|---|
| `Action` | **3** | **0** |
| `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
captured; `27 03 10` at `[264]` may be a trailer or a second record's head)
- whether `Action` is one byte or a uint32
- the **action code values** — `03` is presumably
`DUTYCYCLE_START_MONITOR_WITH_SETUP` but the enum's base is unknown
- `[6]`, and how `Day` and `WDAY` divide the work
1. Slot 15 = 07:30 and slot 39 = 19:30, both matching the operator's stated
times, on a 30-minute grid — and 39 − 15 = 24 slots = exactly 12 hours.
2. The name-length byte reads 9 for `TEST1.mmb`, 40 for the 40-character name in
the read capture, and **0** for the Auto Call Home entry.
3. **Auto Call Home carries no setup name** — which is what it should do, and
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