docs(series4): RETRACT "no file transfer" -- 0x94/0x48/0x8D/0x8E read and write by path
The scheduler capture caught a generic file transfer in the open, and it
invalidates a claim made earlier today.
RETRACTION. The "Setups are FILES" section concluded "no generic file-transfer
command is exposed on the wire", reasoning from the absence of firmware strings.
Wrong. The commands exist, they carry a full filesystem path in plain ASCII,
and they were the first thing Thor did when asked for the schedule:
0x94 <path> -> 0x6B open for read
0x48 -> 0xB7 read next page, until an all-zero response = EOF
0x8D <path> -> 0x72 open for write
0x8E <body> -> 0x71 write the body
Path is unpadded with offset = its exact length; 0x94 and 0x8D sent byte-
identical payloads for "\system\schedule\schedule.dat". The 0x48 read is paged
with the page number in the response header at payload[3:5].
The lesson: absence of a firmware string is not absence of a command. Dispatch
is a 68K jump table and these carry no strings. The setups half of the original
claim survives -- setups go via 0xDA plus the config block, not via this.
Why it matters beyond the scheduler: callhome.MMB is a file too, and call-home
is the last unsolved goal. Reading it may be a matter of pointing 0x94 at the
right path. Recorded as a lead -- no path but schedule.dat has been tried.
The schedule file: an entry carries a length-prefixed SETUP FILE NAME (0x28=40
for the name read off the unit, 0x09=9 for TEST1.mmb written back -- confirmed
both directions). So a schedule entry says "at this time, load this setup",
which is how the help text's "change the record mode" works, and it couples the
scheduler to the setup list. Entry internals are NOT decoded and are recorded
as observed bytes only -- one entry, no variation to diff.
SUB 0x47 is the scheduler enable, probably: two bare frames differing only in
params[7] (0x01, 0x03). Whether it sets or reads is genuinely undetermined --
both returned the same value and there is no disabled reading to compare.
Flagged do-not-implement until a disable-then-enable capture settles it.
A prediction made before the capture -- that the Scheduler On/Off switch would
show up as a byte in the 0x71 block, since the unit's help text lists it beside
Record Mode -- did not hold. 0x71, 0x68 and 0x82 are byte-identical to the
previous capture. Noted that the test is weak (the operator re-sent the same
config), but enabling the scheduler required no config write either way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
This commit is contained in:
@@ -449,6 +449,11 @@ memory 15,000,000 total and free (no events stored).
|
||||
Series III has one config and nothing to enumerate.
|
||||
15. **`SUB 0x1C` carries the device clock** (day/month/year/h/m/s at
|
||||
`data[13:21]`). Nothing else read so far reports the unit's own time.
|
||||
16. **There is a generic file transfer addressed by full path** — `0x94`/`0x48`
|
||||
read, `0x8D`/`0x8E` write. Series III has nothing comparable; its config is
|
||||
reachable only through dedicated commands.
|
||||
17. **The scheduler is a separate file**, `\system\schedule\schedule.dat`,
|
||||
and a schedule entry names a **setup file** to load.
|
||||
|
||||
---
|
||||
|
||||
@@ -649,10 +654,21 @@ The unit's help text describes selecting, renaming and deleting them, and the
|
||||
event list records which setup file produced each event.
|
||||
|
||||
Filesystem primitives exist internally (`NS_ReadFile_internal`,
|
||||
`NS_WriteFile_internal`, `NS_SeekFile_internal`), but **no generic
|
||||
file-transfer command is exposed on the wire** — the only file-transfer string
|
||||
is `CMD_STOP_CALLHOME_FILETRANSFER`. So setups are unlikely to be pushed as
|
||||
raw `.MMB` blobs over the protocol.
|
||||
`NS_WriteFile_internal`, `NS_SeekFile_internal`).
|
||||
|
||||
> ⚠ **RETRACTED 2026-09-24.** This section originally concluded "**no generic
|
||||
> file-transfer command is exposed on the wire**", reasoning from the absence of
|
||||
> firmware strings. **That was wrong.** `SUB 0x94` / `0x48` / `0x8D` / `0x8E`
|
||||
> are exactly that — a read/write file transfer addressed by **full filesystem
|
||||
> path** — and they were caught in the open on the first capture that touched
|
||||
> the scheduler. See *The scheduler, and a generic file transfer* below.
|
||||
>
|
||||
> The lesson is the usual one: absence of a firmware string is not absence of a
|
||||
> command. The dispatch is a 68K jump table and the commands carry no strings
|
||||
> of their own.
|
||||
>
|
||||
> The *setups* half of the original claim survives: setups are **not** pushed as
|
||||
> raw `.MMB` blobs. They go through `0xDA` + the ordinary config block.
|
||||
|
||||
### `SUB 0x1A` reads the whole active setup — 2,092 bytes
|
||||
|
||||
@@ -722,10 +738,14 @@ Data is exactly **256 bytes: the file name, null-padded, nothing else.**
|
||||
54 45 53 54 31 2e 6d 6d 62 00 00 … "TEST1.mmb" + 247 × 0x00
|
||||
```
|
||||
|
||||
This is the missing link in *setups are files*: there is no generic
|
||||
file-transfer command because there does not need to be one. Thor names the
|
||||
target file, then writes the ordinary config block into it. The unit acks with
|
||||
`0x25` before any config bytes are sent.
|
||||
This is the missing link in *setups are files*: Thor names the target file, then
|
||||
writes the ordinary config block into it. The unit acks with `0x25` before any
|
||||
config bytes are sent.
|
||||
|
||||
Note `0xDA` takes a **bare filename** null-padded to 256 bytes. That is a
|
||||
different mechanism from the path-addressed file transfer (`0x94` / `0x8D`,
|
||||
which carry `\system\schedule\schedule.dat` unpadded) — setups do not go through
|
||||
the file transfer, and the file transfer is not how setups are written.
|
||||
|
||||
### Reads are single-step — no probe
|
||||
|
||||
@@ -920,6 +940,116 @@ a create and an overwrite. We have never seen this protocol report a *failed*
|
||||
write, so **do not treat a zero ack as proof a write was applied.** Read the
|
||||
config back and compare; `SUB 0x41` plus `SUB 0x1A` make that cheap.
|
||||
|
||||
## The scheduler, and a generic file transfer (2026-09-24)
|
||||
|
||||
Capture: `bridges/captures/9-24-26 - micromate2/*_read_scheduler.bin`.
|
||||
25 request frames, 25 responses, every checksum valid.
|
||||
|
||||
Four operations in one capture, segmented by Thor's `POLL` preamble (marks are
|
||||
not written into the raw `.bin` — in TCP mode seismo_lab logs them to the
|
||||
on-screen log only):
|
||||
|
||||
| frames | operation |
|
||||
|---|---|
|
||||
| 0–4 | read the schedule off the unit |
|
||||
| 5–18 | push a setup — the sequence already documented above, unchanged |
|
||||
| 19–21 | write the schedule back |
|
||||
| 22–24 | enable the scheduler |
|
||||
|
||||
### 🔑 `SUB 0x94` / `0x48` / `0x8D` / `0x8E` — file transfer by path
|
||||
|
||||
**The unit will read and write files addressed by full filesystem path.**
|
||||
|
||||
```
|
||||
0x94 <path> → 0x6B open for READ
|
||||
0x48 → 0xB7 read next page; repeat until an all-zero response
|
||||
0x8D <path> → 0x72 open for WRITE
|
||||
0x8E <body> → 0x71 write the file body
|
||||
```
|
||||
|
||||
The path is plain ASCII, **unpadded**, with `offset` = its exact length:
|
||||
|
||||
```
|
||||
5c 73 79 73 74 65 6d 5c 73 63 68 65 64 75 6c 65 5c 73 63 68 65 64 75 6c 65 2e 64 61 74
|
||||
\system\schedule\schedule.dat 29 bytes, offset = 0x001D
|
||||
```
|
||||
|
||||
`0x94` and `0x8D` sent **byte-identical** 29-byte payloads — the same path, only
|
||||
the command distinguishing read from write.
|
||||
|
||||
The `0x48` read is paged, and the page number comes back in the response header
|
||||
at `payload[3:5]`:
|
||||
|
||||
| call | page | payload | content |
|
||||
|---|---|---|---|
|
||||
| 1st | `0x0000` | 15 B | descriptor — `04 00 … 01 00 00 00 00 00 01` |
|
||||
| 2nd | `0x0002` | 535 B | the file body |
|
||||
| 3rd | `0x0000` | 11 B | all zeros = **end of file** |
|
||||
|
||||
⚠ This is the single most consequential find of the session, because it is not
|
||||
specific to the scheduler. **`callhome.MMB` is a file too**, and the call-home
|
||||
config is the last unsolved piece of the project. Reading it should be a
|
||||
matter of pointing `0x94` at the right path. That is a *lead*, not a result —
|
||||
no path other than `schedule.dat` has been tried.
|
||||
|
||||
### The schedule file itself
|
||||
|
||||
The write body is the read body minus an 11-byte response prefix (524 vs 535),
|
||||
so both describe the same structure:
|
||||
|
||||
```
|
||||
03 00 00 00 0f 03 02 [namelen] [setup-file name] …zeros… 27 03 10
|
||||
```
|
||||
|
||||
- **`[namelen]` is a length prefix**: `0x28` = 40 for the 40-character name read
|
||||
off the unit, `0x09` = 9 for `TEST1.mmb` written back. Confirmed both ways.
|
||||
- The trailing `27 03 10` sits at `0x108` in the write body and `0x113` in the
|
||||
read — the same offset once the 11-byte prefix is accounted for.
|
||||
|
||||
**A schedule entry names a setup file.** That is how the help text's "change
|
||||
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.
|
||||
|
||||
### `SUB 0x47` — the scheduler enable, probably
|
||||
|
||||
Step 4 is two bare `0x47` frames with no data, differing only in `params[7]`:
|
||||
|
||||
```
|
||||
req params = 00 00 00 00 00 00 00 [01] 00 00 → rsp 04 00 … 00 [01] 00 00 00 00 00 01
|
||||
req params = 00 00 00 00 00 00 00 [03] 00 00 → rsp 04 00 … 00 [03] 00 00 00 00 00 01
|
||||
```
|
||||
|
||||
The response echoes the selector and ends `01`. Its 15-byte shape is identical
|
||||
to the descriptor `0x48` returns on page 0.
|
||||
|
||||
⚠ **Whether `0x47` sets or merely reads is genuinely undetermined.** Both calls
|
||||
returned the same trailing `01`, and there is no before/after to compare — the
|
||||
scheduler was enabled in this same capture, so no "disabled" reading exists.
|
||||
`params[7]` is also the token position Series III uses, which is suggestive but
|
||||
not evidence. **Do not implement an enable against this until a
|
||||
disable-then-enable capture settles it.**
|
||||
|
||||
### The scheduler enable is NOT in the setup config block
|
||||
|
||||
A prediction made before this capture — that the `Scheduler On/Off` switch would
|
||||
appear as a byte in the `0x71` block, because the unit's help text lists it in
|
||||
the same edit screen as Record Mode and Sample Rate — **did not hold**.
|
||||
|
||||
The `0x71`, `0x68` and `0x82` payloads are **byte-identical** to the previous
|
||||
capture's, all three, zero differences. The scheduler was enabled without any
|
||||
of them changing.
|
||||
|
||||
⚠ The test is weaker than it looks: the operator re-sent the *same* config, so
|
||||
a byte-identical block is also what "nothing changed" looks like. What it does
|
||||
establish is that enabling the scheduler did **not** require a config write —
|
||||
whatever `0x47` does, it does alone.
|
||||
|
||||
## Monitoring control and the setup list (2026-09-24)
|
||||
|
||||
Capture: `bridges/captures/9-24-26 - micromate2/*_turn_on_monitormode_*`.
|
||||
|
||||
Reference in New Issue
Block a user