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:
@@ -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
|
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
|
||||||
|
|
||||||
|
```
|
||||||
|
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 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
|
The coupling above looked like it might be a workaround for the unit crashing on
|
||||||
|
|||||||
Reference in New Issue
Block a user