From 508448e2dd8d10b5316537b47cd6a4308d1a043a Mon Sep 17 00:00:00 2001 From: serversdown Date: Thu, 24 Sep 2026 20:29:51 -0400 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL --- docs/micromate_protocol_reference.md | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/docs/micromate_protocol_reference.md b/docs/micromate_protocol_reference.md index 08ef338..f600869 100644 --- a/docs/micromate_protocol_reference.md +++ b/docs/micromate_protocol_reference.md @@ -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