docs(series4): the schedule record format, named by the firmware itself

A debug printf in the scheduler names the record's fields outright:

  SCHEDULER : _ReadRecord(%d) -> %s (Action=%u, 1/2h=%u, Day=%u, Setup="%s") [WDAY=%d]

They fit the captured record, and the name-length byte anchors the alignment --
it reads 40 for the 40-character name pulled off the unit and 9 for TEST1.mmb
written back, same position, both directions:

  [0]     Action = 3
  [1:4]   zero (padding, or Action is a uint32)
  [4]     1/2h = 15   -> 30-minute resolution, 48 slots/day
  [5]     Day = 3
  [6]     unidentified (WDAY?)
  [7]     name length -- 0x28=40 read, 0x09=9 written  <-- confirms the layout
  [8:]    setup name
  [264]   trailer 27 03 10

Slot 15 would be 07:30 counted from midnight; flagged unconfirmed because the
schedule's actual time was not recorded with the capture.

Six duty-cycle actions, not the five previously recorded -- there is also
DUTYCYCLE_START_MONITOR_WITH_SETUP_STOP_COMPLETE.  THOR exposes four.  Two start
variants it never offers, one needing no setup file.

Strengthened the setup-less-action finding and ruled out an alternative
explanation I had not considered: the Micromate has a separate Timer Mode
(MODE_TIMER, Monitor Once Only, under Special Setup), so START_MONITOR could have
belonged to that path.  It does not -- it is a case in _PSA(), the scheduler's own
dispatcher for _ReadRecord's Action field, and `_PSA() send ->> CMD_DUTYCYCLE_
START_MONITOR` shows it is live code sending a real message, not a dead case.

Also: SysPref.bMonitorScheduler places the scheduler enable in system
preferences, which confirms from the other side why the 0x71 block was
byte-identical when the scheduler was switched on -- the flag was never going to
be in the compliance config.  It also suggests 0x47 is a SysPref get/set rather
than anything scheduler-specific, which would explain its params[7] selector and
its response shape matching 0x48's page-0 descriptor.  Still a hypothesis.

Names the one capture that would settle the rest: a schedule with TWO entries at
different times with different actions.  That yields the record stride, the action
code values, and the time encoding at once.

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 20:29:51 -04:00
co-authored by Claude Opus 5
parent 3f58402085
commit 508448e2dd
+82
View File
@@ -1490,6 +1490,88 @@ 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
```
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:
| 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` |
**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.
`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.
⚠ Still unknown, and all answerable with **one schedule containing two entries**:
- 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
### Six duty-cycle actions, not five
The full `_PSA()` dispatch — the scheduler's own action processor, reading records
via `_ReadRecord`:
```
DUTYCYCLE_START_MONITOR ← no setup
DUTYCYCLE_START_MONITOR_WITH_SETUP
DUTYCYCLE_START_MONITOR_WITH_SETUP_STOP_COMPLETE ← a third start variant
DUTYCYCLE_STOP_MONITOR
DUTYCYCLE_CALLHOME
DUTYCYCLE_SELF_CHECK
```
**THOR exposes four.** Two start variants it never offers, one of which needs no
setup file at all.
`_PSA() send ->> CMD_DUTYCYCLE_START_MONITOR` shows the setup-less action is live
code that the scheduler genuinely dispatches, not a dead case — it sends a real
message. And since every one of these cases sits in the function that consumes
`_ReadRecord`'s `Action` field, **a schedule record can carry it.**
An alternative explanation was checked and ruled out: the Micromate does have a
separate *Timer Mode* (`MODE_TIMER`, `MODE_MONITOR_TIMER`, `Monitor Once Only`,
under Special Setup), so `START_MONITOR` could have belonged to that path
instead. It does not — it is in `_PSA()`, the scheduler's dispatcher.
### The scheduler enable lives in SysPref, not in the setup
```
SysPref.bMonitorScheduler = %s
_Task() CMD_SET_SCHEDULE - MonitorScheduler ENABLED
_Task() CMD_SET_SCHEDULE - ! MonitorScheduler NOT Enabled
```
This **confirms the failed prediction above from the other side**: the
`Scheduler On/Off` switch is a *system preference*, which is why the `0x71`
compliance block was byte-identical when the scheduler was turned on. It was
never going to be in there.
It also suggests `0x47` — the two bare frames at the end of the capture — is a
SysPref get/set rather than anything scheduler-specific, which would explain its
`params[7]` selector and its 15-byte response shape being shared with `0x48`'s
page-0 descriptor. ⚠ Still a hypothesis; the disable/enable capture settles it.
#### The schedule has a setup-less start action — Thor just never uses it
The coupling above looked like it might be a workaround for the unit crashing on