Compare commits
18 Commits
87fb14c71a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ebbb963bd | ||
|
|
4364d0ed48 | ||
|
|
21f0e455ee | ||
|
|
c0304c575c | ||
|
|
0d099235c5 | ||
|
|
57547fb6ed | ||
|
|
a187214e06 | ||
|
|
c007f2180c | ||
|
|
566b4ab108 | ||
|
|
8d98beb6aa | ||
|
|
fb282fea49 | ||
|
|
de992c6087 | ||
|
|
99946170cf | ||
|
|
fec48518c1 | ||
|
|
11b6a2dc3b | ||
|
|
3e1d30527f | ||
|
|
d9a9dade41 | ||
|
|
c0e3aaddee |
32
README.md
32
README.md
@@ -55,14 +55,29 @@ To start the current emulator harness:
|
|||||||
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\table-sweep-ack-000-07f.json --log captures\table-sweep-ack-000-07f.txt --result-json captures\table-sweep-ack-000-07f-result.json
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\table-sweep-ack-000-07f.json --log captures\table-sweep-ack-000-07f.txt --result-json captures\table-sweep-ack-000-07f-result.json
|
||||||
.\.venv\Scripts\python.exe scripts\state_map_runner.py --preset ok --prompt-screen
|
.\.venv\Scripts\python.exe scripts\state_map_runner.py --preset ok --prompt-screen
|
||||||
.\.venv\Scripts\python.exe scripts\state_map_runner.py --analyze-log captures\ack-race-000-001.txt
|
.\.venv\Scripts\python.exe scripts\state_map_runner.py --analyze-log captures\ack-race-000-001.txt
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --dry-run
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --port COM5 --duration 30 --log captures\ccu-keepalive.txt
|
||||||
.\.venv\Scripts\python.exe h8536_emulator_state_search.py --preset connect-queue --target ok --first-hit --json-out build\connect-state-search-ok.json
|
.\.venv\Scripts\python.exe h8536_emulator_state_search.py --preset connect-queue --target ok --first-hit --json-out build\connect-state-search-ok.json
|
||||||
.\.venv\Scripts\python.exe h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity
|
.\.venv\Scripts\python.exe h8536_emulator_bench_replay.py captures\bench-connect-lcd-sequence-20260525-214411.txt --assert-bench-parity
|
||||||
.\.venv\Scripts\python.exe h8536_emulator.py --max-steps 250000 --p9-fast-path --eeprom-seed blank --eeprom-save build\emulator-eeprom-boot.bin --eeprom-report build\emulator-eeprom-boot.txt --eeprom-report-json build\emulator-eeprom-boot.json
|
.\.venv\Scripts\python.exe h8536_emulator.py --max-steps 250000 --p9-fast-path --eeprom-seed blank --eeprom-save build\emulator-eeprom-boot.bin --eeprom-report build\emulator-eeprom-boot.txt --eeprom-report-json build\emulator-eeprom-boot.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The real-device bench helper uses `pyserial`; install repo dependencies with `.\.venv\Scripts\python.exe -m pip install -r requirements.txt` if needed.
|
The real-device bench helper uses `pyserial`; install repo dependencies with `.\.venv\Scripts\python.exe -m pip install -r requirements.txt` if needed.
|
||||||
|
Optional webcam snapshots for `scripts\serial_scenario.py` use OpenCV; install it only on the bench machine that needs panel photos:
|
||||||
|
|
||||||
The current PT2/protocol reconstruction is documented in [docs/pt2-protocol.md](docs/pt2-protocol.md).
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe -m pip install opencv-python
|
||||||
|
```
|
||||||
|
|
||||||
|
Example snapshot run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\copy-step-006d-006c-1000ms.json --parity E --quiet-console --log captures\copy-webcam.txt --result-json captures\copy-webcam-result.json --snapshot-dir captures\copy-webcam-shots --camera-index 4 --snapshot-delays 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Current bench calibration: webcam index `4` gives the useful panel view, and a `0.5` second post-command delay is enough for LCD changes to settle without producing too many images.
|
||||||
|
|
||||||
|
The current PT2/protocol reconstruction is documented in [docs/pt2-protocol.md](docs/pt2-protocol.md), with focused mini-notes for [COPY state](docs/pt2-copy-state-machine.md), [menu state](docs/pt2-menu-state-machine.md), the [session rhythm ROM trace](docs/pt2-session-rhythm-trace.md), [continuation commands](docs/pt2-continuation-command-trace.md), and [report aftermath handling](docs/pt2-report-aftermath-trace.md).
|
||||||
|
|
||||||
## Real Bench Serial Format
|
## Real Bench Serial Format
|
||||||
|
|
||||||
@@ -133,6 +148,8 @@ Minimal smoke-test shape:
|
|||||||
- Includes a bench ACK probe that reproduces the `01 00 00...` -> `01 00 01...` visible retry burst, waits for `07 80 40 20 90 2D`, then sends a candidate command-5 ACK and reports whether the target keeps repeating.
|
- Includes a bench ACK probe that reproduces the `01 00 00...` -> `01 00 01...` visible retry burst, waits for `07 80 40 20 90 2D`, then sends a candidate command-5 ACK and reports whether the target keeps repeating.
|
||||||
- Includes a checksum-resynchronizing bench receiver that scans RX byte streams for valid six-byte frames, avoids common shifted-heartbeat false locks, and can fall back to the old fixed six-byte slicer with `--sync fixed`.
|
- Includes a checksum-resynchronizing bench receiver that scans RX byte streams for valid six-byte frames, avoids common shifted-heartbeat false locks, and can fall back to the old fixed six-byte slicer with `--sync fixed`.
|
||||||
- Includes a JSON scenario bench runner for repeatable multi-step serial tests, including low-latency ACK-aware command-1 probes that can send the current command-5 ACK candidate immediately after the retry frame appears, with explicit max-ACK/max-target guardrails.
|
- Includes a JSON scenario bench runner for repeatable multi-step serial tests, including low-latency ACK-aware command-1 probes that can send the current command-5 ACK candidate immediately after the retry frame appears, with explicit max-ACK/max-target guardrails.
|
||||||
|
- The scenario runner can optionally schedule webcam snapshots on each command send, writing image paths into logs/result JSON without delaying timing-sensitive serial steps.
|
||||||
|
- Includes a modular fake-CCU runner in `ccu_emulator/` that seeds active state, listens for complete RCP report frames, and immediately sends the neutral command-5 ACK `05 00 40 00 00 1F`; optional periodic refresh frames let lamp/value streaming be tested separately from report ACKs.
|
||||||
- Includes a PT2 state-map-aware bench runner/analyzer for the current CONNECT gate proof: it hunts a fresh device `07...` visible-drain token candidate, sends exactly one selector-zero command-4 force, probes `E000[0]` with command 1, optionally uses command 7 to recover a hidden finalized response, and labels likely token-destroying turns.
|
- Includes a PT2 state-map-aware bench runner/analyzer for the current CONNECT gate proof: it hunts a fresh device `07...` visible-drain token candidate, sends exactly one selector-zero command-4 force, probes `E000[0]` with command 1, optionally uses command 7 to recover a hidden finalized response, and labels likely token-destroying turns.
|
||||||
- Includes a bounded emulator CONNECT state-search tool that patches small ROM-derived RAM/table surfaces, runs either the direct CONNECT branch or the selector-zero queue dispatch path, and classifies LCD outcomes as OK, DXC, NOT ACT, or other.
|
- Includes a bounded emulator CONNECT state-search tool that patches small ROM-derived RAM/table surfaces, runs either the direct CONNECT branch or the selector-zero queue dispatch path, and classifies LCD outcomes as OK, DXC, NOT ACT, or other.
|
||||||
- Includes a bench-log replay harness that feeds recorded host TX frames back into the ROM emulator with bench-style UART byte timing by default and asserts parity against the real device's observed response/LCD state.
|
- Includes a bench-log replay harness that feeds recorded host TX frames back into the ROM emulator with bench-style UART byte timing by default and asserts parity against the real device's observed response/LCD state.
|
||||||
@@ -150,7 +167,7 @@ Current serial observations:
|
|||||||
- Bench serial-format finding: real hardware talks `38400 8E1`. Earlier `8N1` captures primarily exercised SCI1 parity/error handling and retry echoes, not the normal command path. After switching bench scripts to even parity, the selector-zero CONNECT path can reach `CONNECT: OK`.
|
- Bench serial-format finding: real hardware talks `38400 8E1`. Earlier `8N1` captures primarily exercised SCI1 parity/error handling and retry echoes, not the normal command path. After switching bench scripts to even parity, the selector-zero CONNECT path can reach `CONNECT: OK`.
|
||||||
- Bench CONNECT recovery finding: `CONNECT:NOT ACT` is recoverable without a power cycle. This makes it a normal no-active-session/cleared-state display rather than a terminal latch; tests can now probe from the idle NOT ACT state directly, then separately check whether OK is held or needs periodic CCU-like refresh traffic.
|
- Bench CONNECT recovery finding: `CONNECT:NOT ACT` is recoverable without a power cycle. This makes it a normal no-active-session/cleared-state display rather than a terminal latch; tests can now probe from the idle NOT ACT state directly, then separately check whether OK is held or needs periodic CCU-like refresh traffic.
|
||||||
- Bench CONNECT cadence finding: the `40 -> 80 -> C0` sequence stayed at `CONNECT:NOT ACT` with 10 ms, 50 ms, and 150 ms gaps, but produced `CONNECT: OK` then returned to `CONNECT:NOT ACT` with 700 ms and 1.5 s gaps. At 700 ms, single `40`/`80`/`C0` frames did not work, but all tested two-frame pairs did. Repeated `80 -> 80` at about 700 ms also worked, so the values do not need to differ. The no-power-cycle NOT ACT recovery capture produced repeated `02 00 02 00 00 5A` OK-path responses before heartbeat traffic resumed.
|
- Bench CONNECT cadence finding: the `40 -> 80 -> C0` sequence stayed at `CONNECT:NOT ACT` with 10 ms, 50 ms, and 150 ms gaps, but produced `CONNECT: OK` then returned to `CONNECT:NOT ACT` with 700 ms and 1.5 s gaps. At 700 ms, single `40`/`80`/`C0` frames did not work, but all tested two-frame pairs did. Repeated `80 -> 80` at about 700 ms also worked, so the values do not need to differ. The no-power-cycle NOT ACT recovery capture produced repeated `02 00 02 00 00 5A` OK-path responses before heartbeat traffic resumed.
|
||||||
- Bench special-selector finding: in the CONNECT OK advance sweep, command-5 selector `0x006C` (`05 00 6C 00 00 33`) produced `CONNECT OK` then a blank LCD with the CAM POWER lamp still on, while selector `0x006D` (`05 00 6D 00 00 32`) produced `CONNECT OK` then `COPY IN PROGRESS` then `CONNECT NOT ACT`. A later fresh isolated `ack-006d` run in `captures/connect-ok-advance-special-20260526-153339.txt` reproduced the copy path after a relay power-cycle. Forced ROM decoding confirms `0x006C -> H'2FAF` and `0x006D -> H'3015`; the `0x006D` path sets display selector `F732=H'1903`, a long `F798` countdown, and the ROM contains the `COPY IN PROGRESS` LCD string. The RCP-TX7 manual identifies `COPY IN PROGRESS` as the multi-camera `COPY TO SLAVES` transfer state over the RS232C command-link system, with controls locked until `COPY COMPLETED`.
|
- Bench special-selector finding: in the CONNECT OK advance sweep, command-5 selector `0x006C` (`05 00 6C 00 00 33`) produced `CONNECT OK` then a blank LCD with the CAM POWER lamp still on, while selector `0x006D` (`05 00 6D 00 00 32`) produced `CONNECT OK` then `COPY IN PROGRESS` then `CONNECT NOT ACT`. A later fresh isolated `ack-006d` run in `captures/connect-ok-advance-special-20260526-153339.txt` reproduced the copy path after a relay power-cycle. Forced ROM decoding confirms `0x006C -> H'2FAF` and `0x006D -> H'3015`; the `0x006D` path sets display selector `F732=H'1903`, a long `F798` countdown, and the ROM contains the `COPY IN PROGRESS` LCD string. LCD dispatch now traces through `493E[0x19] -> H'930A`, with local table entry `H'9F6A` building `COPY IN PROGRESS` and entry `H'9FDA` building `COPY COMPLETED`; `0x006C` appears to be the completion/exit sibling only after `0x006D` has set the `F795.6/F795.7` copy flags. The RCP-side menu trace now also identifies page `0x01` table entry `H'6FF0` as `OTHERS / COPY TO SLAVES`; it is gated by `E400[0x0015]`, and its local copy-start branch also requires `F791.7` before setting `F76E.6`, `F795.7`, `F731.7`, `F798=H'C8`, `F711.7`, and `F726=H'64`. The RCP-TX7 manual identifies `COPY IN PROGRESS` as the multi-camera `COPY TO SLAVES` transfer state over the RS232C command-link system, with controls locked until `COPY COMPLETED`.
|
||||||
- ROM report-source finding: the active `02/01 ...` frames exposed during CONNECT OK attempts are autonomous `F870 -> BAF2 -> BA26` report-queue transmissions, not ordinary command-1 readbacks. The ROM sets `FAA2.3/FAA3.7` after sending them, so the CCU probably needs to answer in that continuation window with command `4`, `5`, or `6` to consume the report queue and keep the session alive.
|
- ROM report-source finding: the active `02/01 ...` frames exposed during CONNECT OK attempts are autonomous `F870 -> BAF2 -> BA26` report-queue transmissions, not ordinary command-1 readbacks. The ROM sets `FAA2.3/FAA3.7` after sending them, so the CCU probably needs to answer in that continuation window with command `4`, `5`, or `6` to consume the report queue and keep the session alive.
|
||||||
- Board/P9 finding: traced MCU pin 62 `P91` reaches X24164 pin 6 `SCL`, and MCU pin 68 `P97` reaches the shared X24164 pin 5 `SDA` node. The emulator now treats the ROM's `C121/C08B/C0DB/C10C/C142` P9 routines as an X24164-style two-wire EEPROM bus, with ROM logical addresses `0x000-0x7FF` on the `H'A0/H'A1` control-byte family and `0x800-0xFFF` on `H'E0/H'E1`.
|
- Board/P9 finding: traced MCU pin 62 `P91` reaches X24164 pin 6 `SCL`, and MCU pin 68 `P97` reaches the shared X24164 pin 5 `SDA` node. The emulator now treats the ROM's `C121/C08B/C0DB/C10C/C142` P9 routines as an X24164-style two-wire EEPROM bus, with ROM logical addresses `0x000-0x7FF` on the `H'A0/H'A1` control-byte family and `0x800-0xFFF` on `H'E0/H'E1`.
|
||||||
- EEPROM role finding: `loc_40BB` checks `P7DR.7` and the `F402 == H'6B6F` signature before defaulting EEPROM/shadow tables; `loc_4103` writes ROM default words through `BFE0`, `loc_41D2` reads sixteen 8-byte records into `F7B0-F82F`, and the command-4 path at `BD2B-BD5F` can persist serial table writes when `F76E.7` is set.
|
- EEPROM role finding: `loc_40BB` checks `P7DR.7` and the `F402 == H'6B6F` signature before defaulting EEPROM/shadow tables; `loc_4103` writes ROM default words through `BFE0`, `loc_41D2` reads sixteen 8-byte records into `F7B0-F82F`, and the command-4 path at `BD2B-BD5F` can persist serial table writes when `F76E.7` is set.
|
||||||
@@ -159,6 +176,7 @@ Current serial observations:
|
|||||||
- Emulator board-state finding: P7 now reads external pin state for input bits, so the DIP-off default is modeled as `--p7-input 0xFF`; `--eeprom-seed factory` can pre-seed the X24164 devices and `F400-F4FF` shadow from the ROM default table for already-initialized-state experiments.
|
- Emulator board-state finding: P7 now reads external pin state for input bits, so the DIP-off default is modeled as `--p7-input 0xFF`; `--eeprom-seed factory` can pre-seed the X24164 devices and `F400-F4FF` shadow from the ROM default table for already-initialized-state experiments.
|
||||||
- RX probe finding: the `--preset connect-lcd` sequence is sensitive to injection timing and modeled external state. With timed UART injection, the emulator can still reach `CONNECT: OK`/`02 00 02 00 00 5A`, while the real bench remains at `CONNECT NOT ACT`; this points to missing session/P9/external-panel context rather than a simple checksum or UART-spacing issue.
|
- RX probe finding: the `--preset connect-lcd` sequence is sensitive to injection timing and modeled external state. With timed UART injection, the emulator can still reach `CONNECT: OK`/`02 00 02 00 00 5A`, while the real bench remains at `CONNECT NOT ACT`; this points to missing session/P9/external-panel context rather than a simple checksum or UART-spacing issue.
|
||||||
- Emulator state-search finding: the minimum ROM-visible OK display condition is now reproducible without serial. Direct entry at `loc_2CB9` with `E000[0]=0x8080` and unsuppressed `F730=0` reaches `CONNECT: OK`; the queued selector-zero path also reaches OK when `F970[0]=0`, `F9B9=0`, `F9B4=1`, `E000[0]=0x8080`, and `F730=0`. This makes the bench problem sharper: prove whether serial can retain `E000[0]=0x8080` and enqueue selector zero without the reset/clobber path clearing it first.
|
- Emulator state-search finding: the minimum ROM-visible OK display condition is now reproducible without serial. Direct entry at `loc_2CB9` with `E000[0]=0x8080` and unsuppressed `F730=0` reaches `CONNECT: OK`; the queued selector-zero path also reaches OK when `F970[0]=0`, `F9B9=0`, `F9B4=1`, `E000[0]=0x8080`, and `F730=0`. This makes the bench problem sharper: prove whether serial can retain `E000[0]=0x8080` and enqueue selector zero without the reset/clobber path clearing it first.
|
||||||
|
- Emulator panel-input finding: `h8536_emulator_rx_probe.py` can now inject ROM-level panel edges using traced shadow/dirty bytes. `--panel-press cam-power` models `F105 -> F6D4.3/F6F2.4`, `--panel-press call` models `F006 -> F6DB.5/F6F3.3`, and raw specs such as `--panel F6D4.6=press` let adjacent report-capable table entries be probed while the ROM still decides whether a serial report, menu action, or no-op happens.
|
||||||
- Bench follow-up: replaying the emulator CONNECT sequence on the real device did not switch the LCD to OK. The real device answered the `04 00 00 80 00 DE` step with `07 80 C0 60 20 5D` in the captured run and remained at `CONNECT NOT ACT`, so the next mismatch to chase is the missing visible `07 80 C0 60 20 5D` response/session context rather than the LCD OK branch.
|
- Bench follow-up: replaying the emulator CONNECT sequence on the real device did not switch the LCD to OK. The real device answered the `04 00 00 80 00 DE` step with `07 80 C0 60 20 5D` in the captured run and remained at `CONNECT NOT ACT`, so the next mismatch to chase is the missing visible `07 80 C0 60 20 5D` response/session context rather than the LCD OK branch.
|
||||||
- CCU seed-hint finding: `build\rom_ccu_seed_hints.txt` currently ranks selector `0x000`, `0x0F6`, `0x003`, and `0x040` as the highest-value fake-CCU stream candidates. The generated seed frames are `00 00 00 80 80 5A`, `00 01 76 20 00 0D`, `00 00 03 80 00 D9`, and `00 00 40 FF FF 1A`, with command-1 readbacks listed beside them.
|
- CCU seed-hint finding: `build\rom_ccu_seed_hints.txt` currently ranks selector `0x000`, `0x0F6`, `0x003`, and `0x040` as the highest-value fake-CCU stream candidates. The generated seed frames are `00 00 00 80 80 5A`, `00 01 76 20 00 0D`, `00 00 03 80 00 D9`, and `00 00 40 FF FF 1A`, with command-1 readbacks listed beside them.
|
||||||
- Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM.
|
- Observed capture labels such as `cam_power_button_candidate` and `call_button_candidate` are deliberately treated as capture overlays, not protocol facts hard-coded in ROM.
|
||||||
@@ -249,6 +267,7 @@ For gate/queue and table reports:
|
|||||||
python h8536_serial_gate.py --help
|
python h8536_serial_gate.py --help
|
||||||
python h8536_rx_branch_trace.py --help
|
python h8536_rx_branch_trace.py --help
|
||||||
python h8536_report_source_trace.py --help
|
python h8536_report_source_trace.py --help
|
||||||
|
python h8536_panel_button_trace.py --help
|
||||||
python h8536_table_xrefs.py --help
|
python h8536_table_xrefs.py --help
|
||||||
python h8536_ccu_seed_hints.py --help
|
python h8536_ccu_seed_hints.py --help
|
||||||
python h8536_eeprom_layout.py --help
|
python h8536_eeprom_layout.py --help
|
||||||
@@ -258,6 +277,7 @@ python h8536_consistency.py --help
|
|||||||
- `h8536_serial_gate.py`: reports the autonomous TX gate and report queue evidence.
|
- `h8536_serial_gate.py`: reports the autonomous TX gate and report queue evidence.
|
||||||
- `h8536_rx_branch_trace.py`: reports the SCI1 RX branch tree. Current finding: command `0x04/0x05/0x06` are continuation-path commands behind `FAA2 != 0`, so a standalone command-4 force from idle should not reach `BD0E`.
|
- `h8536_rx_branch_trace.py`: reports the SCI1 RX branch tree. Current finding: command `0x04/0x05/0x06` are continuation-path commands behind `FAA2 != 0`, so a standalone command-4 force from idle should not reach `BD0E`.
|
||||||
- `h8536_report_source_trace.py`: traces direct `loc_3E54` report enqueue sources. Current finding: no direct static `R3 = 0x0007` enqueue in the JSON, so CAM power `0x0007` remains runtime/capture-observed unless a later indirect/table path proves it.
|
- `h8536_report_source_trace.py`: traces direct `loc_3E54` report enqueue sources. Current finding: no direct static `R3 = 0x0007` enqueue in the JSON, so CAM power `0x0007` remains runtime/capture-observed unless a later indirect/table path proves it.
|
||||||
|
- `h8536_panel_button_trace.py`: decodes the `H'2706` panel-button jump table and traces known CALL/CAM POWER reports back through the input shadow bytes.
|
||||||
- `h8536_table_xrefs.py`: emits candidate table/index xrefs and LCD text correlation hints.
|
- `h8536_table_xrefs.py`: emits candidate table/index xrefs and LCD text correlation hints.
|
||||||
- `h8536_ccu_seed_hints.py`: mines table, dispatch, LCD, and observed-report hints for the CCU-side state stream the RCP may expect before active displays/reports.
|
- `h8536_ccu_seed_hints.py`: mines table, dispatch, LCD, and observed-report hints for the CCU-side state stream the RCP may expect before active displays/reports.
|
||||||
- `h8536_eeprom_layout.py`: mines the X24164 EEPROM layout, ROM factory defaults, persistent record slots, and serial selector-to-EEPROM offset mapping.
|
- `h8536_eeprom_layout.py`: mines the X24164 EEPROM layout, ROM factory defaults, persistent record slots, and serial selector-to-EEPROM offset mapping.
|
||||||
@@ -289,6 +309,9 @@ python h8536_emulator_rx_divergence.py --help
|
|||||||
- `--trace-report-gates`, `--trace-report-queue`, and `--trace-ram-lifecycle`: inspect the serial report queue, `loc_4046`/`F9C4` gate, and watched RAM byte history.
|
- `--trace-report-gates`, `--trace-report-queue`, and `--trace-ram-lifecycle`: inspect the serial report queue, `loc_4046`/`F9C4` gate, and watched RAM byte history.
|
||||||
- `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame.
|
- `--target-frame "00 00 00 00 80 DA"`: compare staged/emitted TX bytes against an expected six-byte frame.
|
||||||
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.
|
- `h8536_emulator_rx_probe.py "04 00 00 80 00"`: append the checksum, inject the host frame through SCI1 RX, and summarize responses.
|
||||||
|
- `h8536_emulator_rx_probe.py --wait-heartbeats 1 --panel-press cam-power`: inject the traced CAM POWER edge into the ROM panel shadow/dirty path and listen for the resulting selector `0x0007` behavior.
|
||||||
|
- `h8536_emulator_rx_probe.py --panel call=press --panel call=release --keep-listening`: preserve ordered synthetic panel actions and test the CALL active/inactive path through the normal ROM dispatcher.
|
||||||
|
- `h8536_emulator_rx_probe.py --panel F6D4.6=press`: probe an adjacent raw matrix bit from the table at `H'2706`; raw specs may be shadow bytes like `F6D4.6` or source bytes like `F105.6`.
|
||||||
- `h8536_emulator_rx_probe.py --uart-timing --uart-baud 38400 "04 00 00 80 00"`: inject all six host bytes with bench-style wire spacing of about 260 us per byte, letting RXI/TXI/timers interleave; if the ROM has not cleared `RDRF` before the next byte, the SCI model raises `ORER`. The real bench link is `8E1`.
|
- `h8536_emulator_rx_probe.py --uart-timing --uart-baud 38400 "04 00 00 80 00"`: inject all six host bytes with bench-style wire spacing of about 260 us per byte, letting RXI/TXI/timers interleave; if the ROM has not cleared `RDRF` before the next byte, the SCI model raises `ORER`. The real bench link is `8E1`.
|
||||||
- `h8536_emulator_rx_probe.py --uart-timing --uart-format 8E1 --tx-wire-timing --wait-heartbeats 2 --post-frame-ms 700 "04 00 00 80 00 DE" "04 00 00 80 00 DE"`: replay the CONNECT refresh shape after heartbeat readiness and keep the emulator running for a bench-scale gap after each frame. The RAM trace now tags interesting accesses with the executing ROM PC, models SCI1 TDRE/TXI at 8E1 character time, and reports whether X24164 EEPROM bytes were written.
|
- `h8536_emulator_rx_probe.py --uart-timing --uart-format 8E1 --tx-wire-timing --wait-heartbeats 2 --post-frame-ms 700 "04 00 00 80 00 DE" "04 00 00 80 00 DE"`: replay the CONNECT refresh shape after heartbeat readiness and keep the emulator running for a bench-scale gap after each frame. The RAM trace now tags interesting accesses with the executing ROM PC, models SCI1 TDRE/TXI at 8E1 character time, and reports whether X24164 EEPROM bytes were written.
|
||||||
- `h8536_emulator_rx_probe.py --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
- `h8536_emulator_rx_probe.py --preset connect-lcd`: replay the current CONNECT LCD activation candidates.
|
||||||
@@ -336,14 +359,17 @@ python h8536_emulator_rx_divergence.py --help
|
|||||||
- `h8536/protocol_capture.py`: timestamped serial capture parser, frame recombiner, and cadence/gate-session analyzer.
|
- `h8536/protocol_capture.py`: timestamped serial capture parser, frame recombiner, and cadence/gate-session analyzer.
|
||||||
- `h8536/serial_scenario.py`: JSON-driven bench scenario engine shared by real-device serial scripts.
|
- `h8536/serial_scenario.py`: JSON-driven bench scenario engine shared by real-device serial scripts.
|
||||||
- `h8536/state_map_runner.py`: PT2 state-map proof runner and bench-log analyzer for visible-drain token, selector-zero force, `E000[0]` readback, and command-7 recovery experiments.
|
- `h8536/state_map_runner.py`: PT2 state-map proof runner and bench-log analyzer for visible-drain token, selector-zero force, `E000[0]` readback, and command-7 recovery experiments.
|
||||||
|
- `ccu_emulator/`: modular fake-CCU package for reactive report ACKs and optional periodic state refreshes.
|
||||||
- `h8536/serial_gate.py`: autonomous TX gate/queue state-machine reconstruction.
|
- `h8536/serial_gate.py`: autonomous TX gate/queue state-machine reconstruction.
|
||||||
- `h8536/report_source_trace.py`: direct `loc_3E54` report enqueue source tracer.
|
- `h8536/report_source_trace.py`: direct `loc_3E54` report enqueue source tracer.
|
||||||
|
- `h8536/panel_button_trace.py`: panel button matrix/jump-table tracer for CALL, CAM POWER, and adjacent report-capable button paths.
|
||||||
- `h8536/table_xrefs.py`: table/index xrefs and LCD correlation report generation.
|
- `h8536/table_xrefs.py`: table/index xrefs and LCD correlation report generation.
|
||||||
- `h8536/ccu_seed_hints.py`: ROM miner for likely fake-CCU state seed selectors and candidate command/readback frames.
|
- `h8536/ccu_seed_hints.py`: ROM miner for likely fake-CCU state seed selectors and candidate command/readback frames.
|
||||||
- `h8536/eeprom_layout.py`: ROM miner for X24164 EEPROM defaults, 8-byte record slots, and serial persistence mapping.
|
- `h8536/eeprom_layout.py`: ROM miner for X24164 EEPROM defaults, 8-byte record slots, and serial persistence mapping.
|
||||||
- `h8536/consistency.py`: decompiler/pseudocode semantic consistency checks.
|
- `h8536/consistency.py`: decompiler/pseudocode semantic consistency checks.
|
||||||
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, bench-style UART injection timing, P9/X24164 EEPROM bus model, LCD model, manual-derived FRT timer scheduling, runner, probe, CLI, and peripheral scaffolding.
|
- `h8536/emulator/`: early H8/536 emulator package split into CPU state, memory map, SCI1 TX capture, bench-style UART injection timing, P9/X24164 EEPROM bus model, LCD model, manual-derived FRT timer scheduling, runner, probe, CLI, and peripheral scaffolding.
|
||||||
- `h8536/emulator/eeprom_image.py`: logical EEPROM image dump/report helpers for emulator runs, including factory diffs and record-slot summaries.
|
- `h8536/emulator/eeprom_image.py`: logical EEPROM image dump/report helpers for emulator runs, including factory diffs and record-slot summaries.
|
||||||
|
- `h8536/emulator/panel.py`: synthetic panel input descriptors for known CALL/CAM POWER edges and raw ROM shadow/source-bit injection.
|
||||||
- `h8536/emulator/rx_probe.py`: host-frame injection and response/listener probe for SCI1 RX experiments.
|
- `h8536/emulator/rx_probe.py`: host-frame injection and response/listener probe for SCI1 RX experiments.
|
||||||
- `h8536/emulator/state_search.py`: bounded internal-state search for CONNECT LCD outcomes using ROM execution plus explicit RAM/table patches.
|
- `h8536/emulator/state_search.py`: bounded internal-state search for CONNECT LCD outcomes using ROM execution plus explicit RAM/table patches.
|
||||||
- `h8536/board_profile.py`: Sony RCP-TX7 board-trace annotations, including the MAX202 RS232 path.
|
- `h8536/board_profile.py`: Sony RCP-TX7 board-trace annotations, including the MAX202 RS232 path.
|
||||||
@@ -354,7 +380,7 @@ python h8536_emulator_rx_divergence.py --help
|
|||||||
- `h8536_pseudocode.py`: pseudocode CLI wrapper.
|
- `h8536_pseudocode.py`: pseudocode CLI wrapper.
|
||||||
- `h8536_serial_pseudocode.py`: focused serial pseudocode CLI wrapper.
|
- `h8536_serial_pseudocode.py`: focused serial pseudocode CLI wrapper.
|
||||||
- `h8536_protocol_trace.py`, `h8536_protocol_capture.py`: protocol analysis CLI wrappers.
|
- `h8536_protocol_trace.py`, `h8536_protocol_capture.py`: protocol analysis CLI wrappers.
|
||||||
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_table_xrefs.py`, `h8536_ccu_seed_hints.py`, `h8536_eeprom_layout.py`, `h8536_consistency.py`: sidecar analysis CLI wrappers.
|
- `h8536_serial_gate.py`, `h8536_report_source_trace.py`, `h8536_panel_button_trace.py`, `h8536_table_xrefs.py`, `h8536_ccu_seed_hints.py`, `h8536_eeprom_layout.py`, `h8536_consistency.py`: sidecar analysis CLI wrappers.
|
||||||
- `h8536_emulator.py`, `h8536_emulator_probe.py`, `h8536_emulator_rx_probe.py`, `h8536_emulator_rx_divergence.py`, `h8536_emulator_bench_replay.py`: emulator CLI wrappers.
|
- `h8536_emulator.py`, `h8536_emulator_probe.py`, `h8536_emulator_rx_probe.py`, `h8536_emulator_rx_divergence.py`, `h8536_emulator_bench_replay.py`: emulator CLI wrappers.
|
||||||
- `h8536_emulator_state_search.py`: emulator CONNECT state-search CLI wrapper.
|
- `h8536_emulator_state_search.py`: emulator CONNECT state-search CLI wrapper.
|
||||||
- `scripts/bench_connect_lcd_sequence.py`: real-device COM5/COM6 bench runner for the CONNECT LCD sequence.
|
- `scripts/bench_connect_lcd_sequence.py`: real-device COM5/COM6 bench runner for the CONNECT LCD sequence.
|
||||||
|
|||||||
2951
build/panel_button_trace.json
Normal file
2951
build/panel_button_trace.json
Normal file
File diff suppressed because it is too large
Load Diff
74
build/panel_button_trace.md
Normal file
74
build/panel_button_trace.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# PT2 Known Button ROM Trace
|
||||||
|
|
||||||
|
This report follows the panel button edge path from the serial-visible reports back into the ROM input scanner.
|
||||||
|
The key table is the indirect handler table at `H'2706`, used by `loc_1C0E` after byte-level panel input changes are detected.
|
||||||
|
|
||||||
|
## Known Anchors
|
||||||
|
|
||||||
|
### CAM POWER
|
||||||
|
|
||||||
|
- Emitted selector: `0x0007`
|
||||||
|
- Handler: `H'1F40`
|
||||||
|
- Edge source: `F105 -> F6D4` via `F6F2.4`
|
||||||
|
- Trigger bit: `F6D4.3`
|
||||||
|
- Table slot: `H'274C` -> `H'1F40`
|
||||||
|
- Current-level tests: F6D4.3
|
||||||
|
- State writes: E80E
|
||||||
|
|
||||||
|
### IRIS/M.BLACK LINK
|
||||||
|
|
||||||
|
- Emitted selector: `0x0013`
|
||||||
|
- Handler: `H'200E`
|
||||||
|
- Edge source: `F006 -> F6DB` via `F6F3.3`
|
||||||
|
- Trigger bit: `F6DB.7`
|
||||||
|
- Table slot: `H'27C4` -> `H'200E`
|
||||||
|
- Current-level tests: F6DB.7
|
||||||
|
- State writes: E826
|
||||||
|
|
||||||
|
### CALL
|
||||||
|
|
||||||
|
- Emitted selector: `0x0015`
|
||||||
|
- Handler: `H'20A1`
|
||||||
|
- Edge source: `F006 -> F6DB` via `F6F3.3`
|
||||||
|
- Trigger bit: `F6DB.5`
|
||||||
|
- Table slot: `H'27C0` -> `H'20A1`
|
||||||
|
- Current-level tests: F6DB.5
|
||||||
|
- State writes: E82A
|
||||||
|
|
||||||
|
## Button Matrix Entries With Serial Reports
|
||||||
|
|
||||||
|
| Source | Shadow bit | Dirty | Handler | Selector(s) | State writes |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `F105` | `F6D4.6` | `F6F2.4` | `H'2048` | `0x006B` | `E8D6` |
|
||||||
|
| `F105` | `F6D4.3` | `F6F2.4` | `H'1F40` | `0x0007` | `E80E` |
|
||||||
|
| `F105` | `F6D4.2` | `F6F2.4` | `H'1EDE` | `0x0017`, `0x0018` | `E82E`, `E830` |
|
||||||
|
| `F105` | `F6D4.1` | `F6F2.4` | `H'1EA9` | `0x00F8` | `E9F0` |
|
||||||
|
| `F105` | `F6D4.0` | `F6F2.4` | `H'1E20` | `0x00B7`, `0x00C4`, `0x00C6`, `0x0097` | `E96E`, `E988`, `E98C`, `E92E` |
|
||||||
|
| `F106` | `F6D3.7` | `F6F2.3` | `H'1DCE` | `0x0096` | `E92C` |
|
||||||
|
| `F106` | `F6D3.6` | `F6F2.3` | `H'1D87` | `0x0097` | `E92E` |
|
||||||
|
| `F106` | `F6D3.5` | `F6F2.3` | `H'1D56` | `0x001A` | `E834` |
|
||||||
|
| `F106` | `F6D3.4` | `F6F2.3` | `H'1D25` | `0x001A` | `E834` |
|
||||||
|
| `F106` | `F6D3.3` | `F6F2.3` | `H'1CF4` | `0x001A` | `E834` |
|
||||||
|
| `F106` | `F6D3.2` | `F6F2.3` | `H'1CCE` | `0x001A` | `E834` |
|
||||||
|
| `F106` | `F6D3.1` | `F6F2.3` | `H'1CB2` | `0x001A` | `E834` |
|
||||||
|
| `F109` | `F6D0.7` | `F6F2.0` | `H'24E8` | `0x008F` | `E91E` |
|
||||||
|
| `F109` | `F6D0.6` | `F6F2.0` | `H'252E` | `0x008F` | `E91E` |
|
||||||
|
| `F109` | `F6D0.3` | `F6F2.0` | `H'24A9` | `0x0083` | `E906` |
|
||||||
|
| `F109` | `F6D0.2` | `F6F2.0` | `H'2408` | `0x0083` | `E906` |
|
||||||
|
| `F109` | `F6D0.1` | `F6F2.0` | `H'2390` | `0x0083` | `E906` |
|
||||||
|
| `F005` | `F6DC.7` | `F6F3.4` | `H'20F1` | `0x00B9` | `E972` |
|
||||||
|
| `F005` | `F6DC.5` | `F6F3.4` | `H'2204` | `0x0093` | `E926` |
|
||||||
|
| `F005` | `F6DC.4` | `F6F3.4` | `H'226D` | `0x0093` | `E926` |
|
||||||
|
| `F005` | `F6DC.3` | `F6F3.4` | `H'22A6` | `0x0093` | `E926` |
|
||||||
|
| `F005` | `F6DC.1` | `F6F3.4` | `H'22FC` | `0x0093` | `E926` |
|
||||||
|
| `F005` | `F6DC.0` | `F6F3.4` | `H'2326` | `0x0093` | `E926` |
|
||||||
|
| `F006` | `F6DB.7` | `F6F3.3` | `H'200E` | `0x0013` | `E826` |
|
||||||
|
| `F006` | `F6DB.5` | `F6F3.3` | `H'20A1` | `0x0015` | `E82A` |
|
||||||
|
| `F006` | `F6DB.3` | `F6F3.3` | `H'20BE` | `0x009A` | `E934` |
|
||||||
|
|
||||||
|
## Practical Read
|
||||||
|
|
||||||
|
- CALL and CAM POWER do share the general panel edge path with many other buttons.
|
||||||
|
- The shared path is: panel byte snapshot -> shadow byte -> dirty bit -> `loc_1C0E` jump table -> handler -> `loc_3E54` report.
|
||||||
|
- Other buttons diverge in their handlers: many require `F731/F730/F791` session/menu gates, mutate page state, or emit different selectors.
|
||||||
|
- Some table entries are `H'1C25`, an immediate `RTS`, so those physical matrix positions are intentionally ignored in this firmware context.
|
||||||
200
build/rom_0013_button_handler_linear.asm
Normal file
200
build/rom_0013_button_handler_linear.asm
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
; H8/536 ROM disassembly
|
||||||
|
; input: ROM\M27C512@DIP28_1.BIN
|
||||||
|
; bytes: 65536
|
||||||
|
; vector mode: min
|
||||||
|
; analysis: linear sweep
|
||||||
|
;
|
||||||
|
; Notes from the manual:
|
||||||
|
; - H8/536 uses the H8/500 CPU instruction set.
|
||||||
|
; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.
|
||||||
|
; - The register field is H'FE80-H'FFFF; names below come from appendix B.
|
||||||
|
; - @aa:8 short absolute operands use BR as the upper address byte.
|
||||||
|
; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.
|
||||||
|
; - LCD inference treats E-clock H'F200/H'F201 accesses as status/control and data candidates.
|
||||||
|
|
||||||
|
; Memory Map
|
||||||
|
; H'0000-H'009F exception_vectors vectors
|
||||||
|
; H'00A0-H'00FF dtc_vectors dtc_vectors
|
||||||
|
; H'0100-H'F67F program_or_external program
|
||||||
|
; H'F680-H'FE7F on_chip_ram ram
|
||||||
|
; H'FE80-H'FFFF register_field registers
|
||||||
|
|
||||||
|
; Vectors
|
||||||
|
; H'0000 reset -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0004 invalid_instruction -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0006 zero_divide -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0008 trap_vs -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0010 address_error -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0012 trace -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0016 nmi -> vec_nmi_4393 (H'4393)
|
||||||
|
; H'0020 trapa_0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0022 trapa_1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0024 trapa_2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0026 trapa_3 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0028 trapa_4 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002A trapa_5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002C trapa_6 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002E trapa_7 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0030 trapa_8 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0032 trapa_9 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0034 trapa_a -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0036 trapa_b -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0038 trapa_c -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003A trapa_d -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003C trapa_e -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003E trapa_f -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0040 irq0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0042 interval_timer -> vec_interval_timer_BFC4 (H'BFC4)
|
||||||
|
; H'0048 irq1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0050 irq2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0052 irq3 -> vec_irq3_3C30 (H'3C30)
|
||||||
|
; H'0058 irq4 -> vec_irq4_3AC7 (H'3AC7)
|
||||||
|
; H'005A irq5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0062 frt1_ocia -> vec_frt1_ocia_BEEA (H'BEEA)
|
||||||
|
; H'006A frt2_ocia -> vec_frt2_ocia_BF23 (H'BF23)
|
||||||
|
; H'0080 sci1_eri -> vec_sci1_eri_BB57 (H'BB57)
|
||||||
|
; H'0082 sci1_rxi -> vec_sci1_rxi_BB67 (H'BB67)
|
||||||
|
; H'0084 sci1_txi -> vec_sci1_txi_BA84 (H'BA84)
|
||||||
|
; H'0090 ad_adi -> vec_ad_adi_3D99 (H'3D99)
|
||||||
|
|
||||||
|
; Symbols
|
||||||
|
; mem_E026 H'E026 program_or_external memory r=4 w=0 width=word
|
||||||
|
; mem_E02A H'E02A program_or_external memory r=1 w=0 width=word
|
||||||
|
; mem_E046 H'E046 program_or_external memory r=0 w=1 width=word
|
||||||
|
; mem_E110 H'E110 program_or_external memory r=1 w=0 width=word
|
||||||
|
; mem_E134 H'E134 program_or_external memory r=1 w=0 width=word
|
||||||
|
; mem_E824 H'E824 program_or_external memory r=0 w=1 width=word
|
||||||
|
; mem_E826 H'E826 program_or_external memory r=0 w=4 width=word
|
||||||
|
; mem_E82A H'E82A program_or_external memory r=0 w=1 width=word
|
||||||
|
; mem_E8D6 H'E8D6 program_or_external memory r=0 w=1 width=word
|
||||||
|
; ram_F6D4 H'F6D4 on_chip_ram ram r=1 w=0 width=byte
|
||||||
|
; ram_F6DB H'F6DB on_chip_ram ram r=3 w=0 width=byte
|
||||||
|
; ram_F713 H'F713 on_chip_ram ram r=2 w=2 width=byte
|
||||||
|
; ram_F726 H'F726 on_chip_ram ram r=0 w=1 width=byte
|
||||||
|
; ram_F730 H'F730 on_chip_ram ram r=1 w=0 width=byte
|
||||||
|
; ram_F731 H'F731 on_chip_ram ram r=5 w=2 width=byte
|
||||||
|
; ram_F732 H'F732 on_chip_ram ram r=0 w=1 width=word
|
||||||
|
; ram_F76A H'F76A on_chip_ram ram r=0 w=1 width=word
|
||||||
|
; ram_F76E H'F76E on_chip_ram ram r=1 w=1 width=byte
|
||||||
|
; ram_F791 H'F791 on_chip_ram ram r=2 w=1 width=byte
|
||||||
|
; ram_F798 H'F798 on_chip_ram ram r=0 w=2 width=byte
|
||||||
|
; ram_FB03 H'FB03 on_chip_ram ram r=1 w=1 width=byte
|
||||||
|
|
||||||
|
; Board Profile
|
||||||
|
; Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.
|
||||||
|
; H8 pin 66 P95/TXD (TXD) -> MAX202 pin 11
|
||||||
|
; H8 pin 67 P96/RXD (RXD) -> MAX202 pin 12
|
||||||
|
; SCI2 pin routing is disabled by SYSCR2.P9SCI2E=0 in the observed setup.
|
||||||
|
|
||||||
|
; LCD/Text Scan
|
||||||
|
; search 'CONNECT': not literal, hits=0
|
||||||
|
|
||||||
|
1FC0: F7 13 C5 1D ROTR.B @(H'13C5,R7)
|
||||||
|
1FC4: E8 24 07 80 00 MOV:G.W #H'8000, @(H'24,R0)
|
||||||
|
1FC9: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
1FCB: 5B 40 12 MOV:I.W #H'4012, R3 ; dataflow R3=H'4012
|
||||||
|
1FCE: 1E 1E 83 BSR loc_3E54
|
||||||
|
1FD1: 19 RTS
|
||||||
|
1FD2: 15 F7 91 D7 BCLR.B #7, @H'F791 ; refs ram_F791 in on_chip_ram
|
||||||
|
1FD6: 15 F7 13 D5 BCLR.B #5, @H'F713 ; refs ram_F713 in on_chip_ram
|
||||||
|
1FDA: 1D E8 24 06 00 MOV:G.W #H'00, @H'E824 ; refs mem_E824 in program_or_external
|
||||||
|
1FDF: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
1FE1: 5B 40 12 MOV:I.W #H'4012, R3 ; dataflow R3=H'4012
|
||||||
|
1FE4: 1E 1E 6D BSR loc_3E54
|
||||||
|
1FE7: 19 RTS
|
||||||
|
1FE8: 1D E0 26 80 MOV:G.W @H'E026, R0 ; refs mem_E026 in program_or_external
|
||||||
|
1FEC: A8 CF BSET.W #15, R0
|
||||||
|
1FEE: 1D E8 26 90 MOV:G.W R0, @H'E826 ; refs mem_E826 in program_or_external
|
||||||
|
1FF2: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
1FF4: 5B 00 13 MOV:I.W #H'0013, R3 ; dataflow R3=H'0013
|
||||||
|
1FF7: 1E 1E 5A BSR loc_3E54
|
||||||
|
1FFA: 19 RTS
|
||||||
|
1FFB: 1D E0 26 80 MOV:G.W @H'E026, R0 ; refs mem_E026 in program_or_external
|
||||||
|
1FFF: A8 DF BCLR.W #15, R0
|
||||||
|
2001: 1D E8 26 90 MOV:G.W R0, @H'E826 ; refs mem_E826 in program_or_external
|
||||||
|
2005: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
2007: 5B 00 13 MOV:I.W #H'0013, R3 ; dataflow R3=H'0013
|
||||||
|
200A: 1E 1E 47 BSR loc_3E54
|
||||||
|
200D: 19 RTS
|
||||||
|
200E: 15 F6 DB F7 BTST.B #7, @H'F6DB ; refs ram_F6DB in on_chip_ram
|
||||||
|
2012: 27 33 BEQ loc_2047
|
||||||
|
2014: 15 F7 31 04 03 CMP:G.B #H'03, @H'F731 ; refs ram_F731 in on_chip_ram
|
||||||
|
2019: 22 2C BHI loc_2047
|
||||||
|
201B: 15 F7 91 F5 BTST.B #5, @H'F791 ; refs ram_F791 in on_chip_ram
|
||||||
|
201F: 26 14 BNE loc_2035
|
||||||
|
2021: 1D E0 26 80 MOV:G.W @H'E026, R0 ; refs mem_E026 in program_or_external
|
||||||
|
2025: A8 CE BSET.W #14, R0
|
||||||
|
2027: 1D E8 26 90 MOV:G.W R0, @H'E826 ; refs mem_E826 in program_or_external
|
||||||
|
202B: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
202D: 5B 00 13 MOV:I.W #H'0013, R3 ; dataflow R3=H'0013
|
||||||
|
2030: 1E 1E 21 BSR loc_3E54
|
||||||
|
2033: 20 12 BRA loc_2047
|
||||||
|
|
||||||
|
loc_2035:
|
||||||
|
2035: 1D E0 26 80 MOV:G.W @H'E026, R0 ; refs mem_E026 in program_or_external
|
||||||
|
2039: A8 DE BCLR.W #14, R0
|
||||||
|
203B: 1D E8 26 90 MOV:G.W R0, @H'E826 ; refs mem_E826 in program_or_external
|
||||||
|
203F: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
2041: 5B 00 13 MOV:I.W #H'0013, R3 ; dataflow R3=H'0013
|
||||||
|
2044: 1E 1E 0D BSR loc_3E54
|
||||||
|
|
||||||
|
loc_2047:
|
||||||
|
2047: 19 RTS
|
||||||
|
2048: 15 F6 D4 F6 BTST.B #6, @H'F6D4 ; refs ram_F6D4 in on_chip_ram
|
||||||
|
204C: 27 52 BEQ loc_20A0
|
||||||
|
204E: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram
|
||||||
|
2053: 22 4B BHI loc_20A0
|
||||||
|
2055: 15 F7 30 F7 BTST.B #7, @H'F730 ; refs ram_F730 in on_chip_ram
|
||||||
|
2059: 27 19 BEQ loc_2074
|
||||||
|
205B: 1D E8 D6 07 80 00 MOV:G.W #H'8000, @H'E8D6 ; refs mem_E8D6 in program_or_external
|
||||||
|
2061: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
2063: 5B 00 6B MOV:I.W #H'006B, R3 ; dataflow R3=H'006B
|
||||||
|
2066: 1E 1D EB BSR loc_3E54
|
||||||
|
2069: 15 F7 31 C7 BSET.B #7, @H'F731 ; refs ram_F731 in on_chip_ram
|
||||||
|
206D: 15 F7 98 06 C8 MOV:G.B #H'C8, @H'F798 ; refs ram_F798 in on_chip_ram
|
||||||
|
2072: 20 2C BRA loc_20A0
|
||||||
|
|
||||||
|
loc_2074:
|
||||||
|
2074: 1D F7 32 13 CLR.W @H'F732 ; refs ram_F732 in on_chip_ram
|
||||||
|
2078: 15 FB 03 D7 BCLR.B #7, @H'FB03 ; refs ram_FB03 in on_chip_ram
|
||||||
|
207C: 1D E0 46 13 CLR.W @H'E046 ; refs mem_E046 in program_or_external
|
||||||
|
2080: 1D F7 6A 13 CLR.W @H'F76A ; refs ram_F76A in on_chip_ram
|
||||||
|
2084: 1E 28 73 BSR loc_48FA
|
||||||
|
2087: 15 F7 13 C6 BSET.B #6, @H'F713 ; refs ram_F713 in on_chip_ram
|
||||||
|
208B: 15 F7 26 06 1E MOV:G.B #H'1E, @H'F726 ; refs ram_F726 in on_chip_ram
|
||||||
|
2090: 15 F7 6E C6 BSET.B #6, @H'F76E ; refs ram_F76E in on_chip_ram
|
||||||
|
2094: 15 F7 31 C7 BSET.B #7, @H'F731 ; refs ram_F731 in on_chip_ram
|
||||||
|
2098: 15 F7 98 06 C8 MOV:G.B #H'C8, @H'F798 ; refs ram_F798 in on_chip_ram
|
||||||
|
209D: 1E 36 38 BSR loc_56D8
|
||||||
|
|
||||||
|
loc_20A0:
|
||||||
|
20A0: 19 RTS
|
||||||
|
20A1: 1D E0 2A 80 MOV:G.W @H'E02A, R0 ; refs mem_E02A in program_or_external
|
||||||
|
20A5: 15 F6 DB F5 BTST.B #5, @H'F6DB ; refs ram_F6DB in on_chip_ram
|
||||||
|
20A9: 27 04 BEQ loc_20AF
|
||||||
|
20AB: A8 CF BSET.W #15, R0
|
||||||
|
20AD: 20 02 BRA loc_20B1
|
||||||
|
|
||||||
|
loc_20AF:
|
||||||
|
20AF: A8 DF BCLR.W #15, R0
|
||||||
|
|
||||||
|
loc_20B1:
|
||||||
|
20B1: 1D E8 2A 90 MOV:G.W R0, @H'E82A ; refs mem_E82A in program_or_external
|
||||||
|
20B5: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
20B7: 5B 00 15 MOV:I.W #H'0015, R3 ; dataflow R3=H'0015
|
||||||
|
20BA: 1E 1D 97 BSR loc_3E54
|
||||||
|
20BD: 19 RTS
|
||||||
|
20BE: 15 F6 DB F3 BTST.B #3, @H'F6DB ; refs ram_F6DB in on_chip_ram
|
||||||
|
20C2: 27 2C BEQ loc_20F0
|
||||||
|
20C4: 15 F7 31 04 03 CMP:G.B #H'03, @H'F731 ; refs ram_F731 in on_chip_ram
|
||||||
|
20C9: 22 25 BHI loc_20F0
|
||||||
|
20CB: 1D E1 10 16 TST.W @H'E110 ; refs mem_E110 in program_or_external
|
||||||
|
20CF: 27 05 BEQ loc_20D6
|
||||||
|
20D1: 1E 06 14 BSR loc_26E8
|
||||||
|
20D4: 20 1A BRA loc_20F0
|
||||||
|
|
||||||
|
loc_20D6:
|
||||||
|
20D6: 1D E1 34 80 MOV:G.W @H'E134, R0 ; refs mem_E134 in program_or_external
|
||||||
|
20DA: A8 FB BTST.W #11, R0
|
||||||
|
20DC: 27 04 BEQ loc_20E2
|
||||||
|
20DE: A8 DB BCLR.W #11, R0
|
||||||
7108
build/rom_0013_button_handler_linear.json
Normal file
7108
build/rom_0013_button_handler_linear.json
Normal file
File diff suppressed because it is too large
Load Diff
135
build/rom_0013_handler_linear.asm
Normal file
135
build/rom_0013_handler_linear.asm
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
; H8/536 ROM disassembly
|
||||||
|
; input: ROM\M27C512@DIP28_1.BIN
|
||||||
|
; bytes: 65536
|
||||||
|
; vector mode: min
|
||||||
|
; analysis: linear sweep
|
||||||
|
;
|
||||||
|
; Notes from the manual:
|
||||||
|
; - H8/536 uses the H8/500 CPU instruction set.
|
||||||
|
; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.
|
||||||
|
; - The register field is H'FE80-H'FFFF; names below come from appendix B.
|
||||||
|
; - @aa:8 short absolute operands use BR as the upper address byte.
|
||||||
|
; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.
|
||||||
|
; - LCD inference treats E-clock H'F200/H'F201 accesses as status/control and data candidates.
|
||||||
|
|
||||||
|
; Memory Map
|
||||||
|
; H'0000-H'009F exception_vectors vectors
|
||||||
|
; H'00A0-H'00FF dtc_vectors dtc_vectors
|
||||||
|
; H'0100-H'F67F program_or_external program
|
||||||
|
; H'F680-H'FE7F on_chip_ram ram
|
||||||
|
; H'FE80-H'FFFF register_field registers
|
||||||
|
|
||||||
|
; Vectors
|
||||||
|
; H'0000 reset -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0004 invalid_instruction -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0006 zero_divide -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0008 trap_vs -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0010 address_error -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0012 trace -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0016 nmi -> vec_nmi_4393 (H'4393)
|
||||||
|
; H'0020 trapa_0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0022 trapa_1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0024 trapa_2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0026 trapa_3 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0028 trapa_4 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002A trapa_5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002C trapa_6 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002E trapa_7 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0030 trapa_8 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0032 trapa_9 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0034 trapa_a -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0036 trapa_b -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0038 trapa_c -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003A trapa_d -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003C trapa_e -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003E trapa_f -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0040 irq0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0042 interval_timer -> vec_interval_timer_BFC4 (H'BFC4)
|
||||||
|
; H'0048 irq1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0050 irq2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0052 irq3 -> vec_irq3_3C30 (H'3C30)
|
||||||
|
; H'0058 irq4 -> vec_irq4_3AC7 (H'3AC7)
|
||||||
|
; H'005A irq5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0062 frt1_ocia -> vec_frt1_ocia_BEEA (H'BEEA)
|
||||||
|
; H'006A frt2_ocia -> vec_frt2_ocia_BF23 (H'BF23)
|
||||||
|
; H'0080 sci1_eri -> vec_sci1_eri_BB57 (H'BB57)
|
||||||
|
; H'0082 sci1_rxi -> vec_sci1_rxi_BB67 (H'BB67)
|
||||||
|
; H'0084 sci1_txi -> vec_sci1_txi_BA84 (H'BA84)
|
||||||
|
; H'0090 ad_adi -> vec_ad_adi_3D99 (H'3D99)
|
||||||
|
|
||||||
|
; Symbols
|
||||||
|
; mem_E02E H'E02E program_or_external memory r=1 w=0 width=word
|
||||||
|
; mem_E030 H'E030 program_or_external memory r=1 w=0 width=word
|
||||||
|
; mem_E826 H'E826 program_or_external memory r=2 w=0 width=word
|
||||||
|
; mem_E82E H'E82E program_or_external memory r=0 w=1 width=word
|
||||||
|
; ram_F711 H'F711 on_chip_ram ram r=4 w=4 width=byte
|
||||||
|
; ram_F713 H'F713 on_chip_ram ram r=2 w=2 width=byte
|
||||||
|
; ram_F716 H'F716 on_chip_ram ram r=4 w=4 width=byte
|
||||||
|
; ram_F791 H'F791 on_chip_ram ram r=4 w=4 width=byte
|
||||||
|
|
||||||
|
; Board Profile
|
||||||
|
; Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.
|
||||||
|
; H8 pin 66 P95/TXD (TXD) -> MAX202 pin 11
|
||||||
|
; H8 pin 67 P96/RXD (RXD) -> MAX202 pin 12
|
||||||
|
; SCI2 pin routing is disabled by SYSCR2.P9SCI2E=0 in the observed setup.
|
||||||
|
|
||||||
|
; LCD/Text Scan
|
||||||
|
; search 'CONNECT': not literal, hits=0
|
||||||
|
|
||||||
|
2E00: 30 FE A3 BRA loc_2CA6
|
||||||
|
2E03: 30 FE A0 BRA loc_2CA6
|
||||||
|
2E06: 1D E8 26 FF BTST.W #15, @H'E826 ; refs mem_E826 in program_or_external
|
||||||
|
2E0A: 27 0A BEQ loc_2E16
|
||||||
|
2E0C: 15 F7 91 C6 BSET.B #6, @H'F791 ; refs ram_F791 in on_chip_ram
|
||||||
|
2E10: 15 F7 13 C4 BSET.B #4, @H'F713 ; refs ram_F713 in on_chip_ram
|
||||||
|
2E14: 20 08 BRA loc_2E1E
|
||||||
|
|
||||||
|
loc_2E16:
|
||||||
|
2E16: 15 F7 91 D6 BCLR.B #6, @H'F791 ; refs ram_F791 in on_chip_ram
|
||||||
|
2E1A: 15 F7 13 D4 BCLR.B #4, @H'F713 ; refs ram_F713 in on_chip_ram
|
||||||
|
|
||||||
|
loc_2E1E:
|
||||||
|
2E1E: 1D E8 26 FE BTST.W #14, @H'E826 ; refs mem_E826 in program_or_external
|
||||||
|
2E22: 27 0A BEQ loc_2E2E
|
||||||
|
2E24: 15 F7 91 C5 BSET.B #5, @H'F791 ; refs ram_F791 in on_chip_ram
|
||||||
|
2E28: 15 F7 16 C7 BSET.B #7, @H'F716 ; refs ram_F716 in on_chip_ram
|
||||||
|
2E2C: 20 08 BRA loc_2E36
|
||||||
|
|
||||||
|
loc_2E2E:
|
||||||
|
2E2E: 15 F7 91 D5 BCLR.B #5, @H'F791 ; refs ram_F791 in on_chip_ram
|
||||||
|
2E32: 15 F7 16 D7 BCLR.B #7, @H'F716 ; refs ram_F716 in on_chip_ram
|
||||||
|
|
||||||
|
loc_2E36:
|
||||||
|
2E36: 30 FE 6D BRA loc_2CA6
|
||||||
|
2E39: FC E0 00 81 MOV:G.W @(-H'2000,R4), R1
|
||||||
|
2E3D: A9 FF BTST.W #15, R1
|
||||||
|
2E3F: 26 0E BNE loc_2E4F
|
||||||
|
2E41: A9 FE BTST.W #14, R1
|
||||||
|
2E43: 26 0A BNE loc_2E4F
|
||||||
|
2E45: 15 F7 11 D0 BCLR.B #0, @H'F711 ; refs ram_F711 in on_chip_ram
|
||||||
|
2E49: 15 F7 16 D5 BCLR.B #5, @H'F716 ; refs ram_F716 in on_chip_ram
|
||||||
|
2E4D: 20 08 BRA loc_2E57
|
||||||
|
|
||||||
|
loc_2E4F:
|
||||||
|
2E4F: 15 F7 11 C0 BSET.B #0, @H'F711 ; refs ram_F711 in on_chip_ram
|
||||||
|
2E53: 15 F7 16 C5 BSET.B #5, @H'F716 ; refs ram_F716 in on_chip_ram
|
||||||
|
|
||||||
|
loc_2E57:
|
||||||
|
2E57: 30 FE 4C BRA loc_2CA6
|
||||||
|
2E5A: FC E0 00 81 MOV:G.W @(-H'2000,R4), R1
|
||||||
|
2E5E: A9 FF BTST.W #15, R1
|
||||||
|
2E60: 26 06 BNE loc_2E68
|
||||||
|
2E62: 15 F7 11 D1 BCLR.B #1, @H'F711 ; refs ram_F711 in on_chip_ram
|
||||||
|
2E66: 20 04 BRA loc_2E6C
|
||||||
|
|
||||||
|
loc_2E68:
|
||||||
|
2E68: 15 F7 11 C1 BSET.B #1, @H'F711 ; refs ram_F711 in on_chip_ram
|
||||||
|
|
||||||
|
loc_2E6C:
|
||||||
|
2E6C: 30 FE 37 BRA loc_2CA6
|
||||||
|
2E6F: 1D E0 30 81 MOV:G.W @H'E030, R1 ; refs mem_E030 in program_or_external
|
||||||
|
2E73: 1D E0 2E 71 CMP:G.W @H'E02E, R1 ; refs mem_E02E in program_or_external
|
||||||
|
2E77: 27 0C BEQ loc_2E85
|
||||||
|
2E79: 1D E8 2E 91 MOV:G.W R1, @H'E82E ; refs mem_E82E in program_or_external
|
||||||
|
2E7D: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80
|
||||||
|
2E7F: 5B 00 17 MOV:I.W #H'0017, R3 ; dataflow R3=H'0017
|
||||||
2590
build/rom_0013_handler_linear.json
Normal file
2590
build/rom_0013_handler_linear.json
Normal file
File diff suppressed because it is too large
Load Diff
38
build/rom_0013_table_xrefs.txt
Normal file
38
build/rom_0013_table_xrefs.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
Table/Index Cross-Reference Report for build\rom_0013_handler_linear.json
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
Static offsets are emitted only when an index register value can be derived from nearby immediate loads in the current JSON. Other indexed accesses are dynamic.
|
||||||
|
|
||||||
|
LCD correlation hints
|
||||||
|
term 'CONNECT': no LCD/text candidate hits in current decompile
|
||||||
|
term 'CONNECT: OK': no LCD/text candidate hits in current decompile
|
||||||
|
term 'CONNECT: NOT ACT': no LCD/text candidate hits in current decompile
|
||||||
|
term 'NOT ACT': no LCD/text candidate hits in current decompile
|
||||||
|
term 'COMM LINK': no LCD/text candidate hits in current decompile
|
||||||
|
term 'COMPLETED': no LCD/text candidate hits in current decompile
|
||||||
|
caveat: LCD strings can be builder/script output; absence of a literal term does not disprove runtime composition.
|
||||||
|
|
||||||
|
primary_value_table_candidate H'E000-H'E3FF (negative H'2000; direct H'F900-H'F91F)
|
||||||
|
accesses=4 reads=4 writes=0 dynamic=2
|
||||||
|
static offsets: H'002E, H'0030
|
||||||
|
functions: <no function>:4
|
||||||
|
- H'2E39 read index dynamic via R4 operand @(-H'2000,R4); <no function>; MOV:G.W @(-H'2000,R4), R1
|
||||||
|
- H'2E5A read index dynamic via R4 operand @(-H'2000,R4); <no function>; MOV:G.W @(-H'2000,R4), R1
|
||||||
|
- H'2E6F read offset H'0030 selector 0x018 -> H'E030; <no function>; MOV:G.W @H'E030, R1
|
||||||
|
- H'2E73 read offset H'002E selector 0x017 -> H'E02E; <no function>; CMP:G.W @H'E02E, R1
|
||||||
|
|
||||||
|
secondary_value_table_candidate H'E400-H'E7FF (negative H'1C00; direct H'F940-H'F95F)
|
||||||
|
accesses=0 reads=0 writes=0 dynamic=0
|
||||||
|
no references found in current JSON
|
||||||
|
|
||||||
|
current_value_table_candidate H'E800-H'EBFF (negative H'1800; direct H'F920-H'F93F)
|
||||||
|
accesses=3 reads=2 writes=1 dynamic=0
|
||||||
|
static offsets: H'0026, H'002E
|
||||||
|
functions: <no function>:3
|
||||||
|
- H'2E06 read offset H'0026 selector 0x013 -> H'E826; <no function>; BTST.W #15, @H'E826
|
||||||
|
- H'2E1E read offset H'0026 selector 0x013 -> H'E826; <no function>; BTST.W #14, @H'E826
|
||||||
|
- H'2E79 write offset H'002E selector 0x017 -> H'E82E; <no function>; MOV:G.W R1, @H'E82E
|
||||||
|
|
||||||
|
flag_table_candidate H'EC00-H'EFFF (negative H'1400; direct H'F980-H'F99F)
|
||||||
|
accesses=0 reads=0 writes=0 dynamic=0
|
||||||
|
no references found in current JSON
|
||||||
@@ -793,6 +793,108 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"selector_candidates": [
|
"selector_candidates": [
|
||||||
|
{
|
||||||
|
"accesses": [
|
||||||
|
{
|
||||||
|
"access": "read",
|
||||||
|
"address_hex": "H'17D0",
|
||||||
|
"function": "loc_17C9",
|
||||||
|
"instruction": "BTST.W #12, @H'E126",
|
||||||
|
"table": "primary_value_table_candidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"access": "read",
|
||||||
|
"address_hex": "H'1802",
|
||||||
|
"function": "loc_17FB",
|
||||||
|
"instruction": "BTST.W #12, @H'E126",
|
||||||
|
"table": "primary_value_table_candidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"access": "read",
|
||||||
|
"address_hex": "H'183A",
|
||||||
|
"function": "loc_182D",
|
||||||
|
"instruction": "BTST.W #5, @H'E126",
|
||||||
|
"table": "primary_value_table_candidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"access": "read",
|
||||||
|
"address_hex": "H'189E",
|
||||||
|
"function": "loc_1891",
|
||||||
|
"instruction": "BTST.W #5, @H'E126",
|
||||||
|
"table": "primary_value_table_candidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"access": "read",
|
||||||
|
"address_hex": "H'18F4",
|
||||||
|
"function": "loc_18E7",
|
||||||
|
"instruction": "BTST.W #5, @H'E126",
|
||||||
|
"table": "primary_value_table_candidate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cmd1_read_frame": "01 01 13 00 00 49",
|
||||||
|
"name": "white_balance_black_flare_mode_lane",
|
||||||
|
"reasons": [
|
||||||
|
"primary_value_table_candidate read in loc_17C9: BTST.W #12, @H'E126",
|
||||||
|
"primary_value_table_candidate read in loc_17FB: BTST.W #12, @H'E126",
|
||||||
|
"primary_value_table_candidate read in loc_182D: BTST.W #5, @H'E126",
|
||||||
|
"primary_value_table_candidate read in loc_1891: BTST.W #5, @H'E126",
|
||||||
|
"primary_value_table_candidate read in loc_18E7: BTST.W #5, @H'E126",
|
||||||
|
"Bench-visible white-balance and black/flare lamp lane."
|
||||||
|
],
|
||||||
|
"score": 19,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 80 00 C8",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 40 00 08",
|
||||||
|
"value": 16384,
|
||||||
|
"value_hex": "0x4000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 20 00 68",
|
||||||
|
"value": 8192,
|
||||||
|
"value_hex": "0x2000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 10 20 78",
|
||||||
|
"value": 4128,
|
||||||
|
"value_hex": "0x1020"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 40 40 48",
|
||||||
|
"value": 16448,
|
||||||
|
"value_hex": "0x4040"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 80 40 88",
|
||||||
|
"value": 32832,
|
||||||
|
"value_hex": "0x8040"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 00 20 68",
|
||||||
|
"value": 32,
|
||||||
|
"value_hex": "0x0020"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 00 40 08",
|
||||||
|
"value": 64,
|
||||||
|
"value_hex": "0x0040"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 13 00 00 48",
|
||||||
|
"value": 0,
|
||||||
|
"value_hex": "0x0000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 147,
|
||||||
|
"selector_hex": "0x093",
|
||||||
|
"tables": [
|
||||||
|
"primary_value_table_candidate"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"accesses": [
|
"accesses": [
|
||||||
{
|
{
|
||||||
@@ -859,61 +961,6 @@
|
|||||||
"flag_table_candidate"
|
"flag_table_candidate"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"accesses": [
|
|
||||||
{
|
|
||||||
"access": "read",
|
|
||||||
"address_hex": "H'17D0",
|
|
||||||
"function": "loc_17C9",
|
|
||||||
"instruction": "BTST.W #12, @H'E126",
|
|
||||||
"table": "primary_value_table_candidate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"access": "read",
|
|
||||||
"address_hex": "H'1802",
|
|
||||||
"function": "loc_17FB",
|
|
||||||
"instruction": "BTST.W #12, @H'E126",
|
|
||||||
"table": "primary_value_table_candidate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"access": "read",
|
|
||||||
"address_hex": "H'183A",
|
|
||||||
"function": "loc_182D",
|
|
||||||
"instruction": "BTST.W #5, @H'E126",
|
|
||||||
"table": "primary_value_table_candidate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"access": "read",
|
|
||||||
"address_hex": "H'189E",
|
|
||||||
"function": "loc_1891",
|
|
||||||
"instruction": "BTST.W #5, @H'E126",
|
|
||||||
"table": "primary_value_table_candidate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"access": "read",
|
|
||||||
"address_hex": "H'18F4",
|
|
||||||
"function": "loc_18E7",
|
|
||||||
"instruction": "BTST.W #5, @H'E126",
|
|
||||||
"table": "primary_value_table_candidate"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cmd1_read_frame": "01 01 13 00 00 49",
|
|
||||||
"name": "state_selector_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"primary_value_table_candidate read in loc_17C9: BTST.W #12, @H'E126",
|
|
||||||
"primary_value_table_candidate read in loc_17FB: BTST.W #12, @H'E126",
|
|
||||||
"primary_value_table_candidate read in loc_182D: BTST.W #5, @H'E126",
|
|
||||||
"primary_value_table_candidate read in loc_1891: BTST.W #5, @H'E126",
|
|
||||||
"primary_value_table_candidate read in loc_18E7: BTST.W #5, @H'E126"
|
|
||||||
],
|
|
||||||
"score": 15,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 147,
|
|
||||||
"selector_hex": "0x093",
|
|
||||||
"tables": [
|
|
||||||
"primary_value_table_candidate"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"accesses": [
|
"accesses": [
|
||||||
{
|
{
|
||||||
@@ -1044,6 +1091,75 @@
|
|||||||
"current_value_table_candidate"
|
"current_value_table_candidate"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 00 6B 00 00 30",
|
||||||
|
"dispatch_target": {
|
||||||
|
"decoded_code": false,
|
||||||
|
"dispatch_index": 107,
|
||||||
|
"dispatch_index_hex": "0x06B",
|
||||||
|
"entry_address_hex": "H'297C",
|
||||||
|
"selector": 107,
|
||||||
|
"selector_hex": "0x06B",
|
||||||
|
"target": 12146,
|
||||||
|
"target_hex": "H'2F72",
|
||||||
|
"target_label_or_hex": "H'2F72"
|
||||||
|
},
|
||||||
|
"name": "standard_lamp_lane",
|
||||||
|
"reasons": [
|
||||||
|
"when F731.7 is set, command 5 on this selector clears F731.7/F790.7",
|
||||||
|
"Bench-visible STANDARD lamp lane found from ROM-derived F6D4.6 handler candidate.",
|
||||||
|
"selector dispatches to H'2F72"
|
||||||
|
],
|
||||||
|
"score": 11,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 6B 80 00 B1",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 107,
|
||||||
|
"selector_hex": "0x06B",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 00 15 00 00 4E",
|
||||||
|
"dispatch_target": {
|
||||||
|
"decoded_code": false,
|
||||||
|
"dispatch_index": 21,
|
||||||
|
"dispatch_index_hex": "0x015",
|
||||||
|
"entry_address_hex": "H'28D0",
|
||||||
|
"selector": 21,
|
||||||
|
"selector_hex": "0x015",
|
||||||
|
"target": 11833,
|
||||||
|
"target_hex": "H'2E39",
|
||||||
|
"target_label_or_hex": "H'2E39"
|
||||||
|
},
|
||||||
|
"name": "call_and_red_tally_lamp_lane",
|
||||||
|
"reasons": [
|
||||||
|
"observed RCP autonomous report frame(s): 00 00 15 80 00 CF, 00 00 15 00 00 4F",
|
||||||
|
"Bench-visible CALL lamp and red tally lane; local CALL handler mirrors F6DB.5 into E800[0x0015].15.",
|
||||||
|
"selector dispatches to H'2E39"
|
||||||
|
],
|
||||||
|
"score": 9,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 15 80 00 CF",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 15 00 00 4F",
|
||||||
|
"value": 0,
|
||||||
|
"value_hex": "0x0000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 21,
|
||||||
|
"selector_hex": "0x015",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"accesses": [
|
"accesses": [
|
||||||
{
|
{
|
||||||
@@ -1124,31 +1240,6 @@
|
|||||||
"current_value_table_candidate"
|
"current_value_table_candidate"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"accesses": [],
|
|
||||||
"cmd1_read_frame": "01 00 6B 00 00 30",
|
|
||||||
"dispatch_target": {
|
|
||||||
"decoded_code": false,
|
|
||||||
"dispatch_index": 107,
|
|
||||||
"dispatch_index_hex": "0x06B",
|
|
||||||
"entry_address_hex": "H'297C",
|
|
||||||
"selector": 107,
|
|
||||||
"selector_hex": "0x06B",
|
|
||||||
"target": 12146,
|
|
||||||
"target_hex": "H'2F72",
|
|
||||||
"target_label_or_hex": "H'2F72"
|
|
||||||
},
|
|
||||||
"name": "connection_latch_clear_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"when F731.7 is set, command 5 on this selector clears F731.7/F790.7",
|
|
||||||
"selector dispatches to H'2F72"
|
|
||||||
],
|
|
||||||
"score": 7,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 107,
|
|
||||||
"selector_hex": "0x06B",
|
|
||||||
"tables": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"accesses": [],
|
"accesses": [],
|
||||||
"cmd1_read_frame": "01 00 6C 00 00 37",
|
"cmd1_read_frame": "01 00 6C 00 00 37",
|
||||||
@@ -1199,6 +1290,202 @@
|
|||||||
"selector_hex": "0x06D",
|
"selector_hex": "0x06D",
|
||||||
"tables": []
|
"tables": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"accesses": [
|
||||||
|
{
|
||||||
|
"access": "read",
|
||||||
|
"address_hex": "H'17A7",
|
||||||
|
"function": "loc_1795",
|
||||||
|
"instruction": "BTST.W #15, @H'E220",
|
||||||
|
"table": "primary_value_table_candidate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cmd1_read_frame": "01 01 90 00 00 CA",
|
||||||
|
"name": "knee_auto_lamp_or_page_status_lane",
|
||||||
|
"reasons": [
|
||||||
|
"primary_value_table_candidate read in loc_1795: BTST.W #15, @H'E220",
|
||||||
|
"Bench-visible KNEE AUTO source; ROM notes indicate timed KNEE/detail page interaction."
|
||||||
|
],
|
||||||
|
"score": 7,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 90 80 00 4B",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 272,
|
||||||
|
"selector_hex": "0x110",
|
||||||
|
"tables": [
|
||||||
|
"primary_value_table_candidate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 00 13 00 00 48",
|
||||||
|
"dispatch_target": {
|
||||||
|
"decoded_code": false,
|
||||||
|
"dispatch_index": 19,
|
||||||
|
"dispatch_index_hex": "0x013",
|
||||||
|
"entry_address_hex": "H'28CC",
|
||||||
|
"selector": 19,
|
||||||
|
"selector_hex": "0x013",
|
||||||
|
"target": 11782,
|
||||||
|
"target_hex": "H'2E06",
|
||||||
|
"target_label_or_hex": "H'2E06"
|
||||||
|
},
|
||||||
|
"name": "slave_and_iris_mblack_link_lamps",
|
||||||
|
"reasons": [
|
||||||
|
"Selector 0x0013 is a two-bit lamp/status word. ROM dispatch H'2E06 reads current table word H'E826 and fans bit 15 and bit 14 into panel latch RAM.",
|
||||||
|
"0x8000 SLAVE lamp: sets F791.6 and F713.4",
|
||||||
|
"0x4000 IRIS/M.BLACK LINK lamp: sets F791.5 and F716.7",
|
||||||
|
"selector dispatches to H'2E06"
|
||||||
|
],
|
||||||
|
"score": 6,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 13 80 00 C9",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 13 40 00 09",
|
||||||
|
"value": 16384,
|
||||||
|
"value_hex": "0x4000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 13 00 00 49",
|
||||||
|
"value": 0,
|
||||||
|
"value_hex": "0x0000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 19,
|
||||||
|
"selector_hex": "0x013",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 00 17 00 00 4C",
|
||||||
|
"dispatch_target": {
|
||||||
|
"decoded_code": false,
|
||||||
|
"dispatch_index": 23,
|
||||||
|
"dispatch_index_hex": "0x017",
|
||||||
|
"entry_address_hex": "H'28D4",
|
||||||
|
"selector": 23,
|
||||||
|
"selector_hex": "0x017",
|
||||||
|
"target": 11909,
|
||||||
|
"target_hex": "H'2E85",
|
||||||
|
"target_label_or_hex": "H'2E85"
|
||||||
|
},
|
||||||
|
"name": "bars_lamp_lane",
|
||||||
|
"reasons": [
|
||||||
|
"Bench-visible BARS lamp/latch lane; low writes do not reliably clear the visible latch.",
|
||||||
|
"selector dispatches to H'2E85"
|
||||||
|
],
|
||||||
|
"score": 6,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 17 80 00 CD",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 17 40 00 0D",
|
||||||
|
"value": 16384,
|
||||||
|
"value_hex": "0x4000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 17 00 00 4D",
|
||||||
|
"value": 0,
|
||||||
|
"value_hex": "0x0000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 23,
|
||||||
|
"selector_hex": "0x017",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 00 1A 00 00 41",
|
||||||
|
"dispatch_target": {
|
||||||
|
"decoded_code": false,
|
||||||
|
"dispatch_index": 26,
|
||||||
|
"dispatch_index_hex": "0x01A",
|
||||||
|
"entry_address_hex": "H'28DA",
|
||||||
|
"selector": 26,
|
||||||
|
"selector_hex": "0x01A",
|
||||||
|
"target": 11972,
|
||||||
|
"target_hex": "H'2EC4",
|
||||||
|
"target_label_or_hex": "H'2EC4"
|
||||||
|
},
|
||||||
|
"name": "monitor_selector_lamps",
|
||||||
|
"reasons": [
|
||||||
|
"Bench-visible MONITOR selector cluster found from ROM-derived button-output sweep.",
|
||||||
|
"selector dispatches to H'2EC4"
|
||||||
|
],
|
||||||
|
"score": 6,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 1A 08 08 40",
|
||||||
|
"value": 2056,
|
||||||
|
"value_hex": "0x0808"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 1A 20 20 40",
|
||||||
|
"value": 8224,
|
||||||
|
"value_hex": "0x2020"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 1A 40 40 40",
|
||||||
|
"value": 16448,
|
||||||
|
"value_hex": "0x4040"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 1A 80 80 40",
|
||||||
|
"value": 32896,
|
||||||
|
"value_hex": "0x8080"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 26,
|
||||||
|
"selector_hex": "0x01A",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 00 24 00 00 7F",
|
||||||
|
"dispatch_target": {
|
||||||
|
"decoded_code": false,
|
||||||
|
"dispatch_index": 36,
|
||||||
|
"dispatch_index_hex": "0x024",
|
||||||
|
"entry_address_hex": "H'28EE",
|
||||||
|
"selector": 36,
|
||||||
|
"selector_hex": "0x024",
|
||||||
|
"target": 12044,
|
||||||
|
"target_hex": "H'2F0C",
|
||||||
|
"target_label_or_hex": "H'2F0C"
|
||||||
|
},
|
||||||
|
"name": "lcd_selector_button_lamp",
|
||||||
|
"reasons": [
|
||||||
|
"Bench-visible LCD selector-button lamp lane.",
|
||||||
|
"selector dispatches to H'2F0C"
|
||||||
|
],
|
||||||
|
"score": 6,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 24 80 00 FE",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 00 24 00 00 7E",
|
||||||
|
"value": 0,
|
||||||
|
"value_hex": "0x0000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 36,
|
||||||
|
"selector_hex": "0x024",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"accesses": [],
|
"accesses": [],
|
||||||
"cmd1_read_frame": "01 00 07 00 00 5C",
|
"cmd1_read_frame": "01 00 07 00 00 5C",
|
||||||
@@ -1224,31 +1511,6 @@
|
|||||||
"selector_hex": "0x007",
|
"selector_hex": "0x007",
|
||||||
"tables": []
|
"tables": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"accesses": [],
|
|
||||||
"cmd1_read_frame": "01 00 15 00 00 4E",
|
|
||||||
"dispatch_target": {
|
|
||||||
"decoded_code": false,
|
|
||||||
"dispatch_index": 21,
|
|
||||||
"dispatch_index_hex": "0x015",
|
|
||||||
"entry_address_hex": "H'28D0",
|
|
||||||
"selector": 21,
|
|
||||||
"selector_hex": "0x015",
|
|
||||||
"target": 11833,
|
|
||||||
"target_hex": "H'2E39",
|
|
||||||
"target_label_or_hex": "H'2E39"
|
|
||||||
},
|
|
||||||
"name": "call_button_report_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"observed RCP autonomous report frame(s): 00 00 15 80 00 CF, 00 00 15 00 00 4F",
|
|
||||||
"selector dispatches to H'2E39"
|
|
||||||
],
|
|
||||||
"score": 5,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 21,
|
|
||||||
"selector_hex": "0x015",
|
|
||||||
"tables": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"accesses": [
|
"accesses": [
|
||||||
{
|
{
|
||||||
@@ -1349,6 +1611,108 @@
|
|||||||
"selector_hex": "0x0F8",
|
"selector_hex": "0x0F8",
|
||||||
"tables": []
|
"tables": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 01 02 00 00 58",
|
||||||
|
"name": "iris_readout_lane",
|
||||||
|
"reasons": [
|
||||||
|
"Bench-visible IRIS seven-segment/display lane."
|
||||||
|
],
|
||||||
|
"score": 4,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 02 80 00 D9",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 02 40 00 19",
|
||||||
|
"value": 16384,
|
||||||
|
"value_hex": "0x4000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 02 00 00 59",
|
||||||
|
"value": 0,
|
||||||
|
"value_hex": "0x0000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 130,
|
||||||
|
"selector_hex": "0x082",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 01 03 00 00 59",
|
||||||
|
"name": "combined_iris_shutter_master_gain_status_lane",
|
||||||
|
"reasons": [
|
||||||
|
"Bench-visible combined status/readout lane; clear behavior appears latched or copied elsewhere."
|
||||||
|
],
|
||||||
|
"score": 4,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 03 80 00 D8",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 03 40 00 18",
|
||||||
|
"value": 16384,
|
||||||
|
"value_hex": "0x4000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 03 20 00 78",
|
||||||
|
"value": 8192,
|
||||||
|
"value_hex": "0x2000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 03 00 04 5C",
|
||||||
|
"value": 4,
|
||||||
|
"value_hex": "0x0004"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 03 00 00 58",
|
||||||
|
"value": 0,
|
||||||
|
"value_hex": "0x0000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 131,
|
||||||
|
"selector_hex": "0x083",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accesses": [],
|
||||||
|
"cmd1_read_frame": "01 01 0F 00 00 55",
|
||||||
|
"name": "shutter_display_status_lane",
|
||||||
|
"reasons": [
|
||||||
|
"Bench-visible shutter/status display lane; local F6D0.6/F6D0.7 handlers also queue this selector."
|
||||||
|
],
|
||||||
|
"score": 4,
|
||||||
|
"seed_frames": [
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 0F 80 00 D4",
|
||||||
|
"value": 32768,
|
||||||
|
"value_hex": "0x8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 0F 20 00 74",
|
||||||
|
"value": 8192,
|
||||||
|
"value_hex": "0x2000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 0F 08 00 5C",
|
||||||
|
"value": 2048,
|
||||||
|
"value_hex": "0x0800"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmd0_frame": "00 01 0F 10 00 44",
|
||||||
|
"value": 4096,
|
||||||
|
"value_hex": "0x1000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selector": 143,
|
||||||
|
"selector_hex": "0x08F",
|
||||||
|
"tables": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"accesses": [
|
"accesses": [
|
||||||
{
|
{
|
||||||
@@ -1441,29 +1805,6 @@
|
|||||||
"primary_value_table_candidate"
|
"primary_value_table_candidate"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"accesses": [
|
|
||||||
{
|
|
||||||
"access": "read",
|
|
||||||
"address_hex": "H'17A7",
|
|
||||||
"function": "loc_1795",
|
|
||||||
"instruction": "BTST.W #15, @H'E220",
|
|
||||||
"table": "primary_value_table_candidate"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cmd1_read_frame": "01 01 90 00 00 CA",
|
|
||||||
"name": "state_selector_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"primary_value_table_candidate read in loc_1795: BTST.W #15, @H'E220"
|
|
||||||
],
|
|
||||||
"score": 3,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 272,
|
|
||||||
"selector_hex": "0x110",
|
|
||||||
"tables": [
|
|
||||||
"primary_value_table_candidate"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"accesses": [],
|
"accesses": [],
|
||||||
"cmd1_read_frame": "01 00 12 00 00 49",
|
"cmd1_read_frame": "01 00 12 00 00 49",
|
||||||
@@ -1488,30 +1829,6 @@
|
|||||||
"selector_hex": "0x012",
|
"selector_hex": "0x012",
|
||||||
"tables": []
|
"tables": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"accesses": [],
|
|
||||||
"cmd1_read_frame": "01 00 13 00 00 48",
|
|
||||||
"dispatch_target": {
|
|
||||||
"decoded_code": false,
|
|
||||||
"dispatch_index": 19,
|
|
||||||
"dispatch_index_hex": "0x013",
|
|
||||||
"entry_address_hex": "H'28CC",
|
|
||||||
"selector": 19,
|
|
||||||
"selector_hex": "0x013",
|
|
||||||
"target": 11782,
|
|
||||||
"target_hex": "H'2E06",
|
|
||||||
"target_label_or_hex": "H'2E06"
|
|
||||||
},
|
|
||||||
"name": "state_selector_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"selector dispatches to H'2E06"
|
|
||||||
],
|
|
||||||
"score": 2,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 19,
|
|
||||||
"selector_hex": "0x013",
|
|
||||||
"tables": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"accesses": [],
|
"accesses": [],
|
||||||
"cmd1_read_frame": "01 00 16 00 00 4D",
|
"cmd1_read_frame": "01 00 16 00 00 4D",
|
||||||
@@ -1536,30 +1853,6 @@
|
|||||||
"selector_hex": "0x016",
|
"selector_hex": "0x016",
|
||||||
"tables": []
|
"tables": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"accesses": [],
|
|
||||||
"cmd1_read_frame": "01 00 17 00 00 4C",
|
|
||||||
"dispatch_target": {
|
|
||||||
"decoded_code": false,
|
|
||||||
"dispatch_index": 23,
|
|
||||||
"dispatch_index_hex": "0x017",
|
|
||||||
"entry_address_hex": "H'28D4",
|
|
||||||
"selector": 23,
|
|
||||||
"selector_hex": "0x017",
|
|
||||||
"target": 11909,
|
|
||||||
"target_hex": "H'2E85",
|
|
||||||
"target_label_or_hex": "H'2E85"
|
|
||||||
},
|
|
||||||
"name": "state_selector_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"selector dispatches to H'2E85"
|
|
||||||
],
|
|
||||||
"score": 2,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 23,
|
|
||||||
"selector_hex": "0x017",
|
|
||||||
"tables": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"accesses": [],
|
"accesses": [],
|
||||||
"cmd1_read_frame": "01 00 18 00 00 43",
|
"cmd1_read_frame": "01 00 18 00 00 43",
|
||||||
@@ -1584,54 +1877,6 @@
|
|||||||
"selector_hex": "0x018",
|
"selector_hex": "0x018",
|
||||||
"tables": []
|
"tables": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"accesses": [],
|
|
||||||
"cmd1_read_frame": "01 00 1A 00 00 41",
|
|
||||||
"dispatch_target": {
|
|
||||||
"decoded_code": false,
|
|
||||||
"dispatch_index": 26,
|
|
||||||
"dispatch_index_hex": "0x01A",
|
|
||||||
"entry_address_hex": "H'28DA",
|
|
||||||
"selector": 26,
|
|
||||||
"selector_hex": "0x01A",
|
|
||||||
"target": 11972,
|
|
||||||
"target_hex": "H'2EC4",
|
|
||||||
"target_label_or_hex": "H'2EC4"
|
|
||||||
},
|
|
||||||
"name": "state_selector_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"selector dispatches to H'2EC4"
|
|
||||||
],
|
|
||||||
"score": 2,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 26,
|
|
||||||
"selector_hex": "0x01A",
|
|
||||||
"tables": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accesses": [],
|
|
||||||
"cmd1_read_frame": "01 00 24 00 00 7F",
|
|
||||||
"dispatch_target": {
|
|
||||||
"decoded_code": false,
|
|
||||||
"dispatch_index": 36,
|
|
||||||
"dispatch_index_hex": "0x024",
|
|
||||||
"entry_address_hex": "H'28EE",
|
|
||||||
"selector": 36,
|
|
||||||
"selector_hex": "0x024",
|
|
||||||
"target": 12044,
|
|
||||||
"target_hex": "H'2F0C",
|
|
||||||
"target_label_or_hex": "H'2F0C"
|
|
||||||
},
|
|
||||||
"name": "state_selector_candidate",
|
|
||||||
"reasons": [
|
|
||||||
"selector dispatches to H'2F0C"
|
|
||||||
],
|
|
||||||
"score": 2,
|
|
||||||
"seed_frames": [],
|
|
||||||
"selector": 36,
|
|
||||||
"selector_hex": "0x024",
|
|
||||||
"tables": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"accesses": [],
|
"accesses": [],
|
||||||
"cmd1_read_frame": "01 00 25 00 00 7E",
|
"cmd1_read_frame": "01 00 25 00 00 7E",
|
||||||
@@ -1898,7 +2143,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": {
|
"summary": {
|
||||||
"candidate_count": 41,
|
"candidate_count": 44,
|
||||||
"confidence": "medium",
|
"confidence": "medium",
|
||||||
"core_model": "The RCP likely waits for the CCU to seed mirrored state tables, then uses those selector values to update LCD text, panel lamps, and report state changes."
|
"core_model": "The RCP likely waits for the CCU to seed mirrored state tables, then uses those selector values to update LCD text, panel lamps, and report state changes."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ Table Model:
|
|||||||
- flag_table_candidate: H'EC00-H'EFFF; accesses=6 static selectors=0x000
|
- flag_table_candidate: H'EC00-H'EFFF; accesses=6 static selectors=0x000
|
||||||
|
|
||||||
Highest-Value Selector Candidates:
|
Highest-Value Selector Candidates:
|
||||||
|
- 0x093 white_balance_black_flare_mode_lane: score=19 tables=primary_value_table_candidate
|
||||||
|
- primary_value_table_candidate read in loc_17C9: BTST.W #12, @H'E126
|
||||||
|
- primary_value_table_candidate read in loc_17FB: BTST.W #12, @H'E126
|
||||||
|
- primary_value_table_candidate read in loc_182D: BTST.W #5, @H'E126
|
||||||
|
- primary_value_table_candidate read in loc_1891: BTST.W #5, @H'E126
|
||||||
|
seed frames: 0x8000 -> 00 01 13 80 00 C8; 0x4000 -> 00 01 13 40 00 08; 0x2000 -> 00 01 13 20 00 68
|
||||||
|
readback frame: 01 01 13 00 00 49
|
||||||
- 0x000 heartbeat_or_idle_report_candidate: score=18 tables=primary_value_table_candidate, current_value_table_candidate, flag_table_candidate
|
- 0x000 heartbeat_or_idle_report_candidate: score=18 tables=primary_value_table_candidate, current_value_table_candidate, flag_table_candidate
|
||||||
- primary_value_table_candidate write in loc_4096: MOV:G.W #H'0080, @H'E000
|
- primary_value_table_candidate write in loc_4096: MOV:G.W #H'0080, @H'E000
|
||||||
- current_value_table_candidate write in loc_4096: MOV:G.W #H'0080, @H'E800
|
- current_value_table_candidate write in loc_4096: MOV:G.W #H'0080, @H'E800
|
||||||
@@ -17,12 +24,6 @@ Highest-Value Selector Candidates:
|
|||||||
- idle report selector and CONNECT OK emulator condition both center on selector zero
|
- idle report selector and CONNECT OK emulator condition both center on selector zero
|
||||||
seed frames: 0x0080 -> 00 00 00 00 80 DA; 0x8080 -> 00 00 00 80 80 5A
|
seed frames: 0x0080 -> 00 00 00 00 80 DA; 0x8080 -> 00 00 00 80 80 5A
|
||||||
readback frame: 01 00 00 00 00 5B
|
readback frame: 01 00 00 00 00 5B
|
||||||
- 0x093 state_selector_candidate: score=15 tables=primary_value_table_candidate
|
|
||||||
- primary_value_table_candidate read in loc_17C9: BTST.W #12, @H'E126
|
|
||||||
- primary_value_table_candidate read in loc_17FB: BTST.W #12, @H'E126
|
|
||||||
- primary_value_table_candidate read in loc_182D: BTST.W #5, @H'E126
|
|
||||||
- primary_value_table_candidate read in loc_1891: BTST.W #5, @H'E126
|
|
||||||
readback frame: 01 01 13 00 00 49
|
|
||||||
- 0x0F6 active_status_bridge_candidate: score=14 tables=primary_value_table_candidate, current_value_table_candidate
|
- 0x0F6 active_status_bridge_candidate: score=14 tables=primary_value_table_candidate, current_value_table_candidate
|
||||||
- primary_value_table_candidate read in loc_48FA: BTST.W #13, @H'E1EC
|
- primary_value_table_candidate read in loc_48FA: BTST.W #13, @H'E1EC
|
||||||
- primary_value_table_candidate read in loc_48FA: MOV:G.W @H'E1EC, R0
|
- primary_value_table_candidate read in loc_48FA: MOV:G.W @H'E1EC, R0
|
||||||
@@ -42,6 +43,18 @@ Highest-Value Selector Candidates:
|
|||||||
- ROM default table writes E000/E800 selector 0x040 to 0xFFFF and bench tests repeatedly touched the 0x40 family
|
- ROM default table writes E000/E800 selector 0x040 to 0xFFFF and bench tests repeatedly touched the 0x40 family
|
||||||
seed frames: 0xFFFF -> 00 00 40 FF FF 1A; 0x4030 -> 00 00 40 40 30 6A
|
seed frames: 0xFFFF -> 00 00 40 FF FF 1A; 0x4030 -> 00 00 40 40 30 6A
|
||||||
readback frame: 01 00 40 00 00 1B
|
readback frame: 01 00 40 00 00 1B
|
||||||
|
- 0x06B standard_lamp_lane: score=11 tables=none
|
||||||
|
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
|
||||||
|
- Bench-visible STANDARD lamp lane found from ROM-derived F6D4.6 handler candidate.
|
||||||
|
- selector dispatches to H'2F72
|
||||||
|
seed frames: 0x8000 -> 00 00 6B 80 00 B1
|
||||||
|
readback frame: 01 00 6B 00 00 30
|
||||||
|
- 0x015 call_and_red_tally_lamp_lane: score=9 tables=none
|
||||||
|
- observed RCP autonomous report frame(s): 00 00 15 80 00 CF, 00 00 15 00 00 4F
|
||||||
|
- Bench-visible CALL lamp and red tally lane; local CALL handler mirrors F6DB.5 into E800[0x0015].15.
|
||||||
|
- selector dispatches to H'2E39
|
||||||
|
seed frames: 0x8000 -> 00 00 15 80 00 CF; 0x0000 -> 00 00 15 00 00 4F
|
||||||
|
readback frame: 01 00 15 00 00 4E
|
||||||
- 0x081 state_selector_candidate: score=9 tables=primary_value_table_candidate, current_value_table_candidate
|
- 0x081 state_selector_candidate: score=9 tables=primary_value_table_candidate, current_value_table_candidate
|
||||||
- primary_value_table_candidate read in vec_ad_adi_3D99: MOV:G.W @H'E102, R0
|
- primary_value_table_candidate read in vec_ad_adi_3D99: MOV:G.W @H'E102, R0
|
||||||
- primary_value_table_candidate read in vec_ad_adi_3D99: CMP:G.W @H'E102, R1
|
- primary_value_table_candidate read in vec_ad_adi_3D99: CMP:G.W @H'E102, R1
|
||||||
@@ -52,10 +65,6 @@ Highest-Value Selector Candidates:
|
|||||||
- primary_value_table_candidate read in loc_2650: CMP:G.W @H'E124, R0
|
- primary_value_table_candidate read in loc_2650: CMP:G.W @H'E124, R0
|
||||||
- current_value_table_candidate write in loc_2650: MOV:G.W R0, @H'E924
|
- current_value_table_candidate write in loc_2650: MOV:G.W R0, @H'E924
|
||||||
readback frame: 01 01 12 00 00 48
|
readback frame: 01 01 12 00 00 48
|
||||||
- 0x06B connection_latch_clear_candidate: score=7 tables=none
|
|
||||||
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
|
|
||||||
- selector dispatches to H'2F72
|
|
||||||
readback frame: 01 00 6B 00 00 30
|
|
||||||
- 0x06C command5_be70_candidate: score=7 tables=none
|
- 0x06C command5_be70_candidate: score=7 tables=none
|
||||||
- continuation command 5 calls BE70 for selector 0x006C
|
- continuation command 5 calls BE70 for selector 0x006C
|
||||||
- selector dispatches to H'2FAF
|
- selector dispatches to H'2FAF
|
||||||
@@ -64,14 +73,37 @@ Highest-Value Selector Candidates:
|
|||||||
- continuation command 5 calls BE70 for selector 0x006D
|
- continuation command 5 calls BE70 for selector 0x006D
|
||||||
- selector dispatches to H'3015
|
- selector dispatches to H'3015
|
||||||
readback frame: 01 00 6D 00 00 36
|
readback frame: 01 00 6D 00 00 36
|
||||||
|
- 0x110 knee_auto_lamp_or_page_status_lane: score=7 tables=primary_value_table_candidate
|
||||||
|
- primary_value_table_candidate read in loc_1795: BTST.W #15, @H'E220
|
||||||
|
- Bench-visible KNEE AUTO source; ROM notes indicate timed KNEE/detail page interaction.
|
||||||
|
seed frames: 0x8000 -> 00 01 90 80 00 4B
|
||||||
|
readback frame: 01 01 90 00 00 CA
|
||||||
|
- 0x013 slave_and_iris_mblack_link_lamps: score=6 tables=none
|
||||||
|
- Selector 0x0013 is a two-bit lamp/status word. ROM dispatch H'2E06 reads current table word H'E826 and fans bit 15 and bit 14 into panel latch RAM.
|
||||||
|
- 0x8000 SLAVE lamp: sets F791.6 and F713.4
|
||||||
|
- 0x4000 IRIS/M.BLACK LINK lamp: sets F791.5 and F716.7
|
||||||
|
- selector dispatches to H'2E06
|
||||||
|
seed frames: 0x8000 -> 00 00 13 80 00 C9; 0x4000 -> 00 00 13 40 00 09; 0x0000 -> 00 00 13 00 00 49
|
||||||
|
readback frame: 01 00 13 00 00 48
|
||||||
|
- 0x017 bars_lamp_lane: score=6 tables=none
|
||||||
|
- Bench-visible BARS lamp/latch lane; low writes do not reliably clear the visible latch.
|
||||||
|
- selector dispatches to H'2E85
|
||||||
|
seed frames: 0x8000 -> 00 00 17 80 00 CD; 0x4000 -> 00 00 17 40 00 0D; 0x0000 -> 00 00 17 00 00 4D
|
||||||
|
readback frame: 01 00 17 00 00 4C
|
||||||
|
- 0x01A monitor_selector_lamps: score=6 tables=none
|
||||||
|
- Bench-visible MONITOR selector cluster found from ROM-derived button-output sweep.
|
||||||
|
- selector dispatches to H'2EC4
|
||||||
|
seed frames: 0x0808 -> 00 00 1A 08 08 40; 0x2020 -> 00 00 1A 20 20 40; 0x4040 -> 00 00 1A 40 40 40
|
||||||
|
readback frame: 01 00 1A 00 00 41
|
||||||
|
- 0x024 lcd_selector_button_lamp: score=6 tables=none
|
||||||
|
- Bench-visible LCD selector-button lamp lane.
|
||||||
|
- selector dispatches to H'2F0C
|
||||||
|
seed frames: 0x8000 -> 00 00 24 80 00 FE; 0x0000 -> 00 00 24 00 00 7E
|
||||||
|
readback frame: 01 00 24 00 00 7F
|
||||||
- 0x007 camera_power_report_candidate: score=5 tables=none
|
- 0x007 camera_power_report_candidate: score=5 tables=none
|
||||||
- observed RCP autonomous report frame(s): 00 00 07 80 00 DD
|
- observed RCP autonomous report frame(s): 00 00 07 80 00 DD
|
||||||
- selector dispatches to H'2DC3
|
- selector dispatches to H'2DC3
|
||||||
readback frame: 01 00 07 00 00 5C
|
readback frame: 01 00 07 00 00 5C
|
||||||
- 0x015 call_button_report_candidate: score=5 tables=none
|
|
||||||
- observed RCP autonomous report frame(s): 00 00 15 80 00 CF, 00 00 15 00 00 4F
|
|
||||||
- selector dispatches to H'2E39
|
|
||||||
readback frame: 01 00 15 00 00 4E
|
|
||||||
- 0x023 state_selector_candidate: score=5 tables=primary_value_table_candidate
|
- 0x023 state_selector_candidate: score=5 tables=primary_value_table_candidate
|
||||||
- primary_value_table_candidate write in loc_400C: CLR.W @H'E046
|
- primary_value_table_candidate write in loc_400C: CLR.W @H'E046
|
||||||
- selector dispatches to H'2EE6
|
- selector dispatches to H'2EE6
|
||||||
@@ -91,24 +123,10 @@ Highest-Value Selector Candidates:
|
|||||||
- 0x0F8 connection_latch_clear_candidate: score=5 tables=none
|
- 0x0F8 connection_latch_clear_candidate: score=5 tables=none
|
||||||
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
|
- when F731.7 is set, command 5 on this selector clears F731.7/F790.7
|
||||||
readback frame: 01 01 78 00 00 22
|
readback frame: 01 01 78 00 00 22
|
||||||
- 0x002 state_selector_candidate: score=3 tables=primary_value_table_candidate
|
- 0x082 iris_readout_lane: score=4 tables=none
|
||||||
- primary_value_table_candidate read in loc_2650: BTST.W #13, @H'E004
|
- Bench-visible IRIS seven-segment/display lane.
|
||||||
readback frame: 01 00 02 00 00 59
|
seed frames: 0x8000 -> 00 01 02 80 00 D9; 0x4000 -> 00 01 02 40 00 19; 0x0000 -> 00 01 02 00 00 59
|
||||||
- 0x0A7 state_selector_candidate: score=3 tables=primary_value_table_candidate
|
readback frame: 01 01 02 00 00 58
|
||||||
- primary_value_table_candidate read in loc_1705: BTST.W #15, @H'E14E
|
|
||||||
readback frame: 01 01 27 00 00 7D
|
|
||||||
- 0x0B7 state_selector_candidate: score=3 tables=primary_value_table_candidate
|
|
||||||
- primary_value_table_candidate read in loc_174D: BTST.W #13, @H'E16E
|
|
||||||
readback frame: 01 01 37 00 00 6D
|
|
||||||
- 0x0B9 state_selector_candidate: score=3 tables=primary_value_table_candidate
|
|
||||||
- primary_value_table_candidate read in loc_1795: BTST.W #13, @H'E172
|
|
||||||
readback frame: 01 01 39 00 00 63
|
|
||||||
- 0x110 state_selector_candidate: score=3 tables=primary_value_table_candidate
|
|
||||||
- primary_value_table_candidate read in loc_1795: BTST.W #15, @H'E220
|
|
||||||
readback frame: 01 01 90 00 00 CA
|
|
||||||
- 0x012 state_selector_candidate: score=2 tables=none
|
|
||||||
- selector dispatches to H'2E03
|
|
||||||
readback frame: 01 00 12 00 00 49
|
|
||||||
|
|
||||||
Display Text Hints:
|
Display Text Hints:
|
||||||
- CONNECT: 0 hit(s)
|
- CONNECT: 0 hit(s)
|
||||||
|
|||||||
4771
build/rom_copy_lcd.asm
Normal file
4771
build/rom_copy_lcd.asm
Normal file
File diff suppressed because it is too large
Load Diff
248176
build/rom_copy_lcd.json
Normal file
248176
build/rom_copy_lcd.json
Normal file
File diff suppressed because it is too large
Load Diff
4259
build/rom_copy_lcd_path.asm
Normal file
4259
build/rom_copy_lcd_path.asm
Normal file
File diff suppressed because it is too large
Load Diff
221997
build/rom_copy_lcd_path.json
Normal file
221997
build/rom_copy_lcd_path.json
Normal file
File diff suppressed because it is too large
Load Diff
203
build/rom_copy_text_linear.asm
Normal file
203
build/rom_copy_text_linear.asm
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
; H8/536 ROM disassembly
|
||||||
|
; input: ROM\M27C512@DIP28_1.BIN
|
||||||
|
; bytes: 65536
|
||||||
|
; vector mode: min
|
||||||
|
; analysis: linear sweep
|
||||||
|
;
|
||||||
|
; Notes from the manual:
|
||||||
|
; - H8/536 uses the H8/500 CPU instruction set.
|
||||||
|
; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.
|
||||||
|
; - The register field is H'FE80-H'FFFF; names below come from appendix B.
|
||||||
|
; - @aa:8 short absolute operands use BR as the upper address byte.
|
||||||
|
; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.
|
||||||
|
; - LCD inference treats E-clock H'F200/H'F201 accesses as status/control and data candidates.
|
||||||
|
; - Pass --clock-hz to convert SCI BRR settings into numeric baud rates.
|
||||||
|
; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states.
|
||||||
|
|
||||||
|
; Memory Map
|
||||||
|
; H'0000-H'009F exception_vectors vectors
|
||||||
|
; H'00A0-H'00FF dtc_vectors dtc_vectors
|
||||||
|
; H'0100-H'F67F program_or_external program
|
||||||
|
; H'F680-H'FE7F on_chip_ram ram
|
||||||
|
; H'FE80-H'FFFF register_field registers
|
||||||
|
|
||||||
|
; Vectors
|
||||||
|
; H'0000 reset -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0004 invalid_instruction -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0006 zero_divide -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0008 trap_vs -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0010 address_error -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0012 trace -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0016 nmi -> vec_nmi_4393 (H'4393)
|
||||||
|
; H'0020 trapa_0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0022 trapa_1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0024 trapa_2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0026 trapa_3 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0028 trapa_4 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002A trapa_5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002C trapa_6 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002E trapa_7 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0030 trapa_8 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0032 trapa_9 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0034 trapa_a -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0036 trapa_b -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0038 trapa_c -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003A trapa_d -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003C trapa_e -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003E trapa_f -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0040 irq0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0042 interval_timer -> vec_interval_timer_BFC4 (H'BFC4)
|
||||||
|
; H'0048 irq1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0050 irq2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0052 irq3 -> vec_irq3_3C30 (H'3C30)
|
||||||
|
; H'0058 irq4 -> vec_irq4_3AC7 (H'3AC7)
|
||||||
|
; H'005A irq5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0062 frt1_ocia -> vec_frt1_ocia_BEEA (H'BEEA)
|
||||||
|
; H'006A frt2_ocia -> vec_frt2_ocia_BF23 (H'BF23)
|
||||||
|
; H'0080 sci1_eri -> vec_sci1_eri_BB57 (H'BB57)
|
||||||
|
; H'0082 sci1_rxi -> vec_sci1_rxi_BB67 (H'BB67)
|
||||||
|
; H'0084 sci1_txi -> vec_sci1_txi_BA84 (H'BA84)
|
||||||
|
; H'0090 ad_adi -> vec_ad_adi_3D99 (H'3D99)
|
||||||
|
|
||||||
|
; Symbols
|
||||||
|
; ram_F738 H'F738 on_chip_ram ram r=0 w=2 width=word
|
||||||
|
; ram_F73A H'F73A on_chip_ram ram r=0 w=2 width=word
|
||||||
|
; ram_F73C H'F73C on_chip_ram ram r=0 w=2 width=word
|
||||||
|
; ram_F73E H'F73E on_chip_ram ram r=0 w=3 width=word
|
||||||
|
; ram_F740 H'F740 on_chip_ram ram r=0 w=2 width=word
|
||||||
|
; ram_F742 H'F742 on_chip_ram ram r=0 w=3 width=word
|
||||||
|
; ram_F754 H'F754 on_chip_ram ram r=0 w=3 width=word
|
||||||
|
|
||||||
|
; Board Profile
|
||||||
|
; Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.
|
||||||
|
; H8 pin 66 P95/TXD (TXD) -> MAX202 pin 11
|
||||||
|
; H8 pin 67 P96/RXD (RXD) -> MAX202 pin 12
|
||||||
|
; SCI2 pin routing is disabled by SYSCR2.P9SCI2E=0 in the observed setup.
|
||||||
|
|
||||||
|
; LCD/Text Scan
|
||||||
|
; search 'CONNECT': not literal, hits=0
|
||||||
|
; near: H'A025 'COMPLETED', H'9F98 'COPY', H'A008 'COPY'
|
||||||
|
; LCD text regions
|
||||||
|
; region H'9F98-H'A033 count=4 'COPY', 'IN PROGRESS', 'COPY', 'COMPLETED'
|
||||||
|
; LCD text candidates
|
||||||
|
; text H'9F98 len=14 medium 'COPY' xrefs=2
|
||||||
|
; text H'9FB5 len=14 medium 'IN PROGRESS' xrefs=2
|
||||||
|
; text H'A008 len=14 medium 'COPY' xrefs=2
|
||||||
|
; text H'A025 len=14 medium 'COMPLETED' xrefs=2
|
||||||
|
|
||||||
|
9F80: 40 06 CMP:E #H'06, R0 ; cycles=2
|
||||||
|
9F82: 00 NOP ; cycles=2
|
||||||
|
9F83: 1D F7 3E 06 00 MOV:G.W #H'00, @H'F73E ; refs ram_F73E in on_chip_ram; cycles=9
|
||||||
|
9F88: 1D F7 42 06 00 MOV:G.W #H'00, @H'F742 ; refs ram_F742 in on_chip_ram; cycles=11
|
||||||
|
9F8D: 1D F7 54 06 00 MOV:G.W #H'00, @H'F754 ; refs ram_F754 in on_chip_ram; cycles=9
|
||||||
|
9F92: 1E C1 71 BSR loc_6106 ; cycles=13
|
||||||
|
9F95: 20 10 BRA loc_9FA7 ; cycles=8
|
||||||
|
9F97: 06 .db H'06
|
||||||
|
9F98: 20 20 BRA loc_9FBA ; cycles=7
|
||||||
|
9F9A: 20 20 BRA loc_9FBC ; cycles=7
|
||||||
|
9F9C: 20 43 BRA loc_9FE1 ; cycles=7
|
||||||
|
9F9E: 4F 50 59 CMP:I #H'5059, R7 ; cycles=3
|
||||||
|
9FA1: 20 20 BRA loc_9FC3 ; cycles=8
|
||||||
|
9FA3: 20 20 BRA loc_9FC5 ; cycles=8
|
||||||
|
9FA5: 20 07 BRA loc_9FAE ; cycles=8
|
||||||
|
|
||||||
|
loc_9FA7:
|
||||||
|
9FA7: 58 9F 97 MOV:I.W #H'9F97, R0 ; LCD text xref H'9F98 'COPY'; dataflow R0=H'9F97; cycles=3
|
||||||
|
9FAA: 1E BA E4 BSR loc_5A91 ; cycles=13
|
||||||
|
9FAD: 55 01 MOV:E.B #H'01, R5 ; dataflow R5=H'01; cycles=2
|
||||||
|
9FAF: 1E 9F 1A BSR loc_3ECC ; cycles=14
|
||||||
|
9FB2: 20 10 BRA loc_9FC4 ; cycles=7
|
||||||
|
9FB4: 06 .db H'06
|
||||||
|
9FB5: 20 49 BRA loc_A000 ; cycles=8
|
||||||
|
9FB7: 4E 20 50 CMP:I #H'2050, R6 ; cycles=3
|
||||||
|
|
||||||
|
loc_9FBA:
|
||||||
|
9FBA: 52 4F MOV:E.B #H'4F, R2 ; dataflow R2=H'4F; cycles=2
|
||||||
|
|
||||||
|
loc_9FBC:
|
||||||
|
9FBC: 47 52 CMP:E #H'52, R7 ; cycles=2
|
||||||
|
9FBE: 45 53 CMP:E #H'53, R5 ; cycles=2
|
||||||
|
9FC0: 53 20 MOV:E.B #H'20, R3 ; dataflow R3=H'20; cycles=2
|
||||||
|
9FC2: 20 07 BRA loc_9FCB ; cycles=7
|
||||||
|
|
||||||
|
loc_9FC4:
|
||||||
|
9FC4: 58 9F B4 MOV:I.W #H'9FB4, R0 ; LCD text xref H'9FB5 'IN PROGRESS'; dataflow R0=H'9FB4; cycles=3
|
||||||
|
9FC7: 1E BA C7 BSR loc_5A91 ; cycles=14
|
||||||
|
9FCA: 55 02 MOV:E.B #H'02, R5 ; dataflow R5=H'02; cycles=2
|
||||||
|
9FCC: 1E 9E FD BSR loc_3ECC ; cycles=13
|
||||||
|
9FCF: 1E C1 6A BSR loc_613C ; cycles=14
|
||||||
|
9FD2: 1E BA 2F BSR loc_5A04 ; cycles=13
|
||||||
|
9FD5: 19 RTS ; cycles=13
|
||||||
|
9FD6: 00 NOP ; cycles=2
|
||||||
|
9FD7: 00 NOP ; cycles=2
|
||||||
|
9FD8: 00 NOP ; cycles=2
|
||||||
|
9FD9: FF 1D F7 36 SUB.W @(H'1DF7,R7), R6 ; cycles=6
|
||||||
|
9FDD: 06 .db H'06
|
||||||
|
9FDE: 00 NOP ; cycles=2
|
||||||
|
9FDF: 1D F7 38 06 00 MOV:G.W #H'00, @H'F738 ; refs ram_F738 in on_chip_ram; cycles=9
|
||||||
|
9FE4: 1D F7 3A 06 00 MOV:G.W #H'00, @H'F73A ; refs ram_F73A in on_chip_ram; cycles=11
|
||||||
|
9FE9: 1D F7 3C 06 00 MOV:G.W #H'00, @H'F73C ; refs ram_F73C in on_chip_ram; cycles=9
|
||||||
|
9FEE: 1D F7 40 06 00 MOV:G.W #H'00, @H'F740 ; refs ram_F740 in on_chip_ram; cycles=11
|
||||||
|
9FF3: 1D F7 3E 06 00 MOV:G.W #H'00, @H'F73E ; refs ram_F73E in on_chip_ram; cycles=9
|
||||||
|
9FF8: 1D F7 42 06 00 MOV:G.W #H'00, @H'F742 ; refs ram_F742 in on_chip_ram; cycles=11
|
||||||
|
9FFD: 1D F7 54 06 00 MOV:G.W #H'00, @H'F754 ; refs ram_F754 in on_chip_ram; cycles=9
|
||||||
|
A002: 1E C1 01 BSR loc_6106 ; cycles=13
|
||||||
|
A005: 20 10 BRA loc_A017 ; cycles=8
|
||||||
|
A007: 06 .db H'06
|
||||||
|
A008: 20 20 BRA loc_A02A ; cycles=7
|
||||||
|
A00A: 20 20 BRA loc_A02C ; cycles=7
|
||||||
|
A00C: 20 43 BRA loc_A051 ; cycles=7
|
||||||
|
A00E: 4F 50 59 CMP:I #H'5059, R7 ; cycles=3
|
||||||
|
A011: 20 20 BRA loc_A033 ; cycles=8
|
||||||
|
A013: 20 20 BRA loc_A035 ; cycles=8
|
||||||
|
A015: 20 07 BRA loc_A01E ; cycles=8
|
||||||
|
|
||||||
|
loc_A017:
|
||||||
|
A017: 58 A0 07 MOV:I.W #H'A007, R0 ; LCD text xref H'A008 'COPY'; dataflow R0=H'A007; cycles=3
|
||||||
|
A01A: 1E BA 74 BSR loc_5A91 ; cycles=13
|
||||||
|
A01D: 55 01 MOV:E.B #H'01, R5 ; dataflow R5=H'01; cycles=2
|
||||||
|
A01F: 1E 9E AA BSR loc_3ECC ; cycles=14
|
||||||
|
A022: 20 10 BRA loc_A034 ; cycles=7
|
||||||
|
A024: 06 .db H'06
|
||||||
|
A025: 20 20 BRA loc_A047 ; cycles=8
|
||||||
|
A027: 43 4F CMP:E #H'4F, R3 ; cycles=2
|
||||||
|
A029: 4D 50 4C CMP:I #H'504C, R5 ; cycles=3
|
||||||
|
|
||||||
|
loc_A02C:
|
||||||
|
A02C: 45 54 CMP:E #H'54, R5 ; cycles=2
|
||||||
|
A02E: 45 44 CMP:E #H'44, R5 ; cycles=2
|
||||||
|
A030: 20 20 BRA loc_A052 ; cycles=7
|
||||||
|
A032: 20 07 BRA loc_A03B ; cycles=7
|
||||||
|
|
||||||
|
loc_A034:
|
||||||
|
A034: 58 A0 24 MOV:I.W #H'A024, R0 ; LCD text xref H'A025 'COMPLETED'; dataflow R0=H'A024; cycles=3
|
||||||
|
A037: 1E BA 57 BSR loc_5A91 ; cycles=14
|
||||||
|
A03A: 55 02 MOV:E.B #H'02, R5 ; dataflow R5=H'02; cycles=2
|
||||||
|
A03C: 1E 9E 8D BSR loc_3ECC ; cycles=13
|
||||||
|
A03F: 1E C0 FA BSR loc_613C ; cycles=14
|
||||||
|
A042: 1E B9 BF BSR loc_5A04 ; cycles=13
|
||||||
|
A045: 19 RTS ; cycles=13
|
||||||
|
A046: 00 NOP ; cycles=2
|
||||||
|
|
||||||
|
loc_A047:
|
||||||
|
A047: 00 NOP ; cycles=2
|
||||||
|
A048: 00 NOP ; cycles=2
|
||||||
|
A049: FF 1E C2 FB BTST.W #11, @(H'1EC2,R7) ; cycles=6
|
||||||
|
A04D: 19 RTS ; cycles=13
|
||||||
|
A04E: 00 NOP ; cycles=2
|
||||||
|
A04F: 00 NOP ; cycles=2
|
||||||
|
A050: 00 NOP ; cycles=2
|
||||||
|
|
||||||
|
loc_A051:
|
||||||
|
A051: FF 1D F7 36 SUB.W @(H'1DF7,R7), R6 ; cycles=6
|
||||||
|
A055: 06 .db H'06
|
||||||
|
A056: 00 NOP ; cycles=2
|
||||||
|
A057: 1D F7 38 06 00 MOV:G.W #H'00, @H'F738 ; refs ram_F738 in on_chip_ram; cycles=9
|
||||||
|
A05C: 1D F7 3A 06 00 MOV:G.W #H'00, @H'F73A ; refs ram_F73A in on_chip_ram; cycles=11
|
||||||
|
A061: 1D F7 3C 06 00 MOV:G.W #H'00, @H'F73C ; refs ram_F73C in on_chip_ram; cycles=9
|
||||||
|
A066: 1D F7 40 06 00 MOV:G.W #H'00, @H'F740 ; refs ram_F740 in on_chip_ram; cycles=11
|
||||||
|
A06B: 1D F7 3E 06 00 MOV:G.W #H'00, @H'F73E ; refs ram_F73E in on_chip_ram; cycles=9
|
||||||
|
A070: 1D F7 42 06 00 MOV:G.W #H'00, @H'F742 ; refs ram_F742 in on_chip_ram; cycles=11
|
||||||
|
A075: 1D F7 54 07 00 B9 MOV:G.W #H'00B9, @H'F754 ; refs ram_F754 in on_chip_ram; cycles=9
|
||||||
|
A07B: 59 00 A9 MOV:I.W #H'00A9, R1 ; dataflow R1=H'00A9; cycles=3
|
||||||
|
A07E: 58 00 00 MOV:I.W #H'0000, R0 ; dataflow R0=H'0000; cycles=3
|
||||||
6016
build/rom_copy_text_linear.json
Normal file
6016
build/rom_copy_text_linear.json
Normal file
File diff suppressed because it is too large
Load Diff
433
build/rom_f109_handlers.asm
Normal file
433
build/rom_f109_handlers.asm
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
; H8/536 ROM disassembly
|
||||||
|
; input: ROM\M27C512@DIP28_1.BIN
|
||||||
|
; bytes: 65536
|
||||||
|
; vector mode: min
|
||||||
|
; analysis: recursive trace from vectors
|
||||||
|
;
|
||||||
|
; Notes from the manual:
|
||||||
|
; - H8/536 uses the H8/500 CPU instruction set.
|
||||||
|
; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.
|
||||||
|
; - The register field is H'FE80-H'FFFF; names below come from appendix B.
|
||||||
|
; - @aa:8 short absolute operands use BR as the upper address byte.
|
||||||
|
; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.
|
||||||
|
; - LCD inference treats E-clock H'F200/H'F201 accesses as status/control and data candidates.
|
||||||
|
; - Pass --clock-hz to convert SCI BRR settings into numeric baud rates.
|
||||||
|
; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states.
|
||||||
|
|
||||||
|
; Memory Map
|
||||||
|
; H'0000-H'009F exception_vectors vectors
|
||||||
|
; H'00A0-H'00FF dtc_vectors dtc_vectors
|
||||||
|
; H'0100-H'F67F program_or_external program
|
||||||
|
; H'F680-H'FE7F on_chip_ram ram
|
||||||
|
; H'FE80-H'FFFF register_field registers
|
||||||
|
|
||||||
|
; Vectors
|
||||||
|
; H'0000 reset -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0004 invalid_instruction -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0006 zero_divide -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0008 trap_vs -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0010 address_error -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0012 trace -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0016 nmi -> vec_nmi_4393 (H'4393)
|
||||||
|
; H'0020 trapa_0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0022 trapa_1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0024 trapa_2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0026 trapa_3 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0028 trapa_4 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002A trapa_5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002C trapa_6 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002E trapa_7 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0030 trapa_8 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0032 trapa_9 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0034 trapa_a -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0036 trapa_b -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0038 trapa_c -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003A trapa_d -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003C trapa_e -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003E trapa_f -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0040 irq0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0042 interval_timer -> vec_interval_timer_BFC4 (H'BFC4)
|
||||||
|
; H'0048 irq1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0050 irq2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0052 irq3 -> vec_irq3_3C30 (H'3C30)
|
||||||
|
; H'0058 irq4 -> vec_irq4_3AC7 (H'3AC7)
|
||||||
|
; H'005A irq5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0062 frt1_ocia -> vec_frt1_ocia_BEEA (H'BEEA)
|
||||||
|
; H'006A frt2_ocia -> vec_frt2_ocia_BF23 (H'BF23)
|
||||||
|
; H'0080 sci1_eri -> vec_sci1_eri_BB57 (H'BB57)
|
||||||
|
; H'0082 sci1_rxi -> vec_sci1_rxi_BB67 (H'BB67)
|
||||||
|
; H'0084 sci1_txi -> vec_sci1_txi_BA84 (H'BA84)
|
||||||
|
; H'0090 ad_adi -> vec_ad_adi_3D99 (H'3D99)
|
||||||
|
|
||||||
|
; Symbols
|
||||||
|
; mem_E106 H'E106 program_or_external memory r=5 w=2 width=word
|
||||||
|
; mem_E110 H'E110 program_or_external memory r=7 w=0 width=word
|
||||||
|
; mem_E11E H'E11E program_or_external memory r=4 w=0 width=word
|
||||||
|
; mem_E506 H'E506 program_or_external memory r=2 w=0 width=word
|
||||||
|
; mem_E906 H'E906 program_or_external memory r=0 w=4 width=word
|
||||||
|
; mem_E91E H'E91E program_or_external memory r=0 w=2 width=word
|
||||||
|
; mem_E922 H'E922 program_or_external memory r=0 w=1 width=word
|
||||||
|
; mem_F404 H'F404 program_or_external memory r=11 w=0 width=byte
|
||||||
|
; ram_F6D0 H'F6D0 on_chip_ram ram r=13 w=0 width=byte
|
||||||
|
; ram_F6F4 H'F6F4 on_chip_ram ram r=0 w=2 width=word
|
||||||
|
; ram_F6F6 H'F6F6 on_chip_ram ram r=0 w=4 width=byte
|
||||||
|
; ram_F730 H'F730 on_chip_ram ram r=4 w=0 width=byte
|
||||||
|
; ram_F731 H'F731 on_chip_ram ram r=7 w=0 width=byte
|
||||||
|
; ram_F791 H'F791 on_chip_ram ram r=11 w=0 width=byte
|
||||||
|
|
||||||
|
; Board Profile
|
||||||
|
; Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.
|
||||||
|
; H8 pin 66 P95/TXD (TXD) -> MAX202 pin 11
|
||||||
|
; H8 pin 67 P96/RXD (RXD) -> MAX202 pin 12
|
||||||
|
; SCI2 pin routing is disabled by SYSCR2.P9SCI2E=0 in the observed setup.
|
||||||
|
|
||||||
|
; LCD/Text Scan
|
||||||
|
; search 'CONNECT': not literal, hits=0
|
||||||
|
; LCD text candidates
|
||||||
|
; ... 1 more LCD text candidates
|
||||||
|
|
||||||
|
2390: 15 F6 D0 F1 BTST.B #1, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
2394: 37 00 70 BEQ loc_2407 ; cycles=3/7 nt/t
|
||||||
|
2397: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram; cycles=6
|
||||||
|
239C: 32 00 68 BHI loc_2407 ; cycles=3/7 nt/t
|
||||||
|
239F: 1D E1 10 FF BTST.W #15, @H'E110 ; refs mem_E110 in program_or_external; cycles=6
|
||||||
|
23A3: 27 05 BEQ loc_23AA ; cycles=3/8 nt/t
|
||||||
|
23A5: 1E 03 40 BSR loc_26E8 ; cycles=14
|
||||||
|
23A8: 20 5D BRA loc_2407 ; cycles=7
|
||||||
|
|
||||||
|
loc_23AA:
|
||||||
|
23AA: 15 F6 D0 F2 BTST.B #2, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
23AE: 36 00 CF BNE loc_2480 ; cycles=3/7 nt/t
|
||||||
|
23B1: 15 F7 30 F7 BTST.B #7, @H'F730 ; refs ram_F730 in on_chip_ram; cycles=6
|
||||||
|
23B5: 27 35 BEQ loc_23EC ; cycles=3/8 nt/t
|
||||||
|
23B7: 1D E1 06 80 MOV:G.W @H'E106, R0 ; refs mem_E106 in program_or_external; cycles=6
|
||||||
|
|
||||||
|
loc_23BB:
|
||||||
|
23BB: A8 1B SHLR.W R0 ; cycles=3
|
||||||
|
|
||||||
|
loc_23BD:
|
||||||
|
23BD: A8 81 MOV:G.W R0, R1 ; cycles=3
|
||||||
|
23BF: 27 26 BEQ loc_23E7 ; cycles=3/8 nt/t
|
||||||
|
23C1: 1D E5 06 51 AND.W @H'E506, R1 ; refs mem_E506 in program_or_external; cycles=6
|
||||||
|
23C5: 0C FF C4 51 AND.W #H'FFC4, R1 ; cycles=4
|
||||||
|
23C9: 27 F0 BEQ loc_23BB ; cycles=3/8 nt/t
|
||||||
|
23CB: 1D E9 06 91 MOV:G.W R1, @H'E906 ; refs mem_E906 in program_or_external; cycles=6
|
||||||
|
23CF: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
23D1: 5B 00 83 MOV:I.W #H'0083, R3 ; dataflow R3=H'0083; cycles=3
|
||||||
|
23D4: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
23D8: 27 08 BEQ loc_23E2 ; cycles=3/7 nt/t
|
||||||
|
23DA: 15 F4 04 F5 BTST.B #5, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
23DE: 27 02 BEQ loc_23E2 ; cycles=3/7 nt/t
|
||||||
|
23E0: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_23E2:
|
||||||
|
23E2: 1E 1A 6F BSR loc_3E54 ; cycles=13
|
||||||
|
23E5: 20 20 BRA loc_2407 ; cycles=8
|
||||||
|
|
||||||
|
loc_23E7:
|
||||||
|
23E7: 58 00 04 MOV:I.W #H'0004, R0 ; dataflow R0=H'0004; cycles=3
|
||||||
|
23EA: 20 48 BRA loc_2434 ; cycles=7
|
||||||
|
|
||||||
|
loc_23EC:
|
||||||
|
23EC: 1D E1 06 D0 BCLR.W #0, @H'E106 ; refs mem_E106 in program_or_external; cycles=9
|
||||||
|
23F0: 5C 00 00 MOV:I.W #H'0000, R4 ; dataflow R4=H'0000; cycles=3
|
||||||
|
23F3: 5B 00 83 MOV:I.W #H'0083, R3 ; dataflow R3=H'0083; cycles=3
|
||||||
|
23F6: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
23FA: 27 08 BEQ loc_2404 ; cycles=3/7 nt/t
|
||||||
|
23FC: 15 F4 04 F5 BTST.B #5, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
2400: 27 02 BEQ loc_2404 ; cycles=3/7 nt/t
|
||||||
|
2402: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_2404:
|
||||||
|
2404: 1E F6 2E BSR loc_1A35 ; cycles=13
|
||||||
|
|
||||||
|
loc_2407:
|
||||||
|
2407: 19 RTS ; cycles=13
|
||||||
|
2408: 15 F6 D0 F2 BTST.B #2, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
240C: 37 00 70 BEQ loc_247F ; cycles=3/7 nt/t
|
||||||
|
240F: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram; cycles=6
|
||||||
|
2414: 32 00 68 BHI loc_247F ; cycles=3/7 nt/t
|
||||||
|
2417: 1D E1 10 FF BTST.W #15, @H'E110 ; refs mem_E110 in program_or_external; cycles=6
|
||||||
|
241B: 27 05 BEQ loc_2422 ; cycles=3/8 nt/t
|
||||||
|
241D: 1E 02 C8 BSR loc_26E8 ; cycles=14
|
||||||
|
2420: 20 5D BRA loc_247F ; cycles=7
|
||||||
|
|
||||||
|
loc_2422:
|
||||||
|
2422: 15 F6 D0 F1 BTST.B #1, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
2426: 26 58 BNE loc_2480 ; cycles=3/7 nt/t
|
||||||
|
2428: 15 F7 30 F7 BTST.B #7, @H'F730 ; refs ram_F730 in on_chip_ram; cycles=7
|
||||||
|
242C: 27 36 BEQ loc_2464 ; cycles=3/7 nt/t
|
||||||
|
242E: 1D E1 06 80 MOV:G.W @H'E106, R0 ; refs mem_E106 in program_or_external; cycles=7
|
||||||
|
|
||||||
|
loc_2432:
|
||||||
|
2432: A8 1A SHLL.W R0 ; cycles=3
|
||||||
|
|
||||||
|
loc_2434:
|
||||||
|
2434: A8 81 MOV:G.W R0, R1 ; cycles=3
|
||||||
|
2436: 27 26 BEQ loc_245E ; cycles=3/7 nt/t
|
||||||
|
2438: 1D E5 06 51 AND.W @H'E506, R1 ; refs mem_E506 in program_or_external; cycles=7
|
||||||
|
243C: 0C FF C4 51 AND.W #H'FFC4, R1 ; cycles=4
|
||||||
|
2440: 27 F0 BEQ loc_2432 ; cycles=3/7 nt/t
|
||||||
|
2442: 1D E9 06 91 MOV:G.W R1, @H'E906 ; refs mem_E906 in program_or_external; cycles=7
|
||||||
|
2446: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
2448: 5B 00 83 MOV:I.W #H'0083, R3 ; dataflow R3=H'0083; cycles=3
|
||||||
|
244B: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=6
|
||||||
|
244F: 27 08 BEQ loc_2459 ; cycles=3/8 nt/t
|
||||||
|
2451: 15 F4 04 F5 BTST.B #5, @H'F404 ; refs mem_F404 in program_or_external; cycles=6
|
||||||
|
2455: 27 02 BEQ loc_2459 ; cycles=3/8 nt/t
|
||||||
|
2457: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_2459:
|
||||||
|
2459: 1E 19 F8 BSR loc_3E54 ; cycles=14
|
||||||
|
245C: 20 21 BRA loc_247F ; cycles=7
|
||||||
|
|
||||||
|
loc_245E:
|
||||||
|
245E: 58 80 00 MOV:I.W #H'8000, R0 ; dataflow R0=H'8000; cycles=3
|
||||||
|
2461: 30 FF 59 BRA loc_23BD ; cycles=8
|
||||||
|
|
||||||
|
loc_2464:
|
||||||
|
2464: 1D E1 06 D0 BCLR.W #0, @H'E106 ; refs mem_E106 in program_or_external; cycles=9
|
||||||
|
2468: 5C 00 01 MOV:I.W #H'0001, R4 ; dataflow R4=H'0001; cycles=3
|
||||||
|
246B: 5B 00 83 MOV:I.W #H'0083, R3 ; dataflow R3=H'0083; cycles=3
|
||||||
|
246E: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
2472: 27 08 BEQ loc_247C ; cycles=3/7 nt/t
|
||||||
|
2474: 15 F4 04 F5 BTST.B #5, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
2478: 27 02 BEQ loc_247C ; cycles=3/7 nt/t
|
||||||
|
247A: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_247C:
|
||||||
|
247C: 1E F5 B6 BSR loc_1A35 ; cycles=13
|
||||||
|
|
||||||
|
loc_247F:
|
||||||
|
247F: 19 RTS ; cycles=13
|
||||||
|
|
||||||
|
loc_2480:
|
||||||
|
2480: 15 F7 30 F7 BTST.B #7, @H'F730 ; refs ram_F730 in on_chip_ram; cycles=7
|
||||||
|
2484: 27 05 BEQ loc_248B ; cycles=3/7 nt/t
|
||||||
|
2486: 58 40 00 MOV:I.W #H'4000, R0 ; dataflow R0=H'4000; cycles=3
|
||||||
|
2489: 20 03 BRA loc_248E ; cycles=8
|
||||||
|
|
||||||
|
loc_248B:
|
||||||
|
248B: 58 00 20 MOV:I.W #H'0020, R0 ; dataflow R0=H'0020; cycles=3
|
||||||
|
|
||||||
|
loc_248E:
|
||||||
|
248E: 1D E9 06 90 MOV:G.W R0, @H'E906 ; refs mem_E906 in program_or_external; cycles=7
|
||||||
|
2492: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
2494: 5B 00 83 MOV:I.W #H'0083, R3 ; dataflow R3=H'0083; cycles=3
|
||||||
|
2497: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=6
|
||||||
|
249B: 27 08 BEQ loc_24A5 ; cycles=3/8 nt/t
|
||||||
|
249D: 15 F4 04 F5 BTST.B #5, @H'F404 ; refs mem_F404 in program_or_external; cycles=6
|
||||||
|
24A1: 27 02 BEQ loc_24A5 ; cycles=3/8 nt/t
|
||||||
|
24A3: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_24A5:
|
||||||
|
24A5: 1E 19 AC BSR loc_3E54 ; cycles=14
|
||||||
|
24A8: 19 RTS ; cycles=12
|
||||||
|
24A9: 15 F6 D0 F3 BTST.B #3, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=6
|
||||||
|
24AD: 27 38 BEQ loc_24E7 ; cycles=3/8 nt/t
|
||||||
|
24AF: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram; cycles=6
|
||||||
|
24B4: 22 31 BHI loc_24E7 ; cycles=3/7 nt/t
|
||||||
|
24B6: 1D E1 10 FF BTST.W #15, @H'E110 ; refs mem_E110 in program_or_external; cycles=7
|
||||||
|
24BA: 27 05 BEQ loc_24C1 ; cycles=3/7 nt/t
|
||||||
|
24BC: 1E 02 29 BSR loc_26E8 ; cycles=13
|
||||||
|
24BF: 20 26 BRA loc_24E7 ; cycles=8
|
||||||
|
|
||||||
|
loc_24C1:
|
||||||
|
24C1: 15 F7 30 F7 BTST.B #7, @H'F730 ; refs ram_F730 in on_chip_ram; cycles=6
|
||||||
|
24C5: 27 20 BEQ loc_24E7 ; cycles=3/8 nt/t
|
||||||
|
24C7: 1D E1 06 80 MOV:G.W @H'E106, R0 ; refs mem_E106 in program_or_external; cycles=6
|
||||||
|
24CB: A8 E0 BNOT.W #0, R0 ; cycles=3
|
||||||
|
24CD: 1D E9 06 90 MOV:G.W R0, @H'E906 ; refs mem_E906 in program_or_external; cycles=6
|
||||||
|
24D1: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
24D3: 5B 00 83 MOV:I.W #H'0083, R3 ; dataflow R3=H'0083; cycles=3
|
||||||
|
24D6: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
24DA: 27 08 BEQ loc_24E4 ; cycles=3/7 nt/t
|
||||||
|
24DC: 15 F4 04 F5 BTST.B #5, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
24E0: 27 02 BEQ loc_24E4 ; cycles=3/7 nt/t
|
||||||
|
24E2: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_24E4:
|
||||||
|
24E4: 1E 19 6D BSR loc_3E54 ; cycles=13
|
||||||
|
|
||||||
|
loc_24E7:
|
||||||
|
24E7: 19 RTS ; cycles=13
|
||||||
|
24E8: 15 F6 D0 F7 BTST.B #7, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
24EC: 27 3F BEQ loc_252D ; cycles=3/7 nt/t
|
||||||
|
24EE: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram; cycles=7
|
||||||
|
24F3: 22 38 BHI loc_252D ; cycles=3/8 nt/t
|
||||||
|
24F5: 1D E1 10 FE BTST.W #14, @H'E110 ; refs mem_E110 in program_or_external; cycles=6
|
||||||
|
24F9: 27 05 BEQ loc_2500 ; cycles=3/8 nt/t
|
||||||
|
24FB: 1E 01 EA BSR loc_26E8 ; cycles=14
|
||||||
|
24FE: 20 2D BRA loc_252D ; cycles=7
|
||||||
|
|
||||||
|
loc_2500:
|
||||||
|
2500: 15 F6 F6 13 CLR.B @H'F6F6 ; refs ram_F6F6 in on_chip_ram; cycles=9
|
||||||
|
2504: 1D E1 1E 80 MOV:G.W @H'E11E, R0 ; refs mem_E11E in program_or_external; cycles=7
|
||||||
|
2508: A8 FF BTST.W #15, R0 ; cycles=3
|
||||||
|
250A: 26 05 BNE loc_2511 ; cycles=3/7 nt/t
|
||||||
|
250C: 58 80 00 MOV:I.W #H'8000, R0 ; dataflow R0=H'8000; cycles=3
|
||||||
|
250F: 20 02 BRA loc_2513 ; cycles=8
|
||||||
|
|
||||||
|
loc_2511:
|
||||||
|
2511: A8 13 CLR.W R0 ; dataflow R0=H'0000; cycles=3
|
||||||
|
|
||||||
|
loc_2513:
|
||||||
|
2513: 1D E9 1E 90 MOV:G.W R0, @H'E91E ; refs mem_E91E in program_or_external; cycles=6
|
||||||
|
2517: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
2519: 5B 00 8F MOV:I.W #H'008F, R3 ; dataflow R3=H'008F; cycles=3
|
||||||
|
251C: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
2520: 27 08 BEQ loc_252A ; cycles=3/7 nt/t
|
||||||
|
2522: 15 F4 04 F4 BTST.B #4, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
2526: 27 02 BEQ loc_252A ; cycles=3/7 nt/t
|
||||||
|
2528: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_252A:
|
||||||
|
252A: 1E 19 27 BSR loc_3E54 ; cycles=13
|
||||||
|
|
||||||
|
loc_252D:
|
||||||
|
252D: 19 RTS ; cycles=13
|
||||||
|
252E: 15 F6 D0 F6 BTST.B #6, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
2532: 27 3F BEQ loc_2573 ; cycles=3/7 nt/t
|
||||||
|
2534: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram; cycles=7
|
||||||
|
2539: 22 38 BHI loc_2573 ; cycles=3/8 nt/t
|
||||||
|
253B: 1D E1 10 FE BTST.W #14, @H'E110 ; refs mem_E110 in program_or_external; cycles=6
|
||||||
|
253F: 27 05 BEQ loc_2546 ; cycles=3/8 nt/t
|
||||||
|
2541: 1E 01 A4 BSR loc_26E8 ; cycles=14
|
||||||
|
2544: 20 2D BRA loc_2573 ; cycles=7
|
||||||
|
|
||||||
|
loc_2546:
|
||||||
|
2546: 15 F6 F6 13 CLR.B @H'F6F6 ; refs ram_F6F6 in on_chip_ram; cycles=9
|
||||||
|
254A: 1D E1 1E 80 MOV:G.W @H'E11E, R0 ; refs mem_E11E in program_or_external; cycles=7
|
||||||
|
254E: A8 FD BTST.W #13, R0 ; cycles=3
|
||||||
|
2550: 26 05 BNE loc_2557 ; cycles=3/7 nt/t
|
||||||
|
2552: 58 20 00 MOV:I.W #H'2000, R0 ; dataflow R0=H'2000; cycles=3
|
||||||
|
2555: 20 02 BRA loc_2559 ; cycles=8
|
||||||
|
|
||||||
|
loc_2557:
|
||||||
|
2557: A8 13 CLR.W R0 ; dataflow R0=H'0000; cycles=3
|
||||||
|
|
||||||
|
loc_2559:
|
||||||
|
2559: 1D E9 1E 90 MOV:G.W R0, @H'E91E ; refs mem_E91E in program_or_external; cycles=6
|
||||||
|
255D: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
255F: 5B 00 8F MOV:I.W #H'008F, R3 ; dataflow R3=H'008F; cycles=3
|
||||||
|
2562: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
2566: 27 08 BEQ loc_2570 ; cycles=3/7 nt/t
|
||||||
|
2568: 15 F4 04 F4 BTST.B #4, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
256C: 27 02 BEQ loc_2570 ; cycles=3/7 nt/t
|
||||||
|
256E: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_2570:
|
||||||
|
2570: 1E 18 E1 BSR loc_3E54 ; cycles=13
|
||||||
|
|
||||||
|
loc_2573:
|
||||||
|
2573: 19 RTS ; cycles=13
|
||||||
|
2574: 15 F6 D0 F4 BTST.B #4, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
2578: 37 01 45 BEQ loc_26C0 ; cycles=3/7 nt/t
|
||||||
|
257B: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram; cycles=6
|
||||||
|
2580: 22 51 BHI loc_25D3 ; cycles=3/7 nt/t
|
||||||
|
2582: 1D E1 10 FE BTST.W #14, @H'E110 ; refs mem_E110 in program_or_external; cycles=7
|
||||||
|
2586: 27 05 BEQ loc_258D ; cycles=3/7 nt/t
|
||||||
|
2588: 1E 01 5D BSR loc_26E8 ; cycles=13
|
||||||
|
258B: 20 46 BRA loc_25D3 ; cycles=8
|
||||||
|
|
||||||
|
loc_258D:
|
||||||
|
258D: 1D E1 1E 80 MOV:G.W @H'E11E, R0 ; refs mem_E11E in program_or_external; cycles=6
|
||||||
|
2591: A8 FF BTST.W #15, R0 ; cycles=3
|
||||||
|
2593: 26 06 BNE loc_259B ; cycles=3/8 nt/t
|
||||||
|
2595: A8 FD BTST.W #13, R0 ; cycles=3
|
||||||
|
2597: 26 26 BNE loc_25BF ; cycles=3/8 nt/t
|
||||||
|
2599: 20 38 BRA loc_25D3 ; cycles=8
|
||||||
|
|
||||||
|
loc_259B:
|
||||||
|
259B: 15 F6 D0 F5 BTST.B #5, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=6
|
||||||
|
259F: 27 05 BEQ loc_25A6 ; cycles=3/8 nt/t
|
||||||
|
25A1: 1E 00 8F BSR loc_2633 ; cycles=14
|
||||||
|
25A4: 20 2D BRA loc_25D3 ; cycles=7
|
||||||
|
|
||||||
|
loc_25A6:
|
||||||
|
25A6: 5C 00 00 MOV:I.W #H'0000, R4 ; dataflow R4=H'0000; cycles=3
|
||||||
|
25A9: 5B 00 91 MOV:I.W #H'0091, R3 ; dataflow R3=H'0091; cycles=3
|
||||||
|
25AC: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
25B0: 27 08 BEQ loc_25BA ; cycles=3/7 nt/t
|
||||||
|
25B2: 15 F4 04 F4 BTST.B #4, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
25B6: 27 02 BEQ loc_25BA ; cycles=3/7 nt/t
|
||||||
|
25B8: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_25BA:
|
||||||
|
25BA: 1E F4 78 BSR loc_1A35 ; cycles=13
|
||||||
|
25BD: 20 14 BRA loc_25D3 ; cycles=8
|
||||||
|
|
||||||
|
loc_25BF:
|
||||||
|
25BF: 15 F6 D0 F5 BTST.B #5, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=6
|
||||||
|
25C3: 27 05 BEQ loc_25CA ; cycles=3/8 nt/t
|
||||||
|
25C5: 1E 00 FF BSR loc_26C7 ; cycles=14
|
||||||
|
25C8: 20 09 BRA loc_25D3 ; cycles=7
|
||||||
|
|
||||||
|
loc_25CA:
|
||||||
|
25CA: 15 F6 F6 06 C0 MOV:G.B #H'C0, @H'F6F6 ; refs ram_F6F6 in on_chip_ram; cycles=9
|
||||||
|
25CF: 1D F6 F4 13 CLR.W @H'F6F4 ; refs ram_F6F4 in on_chip_ram; cycles=8
|
||||||
|
|
||||||
|
loc_25D3:
|
||||||
|
25D3: 19 RTS ; cycles=13
|
||||||
|
25D4: 15 F6 D0 F5 BTST.B #5, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
25D8: 37 00 E5 BEQ loc_26C0 ; cycles=3/7 nt/t
|
||||||
|
25DB: 15 F7 31 04 02 CMP:G.B #H'02, @H'F731 ; refs ram_F731 in on_chip_ram; cycles=6
|
||||||
|
25E0: 22 50 BHI loc_2632 ; cycles=3/7 nt/t
|
||||||
|
25E2: 1D E1 10 FE BTST.W #14, @H'E110 ; refs mem_E110 in program_or_external; cycles=7
|
||||||
|
25E6: 27 05 BEQ loc_25ED ; cycles=3/7 nt/t
|
||||||
|
25E8: 1E 00 FD BSR loc_26E8 ; cycles=13
|
||||||
|
25EB: 20 45 BRA loc_2632 ; cycles=8
|
||||||
|
|
||||||
|
loc_25ED:
|
||||||
|
25ED: 1D E1 1E 80 MOV:G.W @H'E11E, R0 ; refs mem_E11E in program_or_external; cycles=6
|
||||||
|
25F1: A8 FF BTST.W #15, R0 ; cycles=3
|
||||||
|
25F3: 26 06 BNE loc_25FB ; cycles=3/8 nt/t
|
||||||
|
25F5: A8 FD BTST.W #13, R0 ; cycles=3
|
||||||
|
25F7: 26 25 BNE loc_261E ; cycles=3/8 nt/t
|
||||||
|
25F9: 20 37 BRA loc_2632 ; cycles=8
|
||||||
|
|
||||||
|
loc_25FB:
|
||||||
|
25FB: 15 F6 D0 F4 BTST.B #4, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=6
|
||||||
|
25FF: 27 04 BEQ loc_2605 ; cycles=3/8 nt/t
|
||||||
|
2601: 0E 30 BSR loc_2633 ; cycles=14
|
||||||
|
2603: 20 2D BRA loc_2632 ; cycles=8
|
||||||
|
|
||||||
|
loc_2605:
|
||||||
|
2605: 5C 00 01 MOV:I.W #H'0001, R4 ; dataflow R4=H'0001; cycles=3
|
||||||
|
2608: 5B 00 91 MOV:I.W #H'0091, R3 ; dataflow R3=H'0091; cycles=3
|
||||||
|
260B: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=6
|
||||||
|
260F: 27 08 BEQ loc_2619 ; cycles=3/8 nt/t
|
||||||
|
2611: 15 F4 04 F4 BTST.B #4, @H'F404 ; refs mem_F404 in program_or_external; cycles=6
|
||||||
|
2615: 27 02 BEQ loc_2619 ; cycles=3/8 nt/t
|
||||||
|
2617: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_2619:
|
||||||
|
2619: 1E F4 19 BSR loc_1A35 ; cycles=14
|
||||||
|
261C: 20 14 BRA loc_2632 ; cycles=7
|
||||||
|
|
||||||
|
loc_261E:
|
||||||
|
261E: 15 F6 D0 F4 BTST.B #4, @H'F6D0 ; refs ram_F6D0 in on_chip_ram; cycles=7
|
||||||
|
2622: 27 05 BEQ loc_2629 ; cycles=3/7 nt/t
|
||||||
|
2624: 1E 00 A0 BSR loc_26C7 ; cycles=13
|
||||||
|
2627: 20 09 BRA loc_2632 ; cycles=8
|
||||||
|
|
||||||
|
loc_2629:
|
||||||
|
2629: 15 F6 F6 06 80 MOV:G.B #H'80, @H'F6F6 ; refs ram_F6F6 in on_chip_ram; cycles=9
|
||||||
|
262E: 1D F6 F4 13 CLR.W @H'F6F4 ; refs ram_F6F4 in on_chip_ram; cycles=9
|
||||||
|
|
||||||
|
loc_2632:
|
||||||
|
2632: 19 RTS ; cycles=12
|
||||||
|
|
||||||
|
loc_2633:
|
||||||
|
2633: 1D E9 22 07 80 00 MOV:G.W #H'8000, @H'E922 ; refs mem_E922 in program_or_external; cycles=9
|
||||||
|
2639: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
263B: 5B 00 91 MOV:I.W #H'0091, R3 ; dataflow R3=H'0091; cycles=3
|
||||||
|
263E: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
2642: 27 08 BEQ loc_264C ; cycles=3/7 nt/t
|
||||||
|
2644: 15 F4 04 F4 BTST.B #4, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
2648: 27 02 BEQ loc_264C ; cycles=3/7 nt/t
|
||||||
|
264A: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_264C:
|
||||||
|
264C: 1E 18 05 BSR loc_3E54 ; cycles=13
|
||||||
|
264F: 19 RTS ; cycles=13
|
||||||
16353
build/rom_f109_handlers.json
Normal file
16353
build/rom_f109_handlers.json
Normal file
File diff suppressed because it is too large
Load Diff
107
build/rom_f109_tail.asm
Normal file
107
build/rom_f109_tail.asm
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
; H8/536 ROM disassembly
|
||||||
|
; input: ROM\M27C512@DIP28_1.BIN
|
||||||
|
; bytes: 65536
|
||||||
|
; vector mode: min
|
||||||
|
; analysis: recursive trace from vectors
|
||||||
|
;
|
||||||
|
; Notes from the manual:
|
||||||
|
; - H8/536 uses the H8/500 CPU instruction set.
|
||||||
|
; - In minimum mode the reset vector at H'0000-H'0001 is a 16-bit PC.
|
||||||
|
; - The register field is H'FE80-H'FFFF; names below come from appendix B.
|
||||||
|
; - @aa:8 short absolute operands use BR as the upper address byte.
|
||||||
|
; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.
|
||||||
|
; - LCD inference treats E-clock H'F200/H'F201 accesses as status/control and data candidates.
|
||||||
|
; - Pass --clock-hz to convert SCI BRR settings into numeric baud rates.
|
||||||
|
; - Cycle counts use Appendix A tables A-7/A-8 for on-chip access with no external wait states.
|
||||||
|
|
||||||
|
; Memory Map
|
||||||
|
; H'0000-H'009F exception_vectors vectors
|
||||||
|
; H'00A0-H'00FF dtc_vectors dtc_vectors
|
||||||
|
; H'0100-H'F67F program_or_external program
|
||||||
|
; H'F680-H'FE7F on_chip_ram ram
|
||||||
|
; H'FE80-H'FFFF register_field registers
|
||||||
|
|
||||||
|
; Vectors
|
||||||
|
; H'0000 reset -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0004 invalid_instruction -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0006 zero_divide -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0008 trap_vs -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0010 address_error -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0012 trace -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0016 nmi -> vec_nmi_4393 (H'4393)
|
||||||
|
; H'0020 trapa_0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0022 trapa_1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0024 trapa_2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0026 trapa_3 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0028 trapa_4 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002A trapa_5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002C trapa_6 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'002E trapa_7 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0030 trapa_8 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0032 trapa_9 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0034 trapa_a -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0036 trapa_b -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0038 trapa_c -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003A trapa_d -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003C trapa_e -> vec_reset_1000 (H'1000)
|
||||||
|
; H'003E trapa_f -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0040 irq0 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0042 interval_timer -> vec_interval_timer_BFC4 (H'BFC4)
|
||||||
|
; H'0048 irq1 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0050 irq2 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0052 irq3 -> vec_irq3_3C30 (H'3C30)
|
||||||
|
; H'0058 irq4 -> vec_irq4_3AC7 (H'3AC7)
|
||||||
|
; H'005A irq5 -> vec_reset_1000 (H'1000)
|
||||||
|
; H'0062 frt1_ocia -> vec_frt1_ocia_BEEA (H'BEEA)
|
||||||
|
; H'006A frt2_ocia -> vec_frt2_ocia_BF23 (H'BF23)
|
||||||
|
; H'0080 sci1_eri -> vec_sci1_eri_BB57 (H'BB57)
|
||||||
|
; H'0082 sci1_rxi -> vec_sci1_rxi_BB67 (H'BB67)
|
||||||
|
; H'0084 sci1_txi -> vec_sci1_txi_BA84 (H'BA84)
|
||||||
|
; H'0090 ad_adi -> vec_ad_adi_3D99 (H'3D99)
|
||||||
|
|
||||||
|
; Symbols
|
||||||
|
; mem_E924 H'E924 program_or_external memory r=0 w=1 width=word
|
||||||
|
; mem_F404 H'F404 program_or_external memory r=1 w=0 width=byte
|
||||||
|
; ram_F6F6 H'F6F6 on_chip_ram ram r=0 w=2 width=byte
|
||||||
|
; ram_F732 H'F732 on_chip_ram ram r=1 w=1 width=word
|
||||||
|
; ram_F734 H'F734 on_chip_ram ram r=0 w=1 width=word
|
||||||
|
; ram_F791 H'F791 on_chip_ram ram r=1 w=0 width=byte
|
||||||
|
; ram_FB02 H'FB02 on_chip_ram ram r=0 w=1 width=byte
|
||||||
|
; ram_FB03 H'FB03 on_chip_ram ram r=1 w=1 width=byte
|
||||||
|
|
||||||
|
; Board Profile
|
||||||
|
; Board trace ties the H8/536 SCI1 pins to a MAX202 RS232 transceiver.
|
||||||
|
; H8 pin 66 P95/TXD (TXD) -> MAX202 pin 11
|
||||||
|
; H8 pin 67 P96/RXD (RXD) -> MAX202 pin 12
|
||||||
|
; SCI2 pin routing is disabled by SYSCR2.P9SCI2E=0 in the observed setup.
|
||||||
|
|
||||||
|
; LCD/Text Scan
|
||||||
|
; search 'CONNECT': not literal, hits=0
|
||||||
|
; LCD text candidates
|
||||||
|
; ... 1 more LCD text candidates
|
||||||
|
|
||||||
|
26C0: 15 F6 F6 13 CLR.B @H'F6F6 ; refs ram_F6F6 in on_chip_ram; cycles=9
|
||||||
|
26C4: 30 FF 0C BRA loc_25D3 ; cycles=7
|
||||||
|
26C7: 15 F6 F6 13 CLR.B @H'F6F6 ; refs ram_F6F6 in on_chip_ram; cycles=8
|
||||||
|
26CB: 1D E9 24 07 FF 80 MOV:G.W #H'FF80, @H'E924 ; refs mem_E924 in program_or_external; cycles=9
|
||||||
|
26D1: 52 80 MOV:E.B #H'80, R2 ; dataflow R2=H'80; cycles=2
|
||||||
|
26D3: 5B 00 92 MOV:I.W #H'0092, R3 ; dataflow R3=H'0092; cycles=3
|
||||||
|
26D6: 15 F7 91 F7 BTST.B #7, @H'F791 ; refs ram_F791 in on_chip_ram; cycles=7
|
||||||
|
26DA: 27 08 BEQ loc_26E4 ; cycles=3/7 nt/t
|
||||||
|
26DC: 15 F4 04 F4 BTST.B #4, @H'F404 ; refs mem_F404 in program_or_external; cycles=7
|
||||||
|
26E0: 27 02 BEQ loc_26E4 ; cycles=3/7 nt/t
|
||||||
|
26E2: AB CE BSET.W #14, R3 ; cycles=3
|
||||||
|
|
||||||
|
loc_26E4:
|
||||||
|
26E4: 1E 17 6D BSR loc_3E54 ; cycles=13
|
||||||
|
26E7: 19 RTS ; cycles=13
|
||||||
|
26E8: 15 FB 03 C7 BSET.B #7, @H'FB03 ; refs ram_FB03 in on_chip_ram; cycles=9
|
||||||
|
26EC: 26 08 BNE loc_26F6 ; cycles=3/7 nt/t
|
||||||
|
26EE: 1D F7 32 81 MOV:G.W @H'F732, R1 ; refs ram_F732 in on_chip_ram; cycles=7
|
||||||
|
26F2: 1D F7 34 91 MOV:G.W R1, @H'F734 ; refs ram_F734 in on_chip_ram; cycles=7
|
||||||
|
|
||||||
|
loc_26F6:
|
||||||
|
26F6: 1D F7 32 07 1C 01 MOV:G.W #H'1C01, @H'F732 ; refs ram_F732 in on_chip_ram; cycles=11
|
||||||
|
26FC: 15 FB 02 06 14 MOV:G.B #H'14, @H'FB02 ; refs ram_FB02 in on_chip_ram; cycles=9
|
||||||
|
2701: 1E 21 F6 BSR loc_48FA ; cycles=14
|
||||||
|
2704: 19 RTS ; cycles=12
|
||||||
2076
build/rom_f109_tail.json
Normal file
2076
build/rom_f109_tail.json
Normal file
File diff suppressed because it is too large
Load Diff
4263
build/rom_others_menu.asm
Normal file
4263
build/rom_others_menu.asm
Normal file
File diff suppressed because it is too large
Load Diff
219643
build/rom_others_menu.json
Normal file
219643
build/rom_others_menu.json
Normal file
File diff suppressed because it is too large
Load Diff
4219
build/rom_others_menu_deep.asm
Normal file
4219
build/rom_others_menu_deep.asm
Normal file
File diff suppressed because it is too large
Load Diff
220100
build/rom_others_menu_deep.json
Normal file
220100
build/rom_others_menu_deep.json
Normal file
File diff suppressed because it is too large
Load Diff
4295
build/rom_others_menu_gate.asm
Normal file
4295
build/rom_others_menu_gate.asm
Normal file
File diff suppressed because it is too large
Load Diff
221410
build/rom_others_menu_gate.json
Normal file
221410
build/rom_others_menu_gate.json
Normal file
File diff suppressed because it is too large
Load Diff
4642
build/rom_others_page1.asm
Normal file
4642
build/rom_others_page1.asm
Normal file
File diff suppressed because it is too large
Load Diff
239015
build/rom_others_page1.json
Normal file
239015
build/rom_others_page1.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,7 @@
|
|||||||
typedef uint8_t u8;
|
typedef uint8_t u8;
|
||||||
typedef uint16_t u16;
|
typedef uint16_t u16;
|
||||||
|
|
||||||
|
#define BIT(n) (1u << (n))
|
||||||
extern volatile u8 MEM8[0x10000];
|
extern volatile u8 MEM8[0x10000];
|
||||||
|
|
||||||
#define SCI1_SCR MEM8[0xFEDAu]
|
#define SCI1_SCR MEM8[0xFEDAu]
|
||||||
@@ -193,6 +194,36 @@ extern volatile u8 MEM8[0x10000];
|
|||||||
* evidence: H'1A09, H'1A71, H'3F90, H'407F, H'BB35, H'BC79, H'BC99, H'BD1E
|
* evidence: H'1A09, H'1A71, H'3F90, H'407F, H'BB35, H'BC79, H'BC99, H'BD1E
|
||||||
* - flag_table_candidate at H'EC00 (bit_flags); observed write
|
* - flag_table_candidate at H'EC00 (bit_flags); observed write
|
||||||
* evidence: H'4088, H'BC82, H'BC9D, H'BD22, H'BD39, H'BDE9
|
* evidence: H'4088, H'BC82, H'BC9D, H'BD22, H'BD39, H'BDE9
|
||||||
|
* panel selector semantics:
|
||||||
|
* - 0x0013 slave_and_iris_mblack_link_lamps: Selector 0x0013 is a two-bit lamp/status word. ROM dispatch H'2E06 reads current table word H'E826 and fans bit 15 and bit 14 into panel latch RAM.
|
||||||
|
* current word: H'E826; dispatch: H'2E06
|
||||||
|
* 0x8000 -> SLAVE lamp: sets F791.6 and F713.4; RAM F791.6, F713.4
|
||||||
|
* 0x4000 -> IRIS/M.BLACK LINK lamp: sets F791.5 and F716.7; RAM F791.5, F716.7
|
||||||
|
* observed values: 0x8000 SLAVE lamp on; 0x4000 IRIS/M.BLACK LINK lamp on; 0x0000 SLAVE and IRIS/M.BLACK LINK latch bits clear through H'2E06
|
||||||
|
* state machine: iris_mblack_link_closed_loop_state_candidate: Bench-proven closed loop: the RCP reports local IRIS/M.BLACK LINK intent, the CCU ACKs selector 0x0013, then the CCU mirrors the accepted selector state back with command 0. The mirrored state controls the next toggle direction.
|
||||||
|
* frames: active report 00 00 13 40 00 09; clear report 00 00 13 00 00 49; ACK 05 00 13 00 00 4C; mirror active 00 00 13 40 00 09; mirror clear 00 00 13 00 00 49
|
||||||
|
* local trigger candidates: provisional_iris_mblack_link_button_toggle_report F006.7 / F6DB.7: When F6DB.7 is asserted and F731 <= 3, the ROM toggles current-table bit 14 at H'E826 based on F791.5, then queues selector 0x0013 through loc_3E54.; H'1FE8/H'1FFB: Adjacent local helpers set or clear current-table bit 15 at H'E826 and queue selector 0x0013.
|
||||||
|
* evidence: bench: 00 00 13 80 00 C9 lights far-right SLAVE lamp, bench: 00 00 13 40 00 09 lights IRIS/M.BLACK LINK lamp, ROM: H'2E06-H'2E32 tests H'E826 bits 15/14 and sets/clears F791/F713/F716 latch bits
|
||||||
|
* - 0x0015 call_and_red_tally_lamp_lane: Bench-visible CALL lamp and red tally lane; local CALL handler mirrors F6DB.5 into E800[0x0015].15.
|
||||||
|
* current word: H'E82A; dispatch: handler unknown
|
||||||
|
* observed values: 0x8000 CALL lamp and red tally on; 0x0000 CALL inactive/clear report
|
||||||
|
* evidence: bench: 00 00 15 80 00 CF lights CALL and red tally, ROM: H'20A1-H'20BA reads F6DB.5, writes H'E82A, and queues selector 0x0015
|
||||||
|
* - 0x0017 bars_lamp_lane: Bench-visible BARS lamp/latch lane; low writes do not reliably clear the visible latch.
|
||||||
|
* current word: H'E82E; dispatch: handler unknown
|
||||||
|
* observed values: 0x8000 BARS lamp on; 0x4000 BARS lamp/latch on; 0x0000 BARS low write; visible latch may remain
|
||||||
|
* evidence: bench: 00 00 17 80 00 CD lights BARS, bench: 00 00 17 40 00 0D also lights the BARS latch in neighbor sweep, ROM: H'1EDE can queue selector 0x0017 from F6D4.2
|
||||||
|
* - 0x001A monitor_selector_lamps: Bench-visible MONITOR selector cluster found from ROM-derived button-output sweep.
|
||||||
|
* current word: H'E834; dispatch: handler unknown
|
||||||
|
* observed values: 0x0808 MONITOR ENC lamp; 0x2020 MONITOR B lamp; 0x4040 MONITOR G lamp; 0x8080 MONITOR R lamp
|
||||||
|
* evidence: bench: 00 00 1A 08 08 40 lights MONITOR ENC, bench: 00 00 1A 20 20 40 lights MONITOR B, bench: 00 00 1A 40 40 40 lights MONITOR G, bench: 00 00 1A 80 80 40 lights MONITOR R, ROM: H'1CB2-H'1D56 writes packed values to H'E834 and queues selector 0x001A
|
||||||
|
* - 0x0024 lcd_selector_button_lamp: Bench-visible LCD selector-button lamp lane.
|
||||||
|
* current word: H'E848; dispatch: dispatch unknown
|
||||||
|
* observed values: 0x8000 LCD selector-button lamp visible; 0x0000 lamp remained visible at 0.5 s in isolation run
|
||||||
|
* - 0x006B standard_lamp_lane: Bench-visible STANDARD lamp lane found from ROM-derived F6D4.6 handler candidate.
|
||||||
|
* current word: H'E8D6; dispatch: handler unknown
|
||||||
|
* observed values: 0x8000 STANDARD lamp on
|
||||||
|
* evidence: bench: 00 00 6B 80 00 B1 lights STANDARD, ROM: H'2048 can write H'E8D6=0x8000 and queue selector 0x006B from F6D4.6
|
||||||
|
* - ... 5 more panel selector annotations
|
||||||
* state variable candidates:
|
* state variable candidates:
|
||||||
* - event_queue_read_cursor_candidate H'F9B4: reads 1, writes 2; bits 5
|
* - event_queue_read_cursor_candidate H'F9B4: reads 1, writes 2; bits 5
|
||||||
* evidence: H'BE78, H'BE95, H'BE99
|
* evidence: H'BE78, H'BE95, H'BE99
|
||||||
@@ -357,12 +388,197 @@ void frt2_ocia_candidate_tick_isr(void)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sci1_candidate_panel_selector_annotation(u16 logical_index, u16 value)
|
||||||
|
{
|
||||||
|
/* Known bench/ROM selector labels. This helper is commentary for the decompile. */
|
||||||
|
switch (logical_index) {
|
||||||
|
case 0x0013u:
|
||||||
|
/* 0x0013 slave_and_iris_mblack_link_lamps; current word H'E826; H'2E06. */
|
||||||
|
if ((value & 0x8000u) != 0u) {
|
||||||
|
/* SLAVE lamp: sets F791.6 and F713.4. */
|
||||||
|
} else {
|
||||||
|
/* SLAVE lamp: clears F791.6 and F713.4. */
|
||||||
|
}
|
||||||
|
if ((value & 0x4000u) != 0u) {
|
||||||
|
/* IRIS/M.BLACK LINK lamp: sets F791.5 and F716.7. */
|
||||||
|
} else {
|
||||||
|
/* IRIS/M.BLACK LINK lamp: clears F791.5 and F716.7. */
|
||||||
|
}
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* SLAVE lamp on. */
|
||||||
|
}
|
||||||
|
if (value == 0x4000u) {
|
||||||
|
/* IRIS/M.BLACK LINK lamp on. */
|
||||||
|
}
|
||||||
|
if (value == 0x0000u) {
|
||||||
|
/* SLAVE and IRIS/M.BLACK LINK latch bits clear through H'2E06. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x0015u:
|
||||||
|
/* 0x0015 call_and_red_tally_lamp_lane; current word H'E82A; handler unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* CALL lamp and red tally on. */
|
||||||
|
}
|
||||||
|
if (value == 0x0000u) {
|
||||||
|
/* CALL inactive/clear report. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x0017u:
|
||||||
|
/* 0x0017 bars_lamp_lane; current word H'E82E; handler unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* BARS lamp on. */
|
||||||
|
}
|
||||||
|
if (value == 0x4000u) {
|
||||||
|
/* BARS lamp/latch on. */
|
||||||
|
}
|
||||||
|
if (value == 0x0000u) {
|
||||||
|
/* BARS low write; visible latch may remain. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x001Au:
|
||||||
|
/* 0x001A monitor_selector_lamps; current word H'E834; handler unknown. */
|
||||||
|
if (value == 0x0808u) {
|
||||||
|
/* MONITOR ENC lamp. */
|
||||||
|
}
|
||||||
|
if (value == 0x2020u) {
|
||||||
|
/* MONITOR B lamp. */
|
||||||
|
}
|
||||||
|
if (value == 0x4040u) {
|
||||||
|
/* MONITOR G lamp. */
|
||||||
|
}
|
||||||
|
if (value == 0x8080u) {
|
||||||
|
/* MONITOR R lamp. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x0024u:
|
||||||
|
/* 0x0024 lcd_selector_button_lamp; current word H'E848; dispatch unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* LCD selector-button lamp visible. */
|
||||||
|
}
|
||||||
|
if (value == 0x0000u) {
|
||||||
|
/* lamp remained visible at 0.5 s in isolation run. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x006Bu:
|
||||||
|
/* 0x006B standard_lamp_lane; current word H'E8D6; handler unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* STANDARD lamp on. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x0082u:
|
||||||
|
/* 0x0082 iris_readout_lane; current word H'E904; dispatch unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* IRIS display OP. */
|
||||||
|
}
|
||||||
|
if (value == 0x4000u) {
|
||||||
|
/* IRIS display 1.4. */
|
||||||
|
}
|
||||||
|
if (value == 0x0000u) {
|
||||||
|
/* IRIS display blank. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x0083u:
|
||||||
|
/* 0x0083 combined_iris_shutter_master_gain_status_lane; current word H'E906; dispatch unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* IRIS AUTO, SHUTTER OFF, MASTER GAIN -3. */
|
||||||
|
}
|
||||||
|
if (value == 0x4000u) {
|
||||||
|
/* IRIS AUTO, SHUTTER OFF, MASTER GAIN 0. */
|
||||||
|
}
|
||||||
|
if (value == 0x2000u) {
|
||||||
|
/* IRIS AUTO, SHUTTER OFF, MASTER GAIN 3. */
|
||||||
|
}
|
||||||
|
if (value == 0x0004u) {
|
||||||
|
/* IRIS AUTO, SHUTTER OFF, MASTER GAIN HP. */
|
||||||
|
}
|
||||||
|
if (value == 0x0000u) {
|
||||||
|
/* same visible state remained at 0.5 s. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x008Fu:
|
||||||
|
/* 0x008F shutter_display_status_lane; current word H'E91E; dispatch unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* IRIS AUTO plus shutter value beginning with 1. */
|
||||||
|
}
|
||||||
|
if (value == 0x2000u) {
|
||||||
|
/* IRIS AUTO plus shutter 00.0. */
|
||||||
|
}
|
||||||
|
if (value == 0x0800u) {
|
||||||
|
/* IRIS AUTO plus shutter EVS. */
|
||||||
|
}
|
||||||
|
if (value == 0x1000u) {
|
||||||
|
/* IRIS AUTO plus shutter OFF. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x0093u:
|
||||||
|
/* 0x0093 white_balance_black_flare_mode_lane; current word H'E926; dispatch unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* BLACK/FLARE MANUAL plus white-balance PRESET. */
|
||||||
|
}
|
||||||
|
if (value == 0x4000u) {
|
||||||
|
/* BLACK/FLARE MANUAL plus white-balance AUTO. */
|
||||||
|
}
|
||||||
|
if (value == 0x2000u) {
|
||||||
|
/* BLACK/FLARE MANUAL plus white-balance MANUAL. */
|
||||||
|
}
|
||||||
|
if (value == 0x1020u) {
|
||||||
|
/* BLACK/FLARE MANUAL plus white-balance MANUAL. */
|
||||||
|
}
|
||||||
|
if (value == 0x4040u) {
|
||||||
|
/* BLACK/FLARE AUTO plus white-balance AUTO. */
|
||||||
|
}
|
||||||
|
if (value == 0x8040u) {
|
||||||
|
/* BLACK/FLARE AUTO plus white-balance PRESET. */
|
||||||
|
}
|
||||||
|
if (value == 0x0020u) {
|
||||||
|
/* BLACK/FLARE MANUAL plus white-balance MANUAL. */
|
||||||
|
}
|
||||||
|
if (value == 0x0040u) {
|
||||||
|
/* BLACK/FLARE AUTO plus white-balance MANUAL. */
|
||||||
|
}
|
||||||
|
if (value == 0x0000u) {
|
||||||
|
/* BLACK/FLARE MANUAL plus white-balance MANUAL. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x0110u:
|
||||||
|
/* 0x0110 knee_auto_lamp_or_page_status_lane; current word H'EA20; dispatch unknown. */
|
||||||
|
if (value == 0x8000u) {
|
||||||
|
/* KNEE AUTO lamp/status on. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void provisional_iris_mblack_link_button_toggle_report(void)
|
||||||
|
{
|
||||||
|
/* Provisional name for ROM H'200E: When F6DB.7 is asserted and F731 <= 3, the ROM toggles current-table bit 14 at H'E826 based on F791.5, then queues selector 0x0013 through loc_3E54. */
|
||||||
|
/* Source F006.7 / F6DB.7; gate F731 <= 3; current state F791.5. */
|
||||||
|
if ((MEM8[0xF6DBu] & BIT(7)) == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MEM8[0xF731u] > 3u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((MEM8[0xF791u] & BIT(5)) == 0u) {
|
||||||
|
/* Requests selector 0x0013=0x4000: 00 00 13 40 00 09. */
|
||||||
|
/* CCU should ACK 05 00 13 00 00 4C, then mirror 00 00 13 40 00 09. */
|
||||||
|
} else {
|
||||||
|
/* Requests selector 0x0013=0x0000: 00 00 13 00 00 49. */
|
||||||
|
/* CCU should ACK 05 00 13 00 00 4C, then mirror 00 00 13 00 00 49. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void sci1_process_candidate_protocol_command(void)
|
void sci1_process_candidate_protocol_command(void)
|
||||||
{
|
{
|
||||||
u8 command = sci1_rx_candidate_command();
|
u8 command = sci1_rx_candidate_command();
|
||||||
u16 logical_index = sci1_rx_candidate_logical_index();
|
u16 logical_index = sci1_rx_candidate_logical_index();
|
||||||
u16 value = sci1_rx_candidate_value();
|
u16 value = sci1_rx_candidate_value();
|
||||||
|
|
||||||
|
sci1_candidate_panel_selector_annotation(logical_index, value);
|
||||||
|
|
||||||
bool session_active = MEM8[0xFAA2u] != 0u;
|
bool session_active = MEM8[0xFAA2u] != 0u;
|
||||||
|
|
||||||
if (!session_active) {
|
if (!session_active) {
|
||||||
|
|||||||
@@ -18,32 +18,32 @@ primary_value_table_candidate H'E000-H'E3FF (negative H'2000; direct H'F900-H'F9
|
|||||||
accesses=31 reads=21 writes=10 dynamic=11
|
accesses=31 reads=21 writes=10 dynamic=11
|
||||||
static offsets: H'0000, H'0004, H'0006, H'0046, H'0080, H'0102, H'0124, H'0126, H'014E, H'016E, H'0172, H'01EC, H'0220
|
static offsets: H'0000, H'0004, H'0006, H'0046, H'0080, H'0102, H'0124, H'0126, H'014E, H'016E, H'0172, H'01EC, H'0220
|
||||||
functions: loc_BBAB:5, loc_2650:3, loc_4096:3, loc_1795:2, loc_19DB:2, loc_1A35:2, loc_48FA:2, vec_ad_adi_3D99:2, <no function>:1, loc_1705:1, loc_174D:1, loc_17C9:1
|
functions: loc_BBAB:5, loc_2650:3, loc_4096:3, loc_1795:2, loc_19DB:2, loc_1A35:2, loc_48FA:2, vec_ad_adi_3D99:2, <no function>:1, loc_1705:1, loc_174D:1, loc_17C9:1
|
||||||
- H'170C read offset H'014E -> H'E14E; loc_1705; BTST.W #15, @H'E14E
|
- H'170C read offset H'014E selector 0x0A7 -> H'E14E; loc_1705; BTST.W #15, @H'E14E
|
||||||
- H'175A read offset H'016E -> H'E16E; loc_174D; BTST.W #13, @H'E16E
|
- H'175A read offset H'016E selector 0x0B7 -> H'E16E; loc_174D; BTST.W #13, @H'E16E
|
||||||
- H'179C read offset H'0172 -> H'E172; loc_1795; BTST.W #13, @H'E172
|
- H'179C read offset H'0172 selector 0x0B9 -> H'E172; loc_1795; BTST.W #13, @H'E172
|
||||||
- H'17A7 read offset H'0220 -> H'E220; loc_1795; BTST.W #15, @H'E220
|
- H'17A7 read offset H'0220 selector 0x110 -> H'E220; loc_1795; BTST.W #15, @H'E220
|
||||||
- H'17D0 read offset H'0126 -> H'E126; loc_17C9; BTST.W #12, @H'E126
|
- H'17D0 read offset H'0126 selector 0x093 -> H'E126; loc_17C9; BTST.W #12, @H'E126
|
||||||
- H'1802 read offset H'0126 -> H'E126; loc_17FB; BTST.W #12, @H'E126
|
- H'1802 read offset H'0126 selector 0x093 -> H'E126; loc_17FB; BTST.W #12, @H'E126
|
||||||
- H'183A read offset H'0126 -> H'E126; loc_182D; BTST.W #5, @H'E126
|
- H'183A read offset H'0126 selector 0x093 -> H'E126; loc_182D; BTST.W #5, @H'E126
|
||||||
- H'189E read offset H'0126 -> H'E126; loc_1891; BTST.W #5, @H'E126
|
- H'189E read offset H'0126 selector 0x093 -> H'E126; loc_1891; BTST.W #5, @H'E126
|
||||||
- H'18F4 read offset H'0126 -> H'E126; loc_18E7; BTST.W #5, @H'E126
|
- H'18F4 read offset H'0126 selector 0x093 -> H'E126; loc_18E7; BTST.W #5, @H'E126
|
||||||
- H'19E3 read index dynamic via R3 operand @(-H'2000,R3); loc_19DB; MOV:G.W @(-H'2000,R3), R0
|
- H'19E3 read index dynamic via R3 operand @(-H'2000,R3); loc_19DB; MOV:G.W @(-H'2000,R3), R0
|
||||||
- H'1A03 read index dynamic via R3 operand @(-H'2000,R3); loc_19DB; CMP:G.W @(-H'2000,R3), R1
|
- H'1A03 read index dynamic via R3 operand @(-H'2000,R3); loc_19DB; CMP:G.W @(-H'2000,R3), R1
|
||||||
- H'1A3D read index dynamic via R3 operand @(-H'2000,R3); loc_1A35; MOV:G.W @(-H'2000,R3), R0
|
- H'1A3D read index dynamic via R3 operand @(-H'2000,R3); loc_1A35; MOV:G.W @(-H'2000,R3), R0
|
||||||
- H'1A6B read index dynamic via R3 operand @(-H'2000,R3); loc_1A35; CMP:G.W @(-H'2000,R3), R0
|
- H'1A6B read index dynamic via R3 operand @(-H'2000,R3); loc_1A35; CMP:G.W @(-H'2000,R3), R0
|
||||||
- H'2657 read offset H'0124 -> H'E124; loc_2650; MOV:G.W @H'E124, R0
|
- H'2657 read offset H'0124 selector 0x092 -> H'E124; loc_2650; MOV:G.W @H'E124, R0
|
||||||
- H'266F read offset H'0004 -> H'E004; loc_2650; BTST.W #13, @H'E004
|
- H'266F read offset H'0004 selector 0x002 -> H'E004; loc_2650; BTST.W #13, @H'E004
|
||||||
- H'268B read offset H'0124 -> H'E124; loc_2650; CMP:G.W @H'E124, R0
|
- H'268B read offset H'0124 selector 0x092 -> H'E124; loc_2650; CMP:G.W @H'E124, R0
|
||||||
- H'3DDA read offset H'0102 -> H'E102; vec_ad_adi_3D99; MOV:G.W @H'E102, R0
|
- H'3DDA read offset H'0102 selector 0x081 -> H'E102; vec_ad_adi_3D99; MOV:G.W @H'E102, R0
|
||||||
- H'3DFA read offset H'0102 -> H'E102; vec_ad_adi_3D99; CMP:G.W @H'E102, R1
|
- H'3DFA read offset H'0102 selector 0x081 -> H'E102; vec_ad_adi_3D99; CMP:G.W @H'E102, R1
|
||||||
- H'3F8C write index dynamic via R0 operand @(-H'2000,R0); <no function>; CLR.W @(-H'2000,R0)
|
- H'3F8C write index dynamic via R0 operand @(-H'2000,R0); <no function>; CLR.W @(-H'2000,R0)
|
||||||
- H'402C write offset H'0046 -> H'E046; loc_400C; CLR.W @H'E046
|
- H'402C write offset H'0046 selector 0x023 -> H'E046; loc_400C; CLR.W @H'E046
|
||||||
- H'4077 write index dynamic via R0 operand @(-H'2000,R0); loc_4075; CLR.W @(-H'2000,R0)
|
- H'4077 write index dynamic via R0 operand @(-H'2000,R0); loc_4075; CLR.W @(-H'2000,R0)
|
||||||
- H'4096 write offset H'0000 -> H'E000; loc_4096; MOV:G.W #H'0080, @H'E000
|
- H'4096 write offset H'0000 selector 0x000 -> H'E000; loc_4096; MOV:G.W #H'0080, @H'E000
|
||||||
- H'409C write offset H'0006 -> H'E006; loc_4096; MOV:G.W #H'8000, @H'E006
|
- H'409C write offset H'0006 selector 0x003 -> H'E006; loc_4096; MOV:G.W #H'8000, @H'E006
|
||||||
- H'40A2 write offset H'0080 -> H'E080; loc_4096; MOV:G.W #H'FFFF, @H'E080
|
- H'40A2 write offset H'0080 selector 0x040 -> H'E080; loc_4096; MOV:G.W #H'FFFF, @H'E080
|
||||||
- H'490F read offset H'01EC -> H'E1EC; loc_48FA; BTST.W #13, @H'E1EC
|
- H'490F read offset H'01EC selector 0x0F6 -> H'E1EC; loc_48FA; BTST.W #13, @H'E1EC
|
||||||
- H'4915 read offset H'01EC -> H'E1EC; loc_48FA; MOV:G.W @H'E1EC, R0
|
- H'4915 read offset H'01EC selector 0x0F6 -> H'E1EC; loc_48FA; MOV:G.W @H'E1EC, R0
|
||||||
- H'BC75 write index dynamic via R4 operand @(-H'2000,R4); loc_BBAB; MOV:G.W R0, @(-H'2000,R4)
|
- H'BC75 write index dynamic via R4 operand @(-H'2000,R4); loc_BBAB; MOV:G.W R0, @(-H'2000,R4)
|
||||||
- H'BC95 write index dynamic via R4 operand @(-H'2000,R4); loc_BBAB; MOV:G.W R0, @(-H'2000,R4)
|
- H'BC95 write index dynamic via R4 operand @(-H'2000,R4); loc_BBAB; MOV:G.W R0, @(-H'2000,R4)
|
||||||
- H'BCEC read index dynamic via R4 operand @(-H'2000,R4); loc_BBAB; MOV:G.W @(-H'2000,R4), R0
|
- H'BCEC read index dynamic via R4 operand @(-H'2000,R4); loc_BBAB; MOV:G.W @(-H'2000,R4), R0
|
||||||
@@ -66,16 +66,16 @@ current_value_table_candidate H'E800-H'EBFF (negative H'1800; direct H'F920-H'F9
|
|||||||
accesses=14 reads=1 writes=13 dynamic=8
|
accesses=14 reads=1 writes=13 dynamic=8
|
||||||
static offsets: H'0000, H'0006, H'0080, H'0102, H'0124, H'01EC
|
static offsets: H'0000, H'0006, H'0080, H'0102, H'0124, H'01EC
|
||||||
functions: loc_4096:3, loc_BBAB:3, <no function>:1, loc_15E0:1, loc_19DB:1, loc_1A35:1, loc_2650:1, loc_4075:1, loc_48FA:1, loc_BAF2:1
|
functions: loc_4096:3, loc_BBAB:3, <no function>:1, loc_15E0:1, loc_19DB:1, loc_1A35:1, loc_2650:1, loc_4075:1, loc_48FA:1, loc_BAF2:1
|
||||||
- H'15ED write offset H'0102 -> H'E902; loc_15E0; MOV:G.W R1, @H'E902
|
- H'15ED write offset H'0102 selector 0x081 -> H'E902; loc_15E0; MOV:G.W R1, @H'E902
|
||||||
- H'1A09 write index dynamic via R3 operand @(-H'1800,R3); loc_19DB; MOV:G.W R1, @(-H'1800,R3)
|
- H'1A09 write index dynamic via R3 operand @(-H'1800,R3); loc_19DB; MOV:G.W R1, @(-H'1800,R3)
|
||||||
- H'1A71 write index dynamic via R3 operand @(-H'1800,R3); loc_1A35; MOV:G.W R0, @(-H'1800,R3)
|
- H'1A71 write index dynamic via R3 operand @(-H'1800,R3); loc_1A35; MOV:G.W R0, @(-H'1800,R3)
|
||||||
- H'2691 write offset H'0124 -> H'E924; loc_2650; MOV:G.W R0, @H'E924
|
- H'2691 write offset H'0124 selector 0x092 -> H'E924; loc_2650; MOV:G.W R0, @H'E924
|
||||||
- H'3F90 write index dynamic via R0 operand @(-H'1800,R0); <no function>; CLR.W @(-H'1800,R0)
|
- H'3F90 write index dynamic via R0 operand @(-H'1800,R0); <no function>; CLR.W @(-H'1800,R0)
|
||||||
- H'407F write index dynamic via R0 operand @(-H'1800,R0); loc_4075; CLR.W @(-H'1800,R0)
|
- H'407F write index dynamic via R0 operand @(-H'1800,R0); loc_4075; CLR.W @(-H'1800,R0)
|
||||||
- H'40A8 write offset H'0000 -> H'E800; loc_4096; MOV:G.W #H'0080, @H'E800
|
- H'40A8 write offset H'0000 selector 0x000 -> H'E800; loc_4096; MOV:G.W #H'0080, @H'E800
|
||||||
- H'40AE write offset H'0006 -> H'E806; loc_4096; MOV:G.W #H'8000, @H'E806
|
- H'40AE write offset H'0006 selector 0x003 -> H'E806; loc_4096; MOV:G.W #H'8000, @H'E806
|
||||||
- H'40B4 write offset H'0080 -> H'E880; loc_4096; MOV:G.W #H'FFFF, @H'E880
|
- H'40B4 write offset H'0080 selector 0x040 -> H'E880; loc_4096; MOV:G.W #H'FFFF, @H'E880
|
||||||
- H'491D write offset H'01EC -> H'E9EC; loc_48FA; MOV:G.W R0, @H'E9EC
|
- H'491D write offset H'01EC selector 0x0F6 -> H'E9EC; loc_48FA; MOV:G.W R0, @H'E9EC
|
||||||
- H'BB35 read index dynamic via R0 operand @(-H'1800,R0); loc_BAF2; MOV:G.W @(-H'1800,R0), R4
|
- H'BB35 read index dynamic via R0 operand @(-H'1800,R0); loc_BAF2; MOV:G.W @(-H'1800,R0), R4
|
||||||
- H'BC79 write index dynamic via R4 operand @(-H'1800,R4); loc_BBAB; MOV:G.W R0, @(-H'1800,R4)
|
- H'BC79 write index dynamic via R4 operand @(-H'1800,R4); loc_BBAB; MOV:G.W R0, @(-H'1800,R4)
|
||||||
- H'BC99 write index dynamic via R4 operand @(-H'1800,R4); loc_BBAB; MOV:G.W R0, @(-H'1800,R4)
|
- H'BC99 write index dynamic via R4 operand @(-H'1800,R4); loc_BBAB; MOV:G.W R0, @(-H'1800,R4)
|
||||||
@@ -85,7 +85,7 @@ flag_table_candidate H'EC00-H'EFFF (negative H'1400; direct H'F980-H'F99F)
|
|||||||
accesses=6 reads=0 writes=6 dynamic=5
|
accesses=6 reads=0 writes=6 dynamic=5
|
||||||
static offsets: H'0200
|
static offsets: H'0200
|
||||||
functions: loc_BBAB:5, loc_4075:1
|
functions: loc_BBAB:5, loc_4075:1
|
||||||
- H'4088 write offset H'0200 -> H'EE00; loc_4075; CLR.W @(-H'1400,R0)
|
- H'4088 write offset H'0200 selector 0x000 -> H'EE00; loc_4075; CLR.W @(-H'1400,R0)
|
||||||
- H'BC82 write index dynamic via R5 operand @(-H'1400,R5); loc_BBAB; BSET.B #7, @(-H'1400,R5)
|
- H'BC82 write index dynamic via R5 operand @(-H'1400,R5); loc_BBAB; BSET.B #7, @(-H'1400,R5)
|
||||||
- H'BC9D write index dynamic via R5 operand @(-H'1400,R5); loc_BBAB; BSET.B #7, @(-H'1400,R5)
|
- H'BC9D write index dynamic via R5 operand @(-H'1400,R5); loc_BBAB; BSET.B #7, @(-H'1400,R5)
|
||||||
- H'BD22 write index dynamic via R5 operand @(-H'1400,R5); loc_BBAB; BSET.B #7, @(-H'1400,R5)
|
- H'BD22 write index dynamic via R5 operand @(-H'1400,R5); loc_BBAB; BSET.B #7, @(-H'1400,R5)
|
||||||
|
|||||||
86
ccu_emulator/README.md
Normal file
86
ccu_emulator/README.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# PT2 Fake CCU
|
||||||
|
|
||||||
|
This folder contains a small modular fake-CCU runner for the Sony RCP PT2-style serial link.
|
||||||
|
|
||||||
|
The first goal is simple:
|
||||||
|
|
||||||
|
1. Open the RCP serial link at `38400 8E1`.
|
||||||
|
2. Optionally wait for heartbeat.
|
||||||
|
3. Seed active selector-zero state.
|
||||||
|
4. Listen for complete RCP report frames.
|
||||||
|
5. Immediately answer report-looking frames with the neutral command-5 ACK:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 40 00 00 1F
|
||||||
|
```
|
||||||
|
|
||||||
|
ROM and emulator evidence says this consumes the outstanding report cursor without triggering COPY or lamp/display side effects.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Dry-run configuration:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
Run against the bench RCP on COM5:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --port COM5 --duration 30 --log captures\ccu-keepalive.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Power-cycle first through the COM6 relay:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --port COM5 --power-cycle --relay-port COM6 --duration 30 --log captures\ccu-keepalive-powercycle.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Try the older three-frame CONNECT cadence seed instead of the command-0 active seed:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --seed connect-sequence --seed-gap 0.700 --duration 30
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally add periodic state refresh traffic:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --refresh-active --refresh-interval 0.600 --duration 30
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable the bench-proven IRIS/M.BLACK LINK closed-loop module:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\ccu_emulator.py --iris-mblack-link --duration 60 --log captures\ccu-iris-mblack-link.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
When the RCP reports selector `0x0013` as `0x4000` or `0x0000`, this module sends:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 13 00 00 4C ; ACK selector 0x0013
|
||||||
|
00 00 13 40 00 09 ; mirror active, or 00 00 13 00 00 49 for clear
|
||||||
|
```
|
||||||
|
|
||||||
|
That matches the bench-proven IRIS/M.BLACK LINK state machine: the RCP reports local intent, the CCU acknowledges the selector, then the CCU mirrors the accepted state back so the next physical press toggles the other way.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
- `frames.py`: checksums, built-in frames, and simple host-frame builders.
|
||||||
|
- `iris_mblack_link.py`: selector `0x0013` ACK-and-mirror state-machine module.
|
||||||
|
- `modules.py`: small protocol-module interface for feature-specific CCU behavior.
|
||||||
|
- `policy.py`: decides whether an RCP frame should be ACKed.
|
||||||
|
- `refresh.py`: optional periodic state-refresh scheduling.
|
||||||
|
- `serial_link.py`: serial read/write plus checksum-resync frame detection.
|
||||||
|
- `controller.py`: event loop and stats.
|
||||||
|
- `cli.py`: command-line entry point.
|
||||||
|
|
||||||
|
## Why Periodic Lamp/Value Writes Help
|
||||||
|
|
||||||
|
Repeated button/lamp/status writes probably do play into the normal CCU flow. The ROM reloads the broad `F9C5` session watchdog on every complete six-byte RX frame, and command-0/command-4/command-6 table writes refresh the selector tables that drive lamps, readouts, and menus.
|
||||||
|
|
||||||
|
So a real CCU likely does both:
|
||||||
|
|
||||||
|
- reactively ACK RCP reports so the report queue advances, and
|
||||||
|
- stream or refresh panel state so the visible UI remains current.
|
||||||
|
|
||||||
|
The first version keeps those concerns separate: reactive ACKs are always available, while periodic refresh frames are opt-in with `--refresh-frame` or `--refresh-active`.
|
||||||
30
ccu_emulator/__init__.py
Normal file
30
ccu_emulator/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Small fake-CCU helpers for the Sony RCP PT2-style serial link."""
|
||||||
|
|
||||||
|
from .controller import CcuEmulator, CcuStats
|
||||||
|
from .frames import (
|
||||||
|
ACTIVE_SEED_COMMAND0,
|
||||||
|
CONNECT_CADENCE_SEQUENCE,
|
||||||
|
HEARTBEAT_FRAME,
|
||||||
|
NEUTRAL_ACK_FRAME,
|
||||||
|
format_frame,
|
||||||
|
frame_checksum,
|
||||||
|
frame_checksum_ok,
|
||||||
|
parse_frame,
|
||||||
|
)
|
||||||
|
from .iris_mblack_link import IrisMblackLinkModule
|
||||||
|
from .policy import AckPolicy
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ACTIVE_SEED_COMMAND0",
|
||||||
|
"CONNECT_CADENCE_SEQUENCE",
|
||||||
|
"CcuEmulator",
|
||||||
|
"CcuStats",
|
||||||
|
"HEARTBEAT_FRAME",
|
||||||
|
"IrisMblackLinkModule",
|
||||||
|
"NEUTRAL_ACK_FRAME",
|
||||||
|
"AckPolicy",
|
||||||
|
"format_frame",
|
||||||
|
"frame_checksum",
|
||||||
|
"frame_checksum_ok",
|
||||||
|
"parse_frame",
|
||||||
|
]
|
||||||
5
ccu_emulator/__main__.py
Normal file
5
ccu_emulator/__main__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .cli import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
208
ccu_emulator/cli.py
Normal file
208
ccu_emulator/cli.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TextIO
|
||||||
|
|
||||||
|
from h8536.bench_connect_lcd import (
|
||||||
|
BenchLogger,
|
||||||
|
_import_serial,
|
||||||
|
_read_relay_lines,
|
||||||
|
_relay_command,
|
||||||
|
_relay_settle,
|
||||||
|
add_serial_format_args,
|
||||||
|
open_device_serial,
|
||||||
|
serial_format_label,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .controller import CcuConfig, CcuEmulator
|
||||||
|
from .frames import ACTIVE_SEED_COMMAND0, CONNECT_CADENCE_SEQUENCE, NEUTRAL_ACK_FRAME, format_frame, parse_frame
|
||||||
|
from .iris_mblack_link import IrisMblackLinkModule
|
||||||
|
from .modules import CcuModule
|
||||||
|
from .policy import AckPolicy
|
||||||
|
from .refresh import PeriodicRefresh
|
||||||
|
from .serial_link import SerialLink
|
||||||
|
|
||||||
|
|
||||||
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(description="Run a small fake CCU for the Sony RCP PT2-style serial link.")
|
||||||
|
parser.add_argument("--port", default="COM5", help="RS232 serial port connected to the RCP")
|
||||||
|
parser.add_argument("--baud", type=int, default=38400, help="RCP serial baud rate")
|
||||||
|
add_serial_format_args(parser)
|
||||||
|
parser.add_argument("--duration", type=float, default=30.0, help="seconds to run the CCU loop")
|
||||||
|
parser.add_argument("--sync", choices=("checksum", "fixed"), default="checksum", help="RX frame sync strategy")
|
||||||
|
parser.add_argument("--log", type=Path, help="capture log path")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--seed",
|
||||||
|
choices=("command0", "connect-sequence", "none"),
|
||||||
|
default="command0",
|
||||||
|
help="built-in wake-up seed before reactive ACK loop",
|
||||||
|
)
|
||||||
|
parser.add_argument("--seed-frame", action="append", type=parse_frame, help="custom seed frame; repeatable")
|
||||||
|
parser.add_argument("--seed-gap", type=float, default=0.050, help="seconds to listen after each seed frame")
|
||||||
|
parser.add_argument("--ready-heartbeats", type=int, default=1, help="heartbeats to observe before seeding")
|
||||||
|
parser.add_argument("--ready-timeout", type=float, default=10.0, help="seconds to wait for ready heartbeat")
|
||||||
|
|
||||||
|
parser.add_argument("--ack-frame", type=parse_frame, default=NEUTRAL_ACK_FRAME, help="ACK frame to send after RCP reports")
|
||||||
|
parser.add_argument("--ack-delay", type=float, default=0.0, help="seconds to wait after detecting an RCP frame before ACK")
|
||||||
|
parser.add_argument("--no-ack-heartbeats", action="store_true", help="do not ACK heartbeat frames")
|
||||||
|
parser.add_argument("--no-ack-reports", action="store_true", help="do not ACK report-looking frames")
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-ack-unlabeled",
|
||||||
|
action="store_true",
|
||||||
|
help="do not ACK checksum-valid unlabeled frames outside known report command bytes",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("--refresh-frame", action="append", type=parse_frame, help="optional periodic refresh frame")
|
||||||
|
parser.add_argument(
|
||||||
|
"--refresh-active",
|
||||||
|
action="store_true",
|
||||||
|
help="periodically refresh selector zero with command0 0x8080",
|
||||||
|
)
|
||||||
|
parser.add_argument("--refresh-interval", type=float, default=0.0, help="seconds between optional refresh frames")
|
||||||
|
parser.add_argument("--loop-poll", type=float, default=0.001, help="sleep between service loop iterations")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--iris-mblack-link",
|
||||||
|
action="store_true",
|
||||||
|
help="enable the selector 0x0013 IRIS/M.BLACK LINK ACK-and-mirror module",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--iris-mblack-link-mirror-delay",
|
||||||
|
type=float,
|
||||||
|
default=0.050,
|
||||||
|
help="seconds between the selector 0x0013 ACK and command-0 mirror",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("--power-cycle", action="store_true", help="power-cycle DUT through relay before starting")
|
||||||
|
parser.add_argument("--relay-port", default="COM6", help="Pico relay serial port")
|
||||||
|
parser.add_argument("--relay-baud", type=int, default=115200, help="Pico relay serial baud rate")
|
||||||
|
parser.add_argument("--power-off-command", default="off", help="relay command used to remove DUT power")
|
||||||
|
parser.add_argument("--power-on-command", default="on", help="relay command used to apply DUT power")
|
||||||
|
parser.add_argument("--off-seconds", type=float, default=1.5, help="seconds to hold DUT powered off")
|
||||||
|
parser.add_argument("--relay-settle", type=float, default=2.0, help="seconds to wait after opening relay port")
|
||||||
|
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="print configuration without opening serial ports")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
||||||
|
args = build_arg_parser().parse_args(argv)
|
||||||
|
seed_frames = _seed_frames(args)
|
||||||
|
refresh_frames = _refresh_frames(args)
|
||||||
|
modules = _modules(args)
|
||||||
|
log_path = args.log or _default_log_path()
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
_print_dry_run(args, seed_frames, refresh_frames, modules, log_path, stdout)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
serial = _import_serial()
|
||||||
|
logger = BenchLogger(log_path, stdout=stdout)
|
||||||
|
relay = None
|
||||||
|
try:
|
||||||
|
logger.emit("PT2 fake CCU")
|
||||||
|
logger.emit(f"device={args.port} {args.baud} {serial_format_label(args)} sync={args.sync}")
|
||||||
|
logger.emit(f"log={log_path}")
|
||||||
|
logger.emit(f"ack_frame={format_frame(args.ack_frame)}")
|
||||||
|
logger.emit("seed_frames=" + (" | ".join(format_frame(frame) for frame in seed_frames) or "none"))
|
||||||
|
if refresh_frames:
|
||||||
|
logger.emit(
|
||||||
|
f"refresh_interval={args.refresh_interval:.3f}s frames="
|
||||||
|
+ " | ".join(format_frame(frame) for frame in refresh_frames)
|
||||||
|
)
|
||||||
|
if modules:
|
||||||
|
logger.emit("modules=" + " | ".join(module.name for module in modules))
|
||||||
|
|
||||||
|
with open_device_serial(serial, args) as device:
|
||||||
|
if args.power_cycle:
|
||||||
|
relay = serial.Serial(args.relay_port, args.relay_baud, timeout=0.25)
|
||||||
|
_relay_settle(relay, args.relay_settle, logger)
|
||||||
|
_relay_command(relay, args.power_off_command, logger)
|
||||||
|
time.sleep(max(0.0, args.off_seconds))
|
||||||
|
device.reset_input_buffer()
|
||||||
|
_relay_command(relay, args.power_on_command, logger)
|
||||||
|
else:
|
||||||
|
device.reset_input_buffer()
|
||||||
|
|
||||||
|
link = SerialLink(device, logger, sync_mode=args.sync)
|
||||||
|
config = CcuConfig(
|
||||||
|
seed_frames=tuple(seed_frames),
|
||||||
|
seed_gap=args.seed_gap,
|
||||||
|
ack_delay=args.ack_delay,
|
||||||
|
ready_heartbeats=args.ready_heartbeats,
|
||||||
|
ready_timeout=args.ready_timeout,
|
||||||
|
loop_poll=args.loop_poll,
|
||||||
|
)
|
||||||
|
policy = AckPolicy(
|
||||||
|
ack_frame=args.ack_frame,
|
||||||
|
ack_reports=not args.no_ack_reports,
|
||||||
|
ack_heartbeats=not args.no_ack_heartbeats,
|
||||||
|
ack_unlabeled_checksum_frames=not args.no_ack_unlabeled,
|
||||||
|
)
|
||||||
|
refresh = PeriodicRefresh(frames=refresh_frames, interval=args.refresh_interval)
|
||||||
|
CcuEmulator(link, logger, config=config, ack_policy=policy, refresh=refresh, modules=modules).run(
|
||||||
|
args.duration
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
finally:
|
||||||
|
if relay is not None:
|
||||||
|
relay.close()
|
||||||
|
logger.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_frames(args: argparse.Namespace) -> list[bytes]:
|
||||||
|
if args.seed_frame:
|
||||||
|
return list(args.seed_frame)
|
||||||
|
if args.seed == "none":
|
||||||
|
return []
|
||||||
|
if args.seed == "connect-sequence":
|
||||||
|
return list(CONNECT_CADENCE_SEQUENCE)
|
||||||
|
return [ACTIVE_SEED_COMMAND0]
|
||||||
|
|
||||||
|
|
||||||
|
def _refresh_frames(args: argparse.Namespace) -> list[bytes]:
|
||||||
|
frames = list(args.refresh_frame or [])
|
||||||
|
if args.refresh_active:
|
||||||
|
frames.append(ACTIVE_SEED_COMMAND0)
|
||||||
|
return frames
|
||||||
|
|
||||||
|
|
||||||
|
def _modules(args: argparse.Namespace) -> tuple[CcuModule, ...]:
|
||||||
|
modules: list[CcuModule] = []
|
||||||
|
if args.iris_mblack_link:
|
||||||
|
modules.append(IrisMblackLinkModule(mirror_delay=max(0.0, args.iris_mblack_link_mirror_delay)))
|
||||||
|
return tuple(modules)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_dry_run(
|
||||||
|
args: argparse.Namespace,
|
||||||
|
seed_frames: list[bytes],
|
||||||
|
refresh_frames: list[bytes],
|
||||||
|
modules: tuple[CcuModule, ...],
|
||||||
|
log_path: Path,
|
||||||
|
stdout: TextIO,
|
||||||
|
) -> None:
|
||||||
|
print(f"device={args.port} {args.baud} {serial_format_label(args)} sync={args.sync}", file=stdout)
|
||||||
|
print(f"duration={args.duration:.3f}s log={log_path}", file=stdout)
|
||||||
|
print(f"power_cycle={int(args.power_cycle)} relay={args.relay_port} {args.relay_baud}", file=stdout)
|
||||||
|
print(f"ack_frame={format_frame(args.ack_frame)}", file=stdout)
|
||||||
|
print("seed_frames=" + (" | ".join(format_frame(frame) for frame in seed_frames) or "none"), file=stdout)
|
||||||
|
print(
|
||||||
|
f"refresh_interval={args.refresh_interval:.3f}s frames="
|
||||||
|
+ (" | ".join(format_frame(frame) for frame in refresh_frames) or "none"),
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
|
print("modules=" + (" | ".join(module.name for module in modules) or "none"), file=stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def _default_log_path() -> Path:
|
||||||
|
return Path("captures") / f"ccu-emulator-{datetime.now().strftime('%Y%m%d-%H%M%S')}.txt"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
179
ccu_emulator/controller.py
Normal file
179
ccu_emulator/controller.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from h8536.bench_connect_lcd import BenchLogger, format_frame
|
||||||
|
|
||||||
|
from .frames import ACTIVE_SEED_COMMAND0
|
||||||
|
from .modules import CcuModule, ModuleDecision
|
||||||
|
from .policy import AckPolicy
|
||||||
|
from .refresh import PeriodicRefresh
|
||||||
|
from .serial_link import RxFrame, SerialLink
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CcuStats:
|
||||||
|
rx_frames: int = 0
|
||||||
|
tx_frames: int = 0
|
||||||
|
ack_frames: int = 0
|
||||||
|
module_frames: int = 0
|
||||||
|
seed_frames: int = 0
|
||||||
|
refresh_frames: int = 0
|
||||||
|
started_at: float = 0.0
|
||||||
|
ended_at: float = 0.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def elapsed(self) -> float:
|
||||||
|
end = self.ended_at or time.monotonic()
|
||||||
|
return max(0.0, end - self.started_at)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CcuConfig:
|
||||||
|
seed_frames: tuple[bytes, ...] = (ACTIVE_SEED_COMMAND0,)
|
||||||
|
seed_gap: float = 0.050
|
||||||
|
ack_delay: float = 0.0
|
||||||
|
ready_heartbeats: int = 1
|
||||||
|
ready_timeout: float = 10.0
|
||||||
|
loop_poll: float = 0.001
|
||||||
|
|
||||||
|
|
||||||
|
class CcuEmulator:
|
||||||
|
"""Event-driven fake CCU for the PT2-style RCP serial protocol."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
link: SerialLink,
|
||||||
|
logger: BenchLogger,
|
||||||
|
*,
|
||||||
|
config: CcuConfig | None = None,
|
||||||
|
ack_policy: AckPolicy | None = None,
|
||||||
|
refresh: PeriodicRefresh | None = None,
|
||||||
|
modules: tuple[CcuModule, ...] = (),
|
||||||
|
) -> None:
|
||||||
|
self.link = link
|
||||||
|
self.logger = logger
|
||||||
|
self.config = config or CcuConfig()
|
||||||
|
self.ack_policy = ack_policy or AckPolicy()
|
||||||
|
self.refresh = refresh or PeriodicRefresh()
|
||||||
|
self.modules = modules
|
||||||
|
self.stats = CcuStats()
|
||||||
|
|
||||||
|
def run(self, duration: float) -> CcuStats:
|
||||||
|
self.stats = CcuStats(started_at=time.monotonic())
|
||||||
|
self.logger.event(
|
||||||
|
"CCU_START "
|
||||||
|
f"duration={duration:.3f}s seed_frames={len(self.config.seed_frames)} "
|
||||||
|
f"ack={format_frame(self.ack_policy.ack_frame)} modules={len(self.modules)}"
|
||||||
|
)
|
||||||
|
self._wait_ready()
|
||||||
|
self._send_seed_frames()
|
||||||
|
self.refresh.start()
|
||||||
|
|
||||||
|
deadline = time.monotonic() + max(0.0, duration)
|
||||||
|
try:
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
self._service_rx()
|
||||||
|
self._service_refresh()
|
||||||
|
time.sleep(max(0.0, self.config.loop_poll))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.logger.event("CCU_STOP keyboard_interrupt")
|
||||||
|
finally:
|
||||||
|
self.stats.ended_at = time.monotonic()
|
||||||
|
self._emit_summary()
|
||||||
|
return self.stats
|
||||||
|
|
||||||
|
def _wait_ready(self) -> None:
|
||||||
|
if self.config.ready_heartbeats <= 0:
|
||||||
|
self.logger.event("READY skipped")
|
||||||
|
return
|
||||||
|
self.logger.event(
|
||||||
|
f"READY_WAIT heartbeats={self.config.ready_heartbeats} timeout={self.config.ready_timeout:.3f}s"
|
||||||
|
)
|
||||||
|
start_count = self.link.detector.labels["heartbeat"]
|
||||||
|
deadline = time.monotonic() + max(0.0, self.config.ready_timeout)
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
for frame in self.link.read_available():
|
||||||
|
self._record_rx(frame)
|
||||||
|
if self.link.detector.labels["heartbeat"] - start_count >= self.config.ready_heartbeats:
|
||||||
|
self.logger.event(f"READY heartbeat_count={self.link.detector.labels['heartbeat']}")
|
||||||
|
return
|
||||||
|
time.sleep(max(0.0, self.config.loop_poll))
|
||||||
|
self.logger.event(f"READY_TIMEOUT heartbeat_count={self.link.detector.labels['heartbeat']}")
|
||||||
|
|
||||||
|
def _send_seed_frames(self) -> None:
|
||||||
|
for index, frame in enumerate(self.config.seed_frames, start=1):
|
||||||
|
self.link.send(frame, f"seed[{index}]")
|
||||||
|
self.stats.seed_frames += 1
|
||||||
|
self.stats.tx_frames += 1
|
||||||
|
self._listen_for(self.config.seed_gap)
|
||||||
|
|
||||||
|
def _listen_for(self, seconds: float) -> None:
|
||||||
|
deadline = time.monotonic() + max(0.0, seconds)
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
self._service_rx()
|
||||||
|
self._service_refresh()
|
||||||
|
time.sleep(max(0.0, self.config.loop_poll))
|
||||||
|
|
||||||
|
def _service_rx(self) -> None:
|
||||||
|
for item in self.link.read_available():
|
||||||
|
self._record_rx(item)
|
||||||
|
suppress_default_ack = self._service_modules(item)
|
||||||
|
if suppress_default_ack:
|
||||||
|
continue
|
||||||
|
decision = self.ack_policy.decide(item.frame, item.label)
|
||||||
|
if not decision.should_ack:
|
||||||
|
self.logger.event(f"ACK_SKIP reason={decision.reason} frame={format_frame(item.frame)}")
|
||||||
|
continue
|
||||||
|
if self.config.ack_delay > 0:
|
||||||
|
time.sleep(self.config.ack_delay)
|
||||||
|
self.link.send(decision.frame, f"ack reason={decision.reason}")
|
||||||
|
self.stats.ack_frames += 1
|
||||||
|
self.stats.tx_frames += 1
|
||||||
|
|
||||||
|
def _service_modules(self, item: RxFrame) -> bool:
|
||||||
|
suppress_default_ack = False
|
||||||
|
for module in self.modules:
|
||||||
|
decision = module.on_rx(item.frame, item.label)
|
||||||
|
if decision is None:
|
||||||
|
continue
|
||||||
|
if decision.reason:
|
||||||
|
self.logger.event(
|
||||||
|
f"MODULE {module.name} reason={decision.reason} frame={format_frame(item.frame)}"
|
||||||
|
)
|
||||||
|
self._send_module_decision(decision)
|
||||||
|
suppress_default_ack = suppress_default_ack or decision.suppress_default_ack
|
||||||
|
return suppress_default_ack
|
||||||
|
|
||||||
|
def _send_module_decision(self, decision: ModuleDecision) -> None:
|
||||||
|
for tx in decision.tx:
|
||||||
|
if tx.delay > 0:
|
||||||
|
time.sleep(tx.delay)
|
||||||
|
self.link.send(tx.frame, tx.label)
|
||||||
|
self.stats.module_frames += 1
|
||||||
|
self.stats.tx_frames += 1
|
||||||
|
|
||||||
|
def _service_refresh(self) -> None:
|
||||||
|
for frame in self.refresh.due_frames():
|
||||||
|
self.link.send(frame, "refresh")
|
||||||
|
self.stats.refresh_frames += 1
|
||||||
|
self.stats.tx_frames += 1
|
||||||
|
|
||||||
|
def _record_rx(self, item: RxFrame) -> None:
|
||||||
|
self.stats.rx_frames += 1
|
||||||
|
|
||||||
|
def _emit_summary(self) -> None:
|
||||||
|
self.logger.emit()
|
||||||
|
self.logger.emit("CCU Summary")
|
||||||
|
self.logger.emit(f"elapsed={self.stats.elapsed:.3f}s")
|
||||||
|
self.logger.emit(f"rx_frames={self.stats.rx_frames}")
|
||||||
|
self.logger.emit(f"tx_frames={self.stats.tx_frames}")
|
||||||
|
self.logger.emit(f"ack_frames={self.stats.ack_frames}")
|
||||||
|
self.logger.emit(f"module_frames={self.stats.module_frames}")
|
||||||
|
self.logger.emit(f"seed_frames={self.stats.seed_frames}")
|
||||||
|
self.logger.emit(f"refresh_frames={self.stats.refresh_frames}")
|
||||||
|
self.logger.emit(f"resync_events={self.link.detector.resync_events}")
|
||||||
|
self.logger.emit(f"dropped_bytes={self.link.detector.dropped_bytes}")
|
||||||
|
for label, count in sorted(self.link.detector.labels.items()):
|
||||||
|
self.logger.emit(f"{label}={count}")
|
||||||
59
ccu_emulator/frames.py
Normal file
59
ccu_emulator/frames.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from h8536.bench_connect_lcd import format_frame, frame_checksum, frame_checksum_ok, parse_frame
|
||||||
|
|
||||||
|
|
||||||
|
FRAME_LENGTH = 6
|
||||||
|
|
||||||
|
HEARTBEAT_FRAME = bytes.fromhex("0000000080DA")
|
||||||
|
|
||||||
|
# Command 0, selector 0, value 0x8080. This seeds E000/E800 selector zero.
|
||||||
|
ACTIVE_SEED_COMMAND0 = bytes.fromhex("00000080805A")
|
||||||
|
|
||||||
|
# The older bench cadence sequence. It is still useful as an optional wake-up
|
||||||
|
# strategy because the real panel proved timing-sensitive around these frames.
|
||||||
|
CONNECT_CADENCE_SEQUENCE = (
|
||||||
|
bytes.fromhex("04000040001E"),
|
||||||
|
bytes.fromhex("0400008000DE"),
|
||||||
|
bytes.fromhex("040000C0009E"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Command 5, selector 0x0040, value ignored. ROM trace shows this is the safest
|
||||||
|
# neutral report-consume candidate.
|
||||||
|
NEUTRAL_ACK_FRAME = bytes.fromhex("05004000001F")
|
||||||
|
|
||||||
|
|
||||||
|
def build_frame(command: int, selector: int, value: int) -> bytes:
|
||||||
|
"""Build a six-byte host frame for simple page-0/page-1 selectors.
|
||||||
|
|
||||||
|
This helper covers the selector encodings we currently use in fake-CCU
|
||||||
|
probes. Keep more exotic mapping in one place when we learn it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not 0 <= command <= 0xFF:
|
||||||
|
raise ValueError("command byte out of range")
|
||||||
|
if not 0 <= selector <= 0x01FF:
|
||||||
|
raise ValueError("selector out of supported range 0x000-0x1FF")
|
||||||
|
if not 0 <= value <= 0xFFFF:
|
||||||
|
raise ValueError("value out of range")
|
||||||
|
|
||||||
|
if selector <= 0x007F:
|
||||||
|
byte1 = 0x00
|
||||||
|
byte2 = selector
|
||||||
|
elif selector <= 0x017F:
|
||||||
|
byte1 = 0x01
|
||||||
|
byte2 = selector - 0x0080
|
||||||
|
else:
|
||||||
|
byte1 = 0x02
|
||||||
|
byte2 = selector - 0x0180
|
||||||
|
|
||||||
|
frame = bytes(
|
||||||
|
[
|
||||||
|
command & 0xFF,
|
||||||
|
byte1 & 0xFF,
|
||||||
|
byte2 & 0xFF,
|
||||||
|
(value >> 8) & 0xFF,
|
||||||
|
value & 0xFF,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return frame + bytes([frame_checksum(frame)])
|
||||||
65
ccu_emulator/iris_mblack_link.py
Normal file
65
ccu_emulator/iris_mblack_link.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .frames import build_frame, frame_checksum_ok
|
||||||
|
from .modules import ModuleDecision, ModuleTx
|
||||||
|
|
||||||
|
|
||||||
|
SELECTOR_IRIS_MBLACK_LINK = 0x0013
|
||||||
|
IRIS_MBLACK_LINK_CLEAR = 0x0000
|
||||||
|
IRIS_MBLACK_LINK_ACTIVE = 0x4000
|
||||||
|
|
||||||
|
|
||||||
|
def selector_from_frame(frame: bytes) -> int | None:
|
||||||
|
if len(frame) != 6:
|
||||||
|
return None
|
||||||
|
if frame[1] == 0x00:
|
||||||
|
return frame[2]
|
||||||
|
if frame[1] == 0x01:
|
||||||
|
return 0x0080 + frame[2]
|
||||||
|
if frame[1] == 0x02:
|
||||||
|
return 0x0180 + frame[2]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def value_from_frame(frame: bytes) -> int | None:
|
||||||
|
if len(frame) != 6:
|
||||||
|
return None
|
||||||
|
return ((frame[3] << 8) | frame[4]) & 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class IrisMblackLinkModule:
|
||||||
|
"""Closed-loop CCU side for the IRIS/M.BLACK LINK button/report path."""
|
||||||
|
|
||||||
|
mirror_delay: float = 0.050
|
||||||
|
report_commands: frozenset[int] = frozenset({0x00, 0x01, 0x02})
|
||||||
|
handled_values: frozenset[int] = frozenset({IRIS_MBLACK_LINK_CLEAR, IRIS_MBLACK_LINK_ACTIVE})
|
||||||
|
name: str = "iris_mblack_link"
|
||||||
|
|
||||||
|
def on_rx(self, frame: bytes, label: str = "") -> ModuleDecision | None:
|
||||||
|
if not frame_checksum_ok(frame) or frame[0] not in self.report_commands:
|
||||||
|
return None
|
||||||
|
selector = selector_from_frame(frame)
|
||||||
|
if selector != SELECTOR_IRIS_MBLACK_LINK:
|
||||||
|
return None
|
||||||
|
value = value_from_frame(frame)
|
||||||
|
if value not in self.handled_values:
|
||||||
|
return None
|
||||||
|
|
||||||
|
state = "active" if value == IRIS_MBLACK_LINK_ACTIVE else "clear"
|
||||||
|
ack = build_frame(0x05, SELECTOR_IRIS_MBLACK_LINK, 0x0000)
|
||||||
|
mirror = build_frame(0x00, SELECTOR_IRIS_MBLACK_LINK, value)
|
||||||
|
return ModuleDecision(
|
||||||
|
tx=(
|
||||||
|
ModuleTx(ack, f"{self.name} ack selector=0x0013 value=0x{value:04X}"),
|
||||||
|
ModuleTx(
|
||||||
|
mirror,
|
||||||
|
f"{self.name} mirror {state} selector=0x0013 value=0x{value:04X}",
|
||||||
|
delay=self.mirror_delay,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
suppress_default_ack=True,
|
||||||
|
reason=f"{self.name}_{state}_report",
|
||||||
|
)
|
||||||
25
ccu_emulator/modules.py
Normal file
25
ccu_emulator/modules.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ModuleTx:
|
||||||
|
frame: bytes
|
||||||
|
label: str
|
||||||
|
delay: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ModuleDecision:
|
||||||
|
tx: tuple[ModuleTx, ...] = ()
|
||||||
|
suppress_default_ack: bool = False
|
||||||
|
reason: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class CcuModule(Protocol):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def on_rx(self, frame: bytes, label: str = "") -> ModuleDecision | None:
|
||||||
|
"""Inspect an RCP frame and optionally provide CCU response frames."""
|
||||||
44
ccu_emulator/policy.py
Normal file
44
ccu_emulator/policy.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .frames import HEARTBEAT_FRAME, NEUTRAL_ACK_FRAME, frame_checksum_ok
|
||||||
|
|
||||||
|
|
||||||
|
REPORT_COMMAND_BYTES = frozenset({0x00, 0x01, 0x02})
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AckDecision:
|
||||||
|
should_ack: bool
|
||||||
|
frame: bytes = NEUTRAL_ACK_FRAME
|
||||||
|
reason: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AckPolicy:
|
||||||
|
"""Decides whether an RCP-origin frame should get a continuation ACK."""
|
||||||
|
|
||||||
|
ack_frame: bytes = NEUTRAL_ACK_FRAME
|
||||||
|
ack_reports: bool = True
|
||||||
|
ack_heartbeats: bool = True
|
||||||
|
ack_unlabeled_checksum_frames: bool = True
|
||||||
|
|
||||||
|
def decide(self, frame: bytes, label: str = "") -> AckDecision:
|
||||||
|
if not frame_checksum_ok(frame):
|
||||||
|
return AckDecision(False, self.ack_frame, "checksum_bad")
|
||||||
|
|
||||||
|
if frame == HEARTBEAT_FRAME:
|
||||||
|
if self.ack_heartbeats:
|
||||||
|
return AckDecision(True, self.ack_frame, "heartbeat_report")
|
||||||
|
return AckDecision(False, self.ack_frame, "heartbeat_ignored")
|
||||||
|
|
||||||
|
if frame[0] in REPORT_COMMAND_BYTES:
|
||||||
|
if self.ack_reports:
|
||||||
|
return AckDecision(True, self.ack_frame, f"report_cmd_{frame[0]:02X}")
|
||||||
|
return AckDecision(False, self.ack_frame, "reports_disabled")
|
||||||
|
|
||||||
|
if label == "checksum_ok_unlabeled" and self.ack_unlabeled_checksum_frames:
|
||||||
|
return AckDecision(True, self.ack_frame, "unlabeled_checksum_ok")
|
||||||
|
|
||||||
|
return AckDecision(False, self.ack_frame, f"non_report_cmd_{frame[0]:02X}")
|
||||||
41
ccu_emulator/refresh.py
Normal file
41
ccu_emulator/refresh.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PeriodicRefresh:
|
||||||
|
"""Small scheduler for optional CCU state-refresh frames."""
|
||||||
|
|
||||||
|
frames: list[bytes] = field(default_factory=list)
|
||||||
|
interval: float = 0.0
|
||||||
|
_next_due: float | None = None
|
||||||
|
_index: int = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enabled(self) -> bool:
|
||||||
|
return bool(self.frames) and self.interval > 0
|
||||||
|
|
||||||
|
def start(self, now: float | None = None) -> None:
|
||||||
|
if not self.enabled:
|
||||||
|
self._next_due = None
|
||||||
|
return
|
||||||
|
self._next_due = (time.monotonic() if now is None else now) + self.interval
|
||||||
|
|
||||||
|
def due_frames(self, now: float | None = None) -> list[bytes]:
|
||||||
|
if not self.enabled:
|
||||||
|
return []
|
||||||
|
current = time.monotonic() if now is None else now
|
||||||
|
if self._next_due is None:
|
||||||
|
self._next_due = current + self.interval
|
||||||
|
return []
|
||||||
|
if current < self._next_due:
|
||||||
|
return []
|
||||||
|
|
||||||
|
frame = self.frames[self._index % len(self.frames)]
|
||||||
|
self._index += 1
|
||||||
|
|
||||||
|
while self._next_due <= current:
|
||||||
|
self._next_due += self.interval
|
||||||
|
return [frame]
|
||||||
67
ccu_emulator/serial_link.py
Normal file
67
ccu_emulator/serial_link.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Iterable
|
||||||
|
|
||||||
|
from h8536.bench_connect_lcd import BenchLogger, FrameDetector, format_frame, label_frame
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RxFrame:
|
||||||
|
frame: bytes
|
||||||
|
label: str
|
||||||
|
|
||||||
|
|
||||||
|
class SerialLink:
|
||||||
|
"""Thin serial-port wrapper with checksum-resync frame detection."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
device: Any,
|
||||||
|
logger: BenchLogger,
|
||||||
|
*,
|
||||||
|
sync_mode: str = "checksum",
|
||||||
|
) -> None:
|
||||||
|
self.device = device
|
||||||
|
self.logger = logger
|
||||||
|
self.detector = FrameDetector(sync_mode=sync_mode)
|
||||||
|
|
||||||
|
def reset_input(self) -> None:
|
||||||
|
self.device.reset_input_buffer()
|
||||||
|
self.detector = FrameDetector(sync_mode=self.detector.sync_mode)
|
||||||
|
|
||||||
|
def read_available(self) -> list[RxFrame]:
|
||||||
|
waiting = getattr(self.device, "in_waiting", 0)
|
||||||
|
data = self.device.read(waiting or 1)
|
||||||
|
if not data:
|
||||||
|
return []
|
||||||
|
|
||||||
|
dropped_before = self.detector.dropped_bytes
|
||||||
|
self.logger.chunk("RX", data)
|
||||||
|
frames = [RxFrame(frame, label) for frame, label in self.detector.feed(data)]
|
||||||
|
for item in frames:
|
||||||
|
self.logger.event(f"DETECT {item.label} {format_frame(item.frame)}")
|
||||||
|
dropped_now = self.detector.dropped_bytes - dropped_before
|
||||||
|
if dropped_now:
|
||||||
|
self.logger.event(
|
||||||
|
f"RESYNC dropped_bytes={dropped_now} total_dropped={self.detector.dropped_bytes} "
|
||||||
|
f"buffered={len(self.detector.buffer)}"
|
||||||
|
)
|
||||||
|
return frames
|
||||||
|
|
||||||
|
def send(self, frame: bytes, label: str) -> None:
|
||||||
|
self.device.write(frame)
|
||||||
|
self.device.flush()
|
||||||
|
self.logger.chunk("TX", frame)
|
||||||
|
self.logger.event(f"SENT {label} {format_frame(frame)}")
|
||||||
|
|
||||||
|
def labels(self) -> dict[str, int]:
|
||||||
|
return dict(self.detector.labels)
|
||||||
|
|
||||||
|
|
||||||
|
def label_for_frame(frame: bytes) -> str:
|
||||||
|
return label_frame(frame)
|
||||||
|
|
||||||
|
|
||||||
|
def format_frames(frames: Iterable[bytes]) -> str:
|
||||||
|
return " | ".join(format_frame(frame) for frame in frames)
|
||||||
101
docs/pt2-button-report-bench-plan.md
Normal file
101
docs/pt2-button-report-bench-plan.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# PT2 Button Report Bench Plan
|
||||||
|
|
||||||
|
Date: 2026-05-26
|
||||||
|
|
||||||
|
## Question
|
||||||
|
|
||||||
|
After the SHUTTER ON/OFF test exposed queued frames:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02 01 0F 80 00 D6
|
||||||
|
01 01 0F 80 00 D5
|
||||||
|
```
|
||||||
|
|
||||||
|
the next bench question is whether other buttons share the same common queue-service gate, or whether each button/selector needs its own feature gate.
|
||||||
|
|
||||||
|
## Current ROM Model
|
||||||
|
|
||||||
|
The known button ROM trace says many physical buttons share this front-door path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
panel byte snapshot -> shadow byte -> dirty bit -> loc_1C0E jump table -> handler -> loc_3E54 report builder
|
||||||
|
```
|
||||||
|
|
||||||
|
CALL and CAM POWER use this path, and SHUTTER ON/OFF now appears to use it too once the CCU side services the queued report stream.
|
||||||
|
|
||||||
|
The handlers diverge after the front door. Some directly build reports. Others depend on session/menu/feature state, especially secondary-table `E400` bits.
|
||||||
|
|
||||||
|
## Test Method
|
||||||
|
|
||||||
|
Run a no-button baseline first:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\button-report-common-gate-baseline.json --parity E --quiet-console --log captures\button-common-baseline.txt --result-json captures\button-common-baseline-result.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run one physical button candidate through the same common gate:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\button-report-common-gate-press.json --parity E --quiet-console --log captures\button-common-SHORTNAME.txt --result-json captures\button-common-SHORTNAME-result.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare the result JSONs:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario_compare.py captures\button-common-baseline-result.json captures\button-common-SHORTNAME-result.json --show-labels
|
||||||
|
```
|
||||||
|
|
||||||
|
If the candidate run has extra ACK-target frames, decode their selector/value. That suggests the button shares the common queue-service gate.
|
||||||
|
|
||||||
|
## Broad-Gate Fallback
|
||||||
|
|
||||||
|
If common-gate produces no extra selector for a button that should plausibly report, try the broader secondary-gate run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\button-report-broad-gates-press.json --parity E --quiet-console --log captures\button-broad-SHORTNAME.txt --result-json captures\button-broad-SHORTNAME-result.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare it against the same baseline:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario_compare.py captures\button-common-baseline-result.json captures\button-broad-SHORTNAME-result.json --show-labels
|
||||||
|
```
|
||||||
|
|
||||||
|
If a button only emits in the broad-gate run, it likely needs a selector-specific feature/report gate rather than only the common queue-service gate.
|
||||||
|
|
||||||
|
## Practical Candidate Order
|
||||||
|
|
||||||
|
Start with known or visually obvious controls:
|
||||||
|
|
||||||
|
- SHUTTER ON/OFF: positive control, should reproduce selector `0x008F` value `0x8000`.
|
||||||
|
- CAM POWER: known direct selector `0x0007`, useful sanity check.
|
||||||
|
- CALL: known direct selector `0x0015`, but it can emit outside active state so treat it as a control.
|
||||||
|
- KNEE AUTO, DETAIL, BARS, WHITE BALANCE, BLACK/FLARE, and OTHER: useful unknowns because prior display/lamp tests suggest nearby ROM handlers and selector state.
|
||||||
|
|
||||||
|
Run one physical button per capture. The queue emits background reports, so button identity is much easier to recover by comparing against the no-button baseline than by reading one log in isolation.
|
||||||
|
|
||||||
|
## First Common-Gate Results
|
||||||
|
|
||||||
|
The first common-gate bench set compared each one-button run against `button-common-baseline`.
|
||||||
|
|
||||||
|
Positive results:
|
||||||
|
|
||||||
|
| Button | Extra queued report(s) | Current meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| BARS | `01/02 00 17 80 00`, `01/02 00 18 80 00` | BARS shares the common queue-service gate. This matches the ROM handler that emits selectors `0x0017` and `0x0018`. |
|
||||||
|
| IRIS AUTO | `01/02 01 1A 08 00` | IRIS AUTO shares the common queue-service gate. This matches selector `0x009A` in the ROM button trace. |
|
||||||
|
|
||||||
|
No extra ACK-target frames were found in these common-gate runs:
|
||||||
|
|
||||||
|
- STANDARD
|
||||||
|
- MASTER
|
||||||
|
- SLAVE
|
||||||
|
- KNEE AUTO
|
||||||
|
- BLACK/FLARE FLARE
|
||||||
|
- OTHERS
|
||||||
|
|
||||||
|
Interpretation: at least some local controls use the same queue-service gate as SHUTTER ON/OFF, but not every physical button emits through this simple state. The silent candidates may need a different secondary/menu/session gate, may be display-only in this mode, or may not have been captured as a single edge event.
|
||||||
|
|
||||||
|
OTHERS is now treated separately in `pt2-menu-state-machine.md` because the ROM evidence points to a local menu/display key rather than a simple report button.
|
||||||
|
|
||||||
|
Note: the result JSONs preserved the per-button summaries, but the test log path was reused as `button-common-SHORTNAME.txt` for the candidate runs. For future runs, replace `SHORTNAME` in both `--log` and `--result-json` so the full timeline is preserved per button.
|
||||||
209
docs/pt2-continuation-command-trace.md
Normal file
209
docs/pt2-continuation-command-trace.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# PT2 Continuation Command Trace
|
||||||
|
|
||||||
|
This note enumerates the command paths that are active after the ROM sees `FAA2 != 0`.
|
||||||
|
|
||||||
|
## Dispatcher Shape
|
||||||
|
|
||||||
|
RX command dispatch starts after six-byte frame validation at `H'BBD6-H'BC67`.
|
||||||
|
|
||||||
|
Important gates:
|
||||||
|
|
||||||
|
- `F9C3 == 6`.
|
||||||
|
- SCI/RX physical error latch `FAA4.7 == 0`.
|
||||||
|
- Checksum matches `0x5A ^ F860 ^ F861 ^ F862 ^ F863 ^ F864 == F865`.
|
||||||
|
- Command number is `F860 & 0x07`.
|
||||||
|
- `F861.7` rejects normal command handling.
|
||||||
|
- `FAA2 == 0` enters the initial dispatcher.
|
||||||
|
- `FAA2 != 0` enters the continuation dispatcher.
|
||||||
|
|
||||||
|
Continuation split:
|
||||||
|
|
||||||
|
| Incoming command bits | Path | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `command & 0x04 != 0` | `H'BC3A-H'BC56` | explicit continuation commands `4/5/6/7` |
|
||||||
|
| `command & 0x04 == 0`, `FAA2.3 == 0` | `H'BC5C -> H'BE6F` | ignored/returns |
|
||||||
|
| `command & 0x04 == 0`, `FAA2.3 == 1` | `H'BC5C-H'BC67 -> H'BC15` | clears queued-report continuation bit and re-enters initial command handling |
|
||||||
|
|
||||||
|
That last row is easy to miss: command `0/1/2/3` can consume the report-continuation latch when `FAA2.3` was set by an autonomous report, then command `0/1/2` can run as an initial command. Command `3` still has no recognized initial handler and falls to the clear/return path.
|
||||||
|
|
||||||
|
Selector bytes are packed from `F861:F862`, mapped by `loc_622B`, then doubled into `R4 = selector * 2` before the command handlers use the selector as a word-table offset.
|
||||||
|
|
||||||
|
## Explicit Continuation Commands
|
||||||
|
|
||||||
|
| Command | Handler | Table writes | Queue/report behavior | Cleanup |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `4` | `H'BD0E` | writes `E000`; selector zero also writes `E800`; sets `EC00.7` | calls `BE70` to append selector to `F970`; if `FAA2.3` set, advances `F9B5` | clears `FAA3`, clears `FAA2` |
|
||||||
|
| `5` | `H'BD80` | none | special selectors may append to `F970` or clear latches; if `FAA2.3` set, advances `F9B5` | clears `FAA3`, clears `FAA2` |
|
||||||
|
| `6` | `H'BDDB` | writes `E400`; sets `EC00.6` | does not append to `F970`; if `FAA2.3` set, advances `F9B5` | clears `FAA3`, clears `FAA2` |
|
||||||
|
| `7` | `H'BE05` | none | copies previous finalized TX frame from `F858-F85C` to `F850-F854` and retransmits | does not clear `FAA2/FAA3` in this handler |
|
||||||
|
|
||||||
|
## `BE70`: Selector Processing Queue Append
|
||||||
|
|
||||||
|
`BE70` is the shared helper used by command `0`, command `4`, and selected command-`5` cases.
|
||||||
|
|
||||||
|
Path `H'BE70-H'BE9D`:
|
||||||
|
|
||||||
|
- Scans the `F970` word ring from consumer cursor `F9B9` to producer cursor `F9B4`.
|
||||||
|
- If the selector is already present, returns without appending a duplicate.
|
||||||
|
- If absent, writes the selector to `F970 + 2*F9B4`.
|
||||||
|
- Increments `F9B4` and clears bit 5, making the queue a 32-entry ring.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- `F970` means "process this selector internally".
|
||||||
|
- It is separate from the serial-visible report queue at `F870`.
|
||||||
|
- Re-sending the same selector may refresh table values without creating another queued selector entry if the old entry has not been consumed yet.
|
||||||
|
|
||||||
|
## Command 4: Continuation Primary Write
|
||||||
|
|
||||||
|
Selector zero branch:
|
||||||
|
|
||||||
|
- `H'BD12-H'BD18`: builds value from `F863` and forces low byte `0x80`.
|
||||||
|
- `H'BD1A`: writes `E000[0]`.
|
||||||
|
- `H'BD1E`: writes `E800[0]`.
|
||||||
|
- `H'BD22`: sets `EC00[0].7`.
|
||||||
|
- `H'BD26`: calls `BE70`, appending selector zero to `F970`.
|
||||||
|
|
||||||
|
Nonzero selector branch:
|
||||||
|
|
||||||
|
- `H'BD2B-H'BD35`: builds value from `F863/F864` and writes `E000[selector]`.
|
||||||
|
- `H'BD39`: sets `EC00[selector].7`.
|
||||||
|
- `H'BD3D-H'BD45`: if the selector has a mapped shadow slot, writes the value into the `F400` shadow area.
|
||||||
|
- `H'BD49-H'BD5F`: if `F76E.7` is set, uses the mapped shadow offset plus the `F76E` page nibble and calls `BFE0`, the EEPROM/persistent-write path.
|
||||||
|
- `H'BD64`: calls `BE70`, appending the selector to `F970`.
|
||||||
|
|
||||||
|
Persistent-write detail:
|
||||||
|
|
||||||
|
- If the selector-to-shadow map entry is zero, the mirror/persist path is skipped.
|
||||||
|
- `BFE0` seeds retry timer `F840=0x0A`, writes via the P9/X24164 helper path, reads back for verification, and sets `F841.7` on timeout/failure.
|
||||||
|
|
||||||
|
Report consumption:
|
||||||
|
|
||||||
|
- `H'BD67-H'BD71`: if `FAA2.3` was set by a queued report, increments `F9B5` and clears `F9B5.7`.
|
||||||
|
- `H'BD75-H'BD79`: clears `FAA3` and `FAA2`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- Command `4` is a continuation write/update, not a generic always-live command.
|
||||||
|
- Command `4` selector zero can refresh both the primary/current table and queue the CONNECT selector.
|
||||||
|
- Nonzero command `4` does not directly refresh `E800`; autonomous report values still come from the older/current `E800` value unless another path updates it.
|
||||||
|
|
||||||
|
## Command 5: ACK-Like / Special Selector Command
|
||||||
|
|
||||||
|
Command `5` reaches `H'BD80` only from the continuation dispatcher:
|
||||||
|
|
||||||
|
- `FAA2 != 0`.
|
||||||
|
- `command & 0x04 != 0`.
|
||||||
|
- `F861.7 == 0`.
|
||||||
|
- `F860 & 0x07 == 5`.
|
||||||
|
|
||||||
|
Special enqueue selectors:
|
||||||
|
|
||||||
|
| Selector | Branch | Later selector dispatch | Current meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `0x006C` | `H'BD80 -> H'BDBF` | `H'2FAF` | COPY completion/exit sibling when copy flags are live |
|
||||||
|
| `0x006D` | `H'BD85 -> H'BDBF` | `H'3015` | COPY in-progress/start refresh |
|
||||||
|
| `0x006E` | `H'BD8A/H'BD8F -> H'BDBF` | `H'2CA6` | special-accepted but selector dispatch is currently no-op |
|
||||||
|
|
||||||
|
The duplicate `0x006E` compare at `H'BD8A` and `H'BD8F` appears redundant in the current decode.
|
||||||
|
|
||||||
|
Selector-table math for the special group:
|
||||||
|
|
||||||
|
- Table base is `H'28A6`.
|
||||||
|
- `0x006C` indexes `H'297E`, whose pointer is `H'2FAF`.
|
||||||
|
- `0x006D` indexes `H'2980`, whose pointer is `H'3015`.
|
||||||
|
- `0x006E` indexes `H'2982`, whose pointer is `H'2CA6`.
|
||||||
|
- `H'2CA6` clears `F769.7` and returns.
|
||||||
|
|
||||||
|
Latch-clear selectors:
|
||||||
|
|
||||||
|
| Selector | Gate | Effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `0x006B` | only if `F731.7` set | clears `F731.7` and `F790.7` |
|
||||||
|
| `0x0096` | only if `F731.7` set | clears `F731.7` and `F790.7` |
|
||||||
|
| `0x0097` | only if `F731.7` set | clears `F731.7` and `F790.7` |
|
||||||
|
| `0x00C6` | only if `F731.7` set | clears `F731.7` and `F790.7` |
|
||||||
|
| `0x00F8` | only if `F731.7` set | clears `F731.7` and `F790.7` |
|
||||||
|
|
||||||
|
Report consumption and cleanup:
|
||||||
|
|
||||||
|
- `H'BDC2-H'BDCC`: if `FAA2.3` was set by a queued report, increments `F9B5` and clears `F9B5.7`.
|
||||||
|
- `H'BDD0-H'BDD4`: clears `FAA3` and `FAA2`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- Command `5` is the closest thing to an ACK, but only inside the continuation side.
|
||||||
|
- Most command-5 selectors do not have selector-specific meaning; they just consume/clear the continuation state if one is live.
|
||||||
|
- `0x006C/0x006D/0x006E` are command-5 special cases because they enter the `F970` selector-processing queue.
|
||||||
|
- `0x006E` is accepted as special by the command handler, but its current selector-table target is `2CA6`, the no-op return path.
|
||||||
|
|
||||||
|
## Command 6: Continuation Secondary Write
|
||||||
|
|
||||||
|
Path `H'BDDB-H'BE03`:
|
||||||
|
|
||||||
|
- `H'BDDB-H'BDE5`: builds value from `F863/F864` and writes `E400[selector]`.
|
||||||
|
- `H'BDE9`: sets `EC00[selector].6`.
|
||||||
|
- `H'BDED-H'BDF7`: if `FAA2.3` was set by a queued report, increments `F9B5` and clears `F9B5.7`.
|
||||||
|
- `H'BDFB-H'BDFF`: clears `FAA3` and `FAA2`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- Command `6` is the secondary feature/status table write used by menu visibility and gate code.
|
||||||
|
- It does not append the selector to `F970`, so it changes later decisions but does not itself force immediate selector dispatch.
|
||||||
|
|
||||||
|
## Command 7: Retransmit
|
||||||
|
|
||||||
|
Path `H'BE05-H'BE25`:
|
||||||
|
|
||||||
|
- Copies previous finalized TX bytes `F858-F85C` into staging bytes `F850-F854`.
|
||||||
|
- Loads `F9C0=0x1F`.
|
||||||
|
- Calls `BA26` to finalize/send again.
|
||||||
|
- `BA26` then waits for `F9C0 == 0`, reloads `F9C0=0x64`, reloads `F9C4=0x07`, copies staging bytes back to `F858-F85C`, recomputes the checksum into `F85D`, and starts SCI1 TX.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- Command `7` repeats the previous finalized TX frame.
|
||||||
|
- It is valid from both initial and continuation dispatch.
|
||||||
|
- It does not acknowledge/consume `F870` report queue entries by itself and does not clear `FAA2/FAA3` in this handler.
|
||||||
|
- The copied old checksum byte is not trusted; `BA26` recomputes the checksum.
|
||||||
|
|
||||||
|
## Distinct `07` Error Echo Path
|
||||||
|
|
||||||
|
The ROM also emits `07...` frames from an error/retry path that is separate from explicit command `7`.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
- SCI/RX physical error latched in `FAA4.7`.
|
||||||
|
- Checksum mismatch at `H'BBF0`.
|
||||||
|
|
||||||
|
Path `H'BE29-H'BE6A`:
|
||||||
|
|
||||||
|
- Clears `FAA4.7`.
|
||||||
|
- Requires `FAA5.7`; if the session gate is absent, no echo is sent.
|
||||||
|
- Increments retry counter `FAA6`.
|
||||||
|
- For the first two retry attempts, stages:
|
||||||
|
|
||||||
|
```text
|
||||||
|
byte0 = 0x07
|
||||||
|
byte1 = RX[1]
|
||||||
|
byte2 = RX[2]
|
||||||
|
byte3 = RX[3]
|
||||||
|
byte4 = RX[4]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Calls `BA26` to send the error echo.
|
||||||
|
- After too many retries, loads `F9C0=0x1F` and clears `FAA3/FAA2` instead of sending another echo.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- A `07 ...` frame is not automatically a normal command-7 retransmit.
|
||||||
|
- Old captures made with the wrong serial parity can easily be retry/error echoes.
|
||||||
|
- Valid frames clear `FAA6` at `H'BBF3`.
|
||||||
|
|
||||||
|
## Bench Implications
|
||||||
|
|
||||||
|
- To update active selector state during a report window, use command `4` or `6` while `FAA2 != 0`.
|
||||||
|
- To consume a queued report without changing tables, command `5` is the cleanest continuation ACK shape.
|
||||||
|
- To trigger COPY side effects, command `5` with `0x006D` starts/refreshes `COPY IN PROGRESS`, and command `5` with `0x006C` completes/exits only while the copy flags are live.
|
||||||
|
- Do not treat command `7` as an ACK. It is a repeat probe.
|
||||||
|
- A low command `0/1/2` sent while `FAA2.3` is live may clear the report continuation and then run as an initial command, which can make timing-sensitive bench traces look like an ACK plus a new command in one frame.
|
||||||
154
docs/pt2-copy-state-machine.md
Normal file
154
docs/pt2-copy-state-machine.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# PT2 Copy State Machine
|
||||||
|
|
||||||
|
This is a focused reference for the COPY behavior seen on the RCP LCD and traced in the ROM.
|
||||||
|
|
||||||
|
## Known Entry Points
|
||||||
|
|
||||||
|
### Serial Start / Progress
|
||||||
|
|
||||||
|
Frame:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 6D 00 00 32
|
||||||
|
00 00 6D 00 00 37 ; family-00 set/queue form to test
|
||||||
|
```
|
||||||
|
|
||||||
|
ROM path:
|
||||||
|
|
||||||
|
- Command 5 accepts selector `0x006D` at `BD80-BDBF`.
|
||||||
|
- The selector is queued through `BE70`.
|
||||||
|
- Selector `0x006D` dispatches to `H'3015`.
|
||||||
|
|
||||||
|
Observed effects from forced decode:
|
||||||
|
|
||||||
|
- Sets `F731.7`.
|
||||||
|
- Sets `F795.6/F795.7`.
|
||||||
|
- Loads `F798=H'C8`.
|
||||||
|
- Sets display selector `F732=H'1903`.
|
||||||
|
- Sets `FB02=H'64`.
|
||||||
|
- Calls the display/report bridge at `48FA`.
|
||||||
|
- Sets `F76E.6`.
|
||||||
|
|
||||||
|
LCD dispatch:
|
||||||
|
|
||||||
|
- `F732` high byte `0x19` selects `493E[0x19] -> H'930A`.
|
||||||
|
- The local table at `H'931C` maps substate `0x03` to `H'9F6A`.
|
||||||
|
- `H'9F6A` builds `COPY` / `IN PROGRESS`.
|
||||||
|
|
||||||
|
### Serial Complete / Exit
|
||||||
|
|
||||||
|
Frame:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 6C 00 00 33
|
||||||
|
00 00 6C 00 00 36 ; family-00 set/queue form observed during active report drain
|
||||||
|
```
|
||||||
|
|
||||||
|
ROM path:
|
||||||
|
|
||||||
|
- Command 5 accepts selector `0x006C` at `BD80-BDBF`.
|
||||||
|
- The selector is queued through `BE70`.
|
||||||
|
- Selector `0x006C` dispatches to `H'2FAF`.
|
||||||
|
|
||||||
|
Observed effects from forced decode:
|
||||||
|
|
||||||
|
- Manipulates `F76E`, `F795`, `F797`, and `F799`.
|
||||||
|
- Can set display selector `F732=H'1904`.
|
||||||
|
- Sets `FB02=H'14`.
|
||||||
|
- Calls the display/report bridge at `48FA`.
|
||||||
|
|
||||||
|
LCD dispatch:
|
||||||
|
|
||||||
|
- `F732` high byte `0x19` selects `493E[0x19] -> H'930A`.
|
||||||
|
- The local table at `H'931C` maps substate `0x04` to `H'9FDA`.
|
||||||
|
- `H'9FDA` builds `COPY` / `COMPLETED`.
|
||||||
|
|
||||||
|
### RCP-Side Menu Start
|
||||||
|
|
||||||
|
ROM path:
|
||||||
|
|
||||||
|
- OTHERS menu page: `493E[0x01] -> H'631C`.
|
||||||
|
- Local page table: `H'632E`.
|
||||||
|
- `COPY TO SLAVES` entry handler: `H'6FF0`.
|
||||||
|
|
||||||
|
Required gates:
|
||||||
|
|
||||||
|
- The entry descriptor before `H'6FF0` requires `E400[0x0015] != 0`.
|
||||||
|
- The local COPY action branch requires `F770.2` and `F791.7`.
|
||||||
|
|
||||||
|
Local branch effects:
|
||||||
|
|
||||||
|
- Sets `F76E.6`.
|
||||||
|
- Sets `F795.7`.
|
||||||
|
- Sets `F731.7`.
|
||||||
|
- Loads `F798=H'C8`.
|
||||||
|
- Sets `F711.7`.
|
||||||
|
- Loads `F726=H'64`.
|
||||||
|
- Calls `loc_5500`.
|
||||||
|
- Displays `COPY TO SLAVES`.
|
||||||
|
|
||||||
|
If `F770.2` is set while `F791.7` is clear, the ROM diverts through `H'704C` to a `SET RCP` / `MASTER` display path instead of starting copy.
|
||||||
|
|
||||||
|
## Working State Model
|
||||||
|
|
||||||
|
| State | Likely indicators | Entry | Exit |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Idle / no copy | `F731.7` clear | Boot, timeout, or completion cleanup | `0x006D` or local COPY branch |
|
||||||
|
| Copy in progress | `F731.7`, `F795.6/F795.7`, `F798` live, `F732=H'1903` | `05 00 6D 00 00 32` or local COPY branch | `0x006C` in the live window, or timer expiry |
|
||||||
|
| Copy completed | `F732=H'1904` | `05 00 6C 00 00 33` while copy flags are live | Display/session timeout or next state update |
|
||||||
|
| Timeout / not active | `F731.7` cleared by timer path | `F797` or `F798` reaches zero | Normal CONNECT recovery traffic |
|
||||||
|
|
||||||
|
## Bench Results
|
||||||
|
|
||||||
|
Observed on the real panel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
006C alone -> CONNECT OK -> blank
|
||||||
|
006D alone -> CONNECT OK -> COPY IN PROGRESS -> CONNECT NOT ACT
|
||||||
|
006D -> 006C after 250 ms -> COPY IN PROGRESS -> COPY COMPLETED
|
||||||
|
006D -> 006C after 1.0-1.5 s -> COPY IN PROGRESS -> COPY COMPLETED
|
||||||
|
006D -> 006C after 2.0-2.5 s -> COPY IN PROGRESS -> CONNECT NOT ACT
|
||||||
|
006D repeated, then 006C -> COPY IN PROGRESS held longer -> COPY COMPLETED
|
||||||
|
006D repeated without 006C -> COPY IN PROGRESS -> CONNECT NOT ACT
|
||||||
|
```
|
||||||
|
|
||||||
|
Family-00 comparison run on 2026-05-27:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 00 6D 00 00 37 -> 00 00 6C 00 00 36
|
||||||
|
-> immediate 04 00 6D / 04 00 6C table readbacks
|
||||||
|
-> no command-5-like 01 00 02 / 02 00 04 response rhythm
|
||||||
|
|
||||||
|
05 00 6D 00 00 32 -> 05 00 6C 00 00 33
|
||||||
|
-> 01 00 02 responses after 006D
|
||||||
|
-> 02 00 04 responses after 006C
|
||||||
|
```
|
||||||
|
|
||||||
|
Webcam-confirmed LCD results from the same 2026-05-27 run:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 00 6D 00 00 37 -> COPY IN PROGRESS
|
||||||
|
00 00 6C 00 00 36 -> COPY COMPLETED
|
||||||
|
05 00 6D 00 00 32 -> COPY IN PROGRESS
|
||||||
|
05 00 6C 00 00 33 -> COPY COMPLETED
|
||||||
|
```
|
||||||
|
|
||||||
|
The 250 ms and 1000 ms gap variants both produced the same LCD sequence. This
|
||||||
|
means the family-00 selector/write form can reach the COPY LCD side effects, even
|
||||||
|
though its serial response rhythm is the table-readback rhythm rather than the
|
||||||
|
command-5 `01 00 02` / `02 00 04` rhythm.
|
||||||
|
|
||||||
|
Current interpretation:
|
||||||
|
|
||||||
|
- `0x006D` is a copy-start/progress-window refresh selector.
|
||||||
|
- `0x006C` is a completion/exit selector that only behaves cleanly while the copy window is live.
|
||||||
|
- The observed `00 00 6C 00 00 36` frame can display `COPY COMPLETED` when it is sent inside a live copy window, but should not be read as a stateless "show completed" command.
|
||||||
|
- The copy window is transient and timer-controlled.
|
||||||
|
- The panel does not treat `0x006C` as a stateless "show completed" command.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- What sets `F791.7` during normal CCU/RCP operation?
|
||||||
|
- What exact official PT2 name belongs to selectors `0x006C`, `0x006D`, and `0x006E`?
|
||||||
|
- Whether `0x006E` is a copy cancel/error sibling or an unrelated special selector.
|
||||||
|
- Whether the CCU sends repeated progress refreshes during a real COPY TO SLAVES operation.
|
||||||
78
docs/pt2-iris-mblack-link-rom-trace.md
Normal file
78
docs/pt2-iris-mblack-link-rom-trace.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# PT2 IRIS/M.BLACK LINK ROM Trace
|
||||||
|
|
||||||
|
This note records the ROM evidence for the bench-visible
|
||||||
|
`IRIS/M.BLACK LINK` lamp.
|
||||||
|
|
||||||
|
## Confirmed Host Trigger
|
||||||
|
|
||||||
|
The isolated bench trigger is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 00 13 40 00 09 ; command 0, selector 0x0013, value 0x4000
|
||||||
|
```
|
||||||
|
|
||||||
|
Command 0 mirrors nonzero selector writes into both `E000[selector]` and
|
||||||
|
`E800[selector]`, then queues the selector for internal processing. For this
|
||||||
|
frame that means:
|
||||||
|
|
||||||
|
```text
|
||||||
|
E000[0x0013] = 0x4000
|
||||||
|
E800[0x0013] = 0x4000
|
||||||
|
```
|
||||||
|
|
||||||
|
The current-table word is `E800 + 2 * 0x0013 = H'E826`.
|
||||||
|
|
||||||
|
## Selector Handler
|
||||||
|
|
||||||
|
The selector dispatch table maps selector `0x0013` to `H'2E06`.
|
||||||
|
|
||||||
|
Focused linear decode:
|
||||||
|
|
||||||
|
```text
|
||||||
|
H'2E06 BTST.W #15, @H'E826
|
||||||
|
set/clear F791.6 and F713.4
|
||||||
|
|
||||||
|
H'2E1E BTST.W #14, @H'E826
|
||||||
|
set/clear F791.5 and F716.7
|
||||||
|
```
|
||||||
|
|
||||||
|
Bench labels:
|
||||||
|
|
||||||
|
| Selector value | ROM latch bits | Visible result |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `0x8000` | `F791.6`, `F713.4` | far-right `SLAVE` lamp |
|
||||||
|
| `0x4000` | `F791.5`, `F716.7` | `IRIS/M.BLACK LINK` lamp |
|
||||||
|
| `0x0000` | clears both bit groups through `H'2E06` | both latch groups clear |
|
||||||
|
|
||||||
|
## Other Trigger Path
|
||||||
|
|
||||||
|
There is also a local panel-input path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
F006.7 / F6DB.7 -> H'200E -> H'E826 bit14 -> loc_3E54 queues selector 0x0013
|
||||||
|
```
|
||||||
|
|
||||||
|
At `H'200E`, the ROM checks `F6DB.7` and requires `F731 <= 3`. It then uses
|
||||||
|
`F791.5` as a current-state toggle:
|
||||||
|
|
||||||
|
- if `F791.5` is clear, it sets `H'E826.14`,
|
||||||
|
- if `F791.5` is set, it clears `H'E826.14`,
|
||||||
|
- in both cases it calls `loc_3E54` with selector `0x0013`.
|
||||||
|
|
||||||
|
This is not a separate lamp driver. It feeds the same `E800[0x0013].14`
|
||||||
|
state consumed by the selector handler.
|
||||||
|
|
||||||
|
## Practical Meaning
|
||||||
|
|
||||||
|
The strongest current model is:
|
||||||
|
|
||||||
|
- CCU/host can drive the lamp directly by sending selector `0x0013` value
|
||||||
|
`0x4000` through command 0.
|
||||||
|
- The panel can also toggle/report the same state through a local input lane,
|
||||||
|
but only when the relevant session/page gate is open.
|
||||||
|
- No other decoded selector currently appears to directly set the same
|
||||||
|
`F791.5` plus `F716.7` latch pair.
|
||||||
|
|
||||||
|
Generated semantics now label selector `0x0013`, `E800[0x0013]`, and the
|
||||||
|
`IRIS/M.BLACK LINK` / `SLAVE` bit meanings so pseudo-code output does not leave
|
||||||
|
this as a generic table write.
|
||||||
193
docs/pt2-iris-mblack-link-state-machine.md
Normal file
193
docs/pt2-iris-mblack-link-state-machine.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# PT2 IRIS/M.BLACK LINK State Machine
|
||||||
|
|
||||||
|
Date: 2026-05-27
|
||||||
|
|
||||||
|
This note records the bench-proven closed loop for the `IRIS/M.BLACK LINK`
|
||||||
|
button/lamp path.
|
||||||
|
|
||||||
|
## Short Answer
|
||||||
|
|
||||||
|
There is no current evidence that the CCU first sends a separate
|
||||||
|
"this function exists" capability command for `IRIS/M.BLACK LINK`.
|
||||||
|
|
||||||
|
The stronger model is:
|
||||||
|
|
||||||
|
1. The CCU/RCP session must be awake/active.
|
||||||
|
2. The CCU must service the RCP report queue.
|
||||||
|
3. The CCU is the authoritative owner of selector state.
|
||||||
|
4. When the RCP reports a local button intent, the CCU ACKs the report and
|
||||||
|
mirrors the resulting selector value back to the RCP.
|
||||||
|
|
||||||
|
For this control, selector `0x0013` bit `0x4000` is the
|
||||||
|
`IRIS/M.BLACK LINK` state.
|
||||||
|
|
||||||
|
## Proven Frames
|
||||||
|
|
||||||
|
Selector value frames:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 00 13 40 00 09 ; command 0, selector 0x0013, value 0x4000, active
|
||||||
|
00 00 13 00 00 49 ; command 0, selector 0x0013, value 0x0000, clear
|
||||||
|
```
|
||||||
|
|
||||||
|
Report ACK:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 13 00 00 4C ; command 5 ACK/continuation for selector 0x0013
|
||||||
|
```
|
||||||
|
|
||||||
|
Readback request:
|
||||||
|
|
||||||
|
```text
|
||||||
|
01 00 13 00 00 48 ; command 1 read selector 0x0013
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed readback shapes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
04 00 13 40 00 0D ; command-0 write response, selector 0x0013 active
|
||||||
|
04 00 13 00 00 4D ; command-0 write response, selector 0x0013 clear
|
||||||
|
04 13 00 40 00 0D ; command-1 readback response, selector 0x0013 active
|
||||||
|
04 13 00 00 00 4D ; command-1 readback response, selector 0x0013 clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## Successful Closed Loop
|
||||||
|
|
||||||
|
Capture:
|
||||||
|
|
||||||
|
```text
|
||||||
|
captures/iris-mblack-link-mirror-state-machine.txt
|
||||||
|
captures/iris-mblack-link-mirror-state-machine-result.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Scenario:
|
||||||
|
|
||||||
|
```text
|
||||||
|
scenarios/iris-mblack-link-mirror-state-machine.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The visible panel behavior was:
|
||||||
|
|
||||||
|
```text
|
||||||
|
press 1: lamp on
|
||||||
|
press 2: lamp off
|
||||||
|
press 3: lamp on
|
||||||
|
```
|
||||||
|
|
||||||
|
The serial behavior matched that cycle.
|
||||||
|
|
||||||
|
### Baseline Clear
|
||||||
|
|
||||||
|
```text
|
||||||
|
TX 00 00 13 00 00 49
|
||||||
|
RX 04 00 13 00 00 4D
|
||||||
|
```
|
||||||
|
|
||||||
|
### Press 1: Active
|
||||||
|
|
||||||
|
```text
|
||||||
|
RX 00 00 13 40 00 09
|
||||||
|
TX 05 00 13 00 00 4C ; ACK report
|
||||||
|
TX 00 00 13 40 00 09 ; mirror active back
|
||||||
|
RX 04 00 13 40 00 0D
|
||||||
|
TX 01 00 13 00 00 48 ; readback
|
||||||
|
RX 04 13 00 40 00 0D
|
||||||
|
```
|
||||||
|
|
||||||
|
### Press 2: Clear
|
||||||
|
|
||||||
|
```text
|
||||||
|
RX 00 00 13 00 00 49
|
||||||
|
TX 05 00 13 00 00 4C ; ACK report
|
||||||
|
TX 00 00 13 00 00 49 ; mirror clear back
|
||||||
|
RX 04 00 13 00 00 4D
|
||||||
|
TX 01 00 13 00 00 48 ; readback
|
||||||
|
RX 04 13 00 00 00 4D
|
||||||
|
```
|
||||||
|
|
||||||
|
### Press 3: Active Again
|
||||||
|
|
||||||
|
```text
|
||||||
|
RX 00 00 13 40 00 09
|
||||||
|
TX 05 00 13 00 00 4C
|
||||||
|
TX 00 00 13 40 00 09
|
||||||
|
RX 04 00 13 40 00 0D
|
||||||
|
TX 01 00 13 00 00 48
|
||||||
|
RX 04 13 00 40 00 0D
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interpretation
|
||||||
|
|
||||||
|
The RCP does not appear to treat the local button press as final local state.
|
||||||
|
It reports local intent to the CCU.
|
||||||
|
|
||||||
|
The CCU then:
|
||||||
|
|
||||||
|
1. ACKs the selector report with command `5`.
|
||||||
|
2. Applies the chosen state back to the RCP with command `0`.
|
||||||
|
|
||||||
|
The RCP uses the mirrored/current selector state to decide the next toggle
|
||||||
|
direction. This explains the earlier failed/incomplete behavior:
|
||||||
|
|
||||||
|
- Without mirroring `0x0013=0x4000` back, repeated presses could keep reporting
|
||||||
|
active because the RCP still believed the selector was clear.
|
||||||
|
- Once the CCU mirrored active back, the next press reported clear.
|
||||||
|
- Once the CCU mirrored clear back, the next press reported active again.
|
||||||
|
|
||||||
|
## Wakeup Versus Capability
|
||||||
|
|
||||||
|
The successful test also had active session traffic:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 00 00 80 80 5A ; active selector-zero keepalive/report
|
||||||
|
05 00 00 00 00 5F ; ACK for selector zero
|
||||||
|
```
|
||||||
|
|
||||||
|
That traffic looks like general session/connected behavior, not a
|
||||||
|
per-function enable for `IRIS/M.BLACK LINK`.
|
||||||
|
|
||||||
|
So the current working model is:
|
||||||
|
|
||||||
|
- `CONNECT: OK` / active rhythm opens the report path and keeps the panel from
|
||||||
|
falling back to `CONNECT: NOT ACT`.
|
||||||
|
- Selector `0x0013` state tells the RCP the current value of this specific
|
||||||
|
control/lamp.
|
||||||
|
- There may still be feature-specific gates for other controls, but this test
|
||||||
|
did not require a distinct `IRIS/M.BLACK LINK exists` command.
|
||||||
|
|
||||||
|
## ROM Correlation
|
||||||
|
|
||||||
|
The ROM trace in `docs/pt2-iris-mblack-link-rom-trace.md` matches the bench
|
||||||
|
behavior:
|
||||||
|
|
||||||
|
```text
|
||||||
|
F006.7 / F6DB.7 -> H'200E -> H'E826 bit14 -> loc_3E54 queues selector 0x0013
|
||||||
|
```
|
||||||
|
|
||||||
|
At `H'200E`, the ROM reads `F791.5` as the current `IRIS/M.BLACK LINK` state:
|
||||||
|
|
||||||
|
- if `F791.5` is clear, the local press queues/set reports `0x0013=0x4000`;
|
||||||
|
- if `F791.5` is set, the local press queues/clear reports `0x0013=0x0000`.
|
||||||
|
|
||||||
|
The selector handler for `0x0013` updates `F791.5` from `E800[0x0013].14`.
|
||||||
|
That is why mirroring command-0 selector state back to the RCP completes the
|
||||||
|
toggle loop.
|
||||||
|
|
||||||
|
## Implications
|
||||||
|
|
||||||
|
For a CCU emulator:
|
||||||
|
|
||||||
|
- Maintain an authoritative selector table.
|
||||||
|
- Treat RCP button reports as requested state changes, not just notifications.
|
||||||
|
- ACK each report with command `5`.
|
||||||
|
- Write the accepted selector value back with command `0`.
|
||||||
|
- Keep the selector-zero/session rhythm alive separately.
|
||||||
|
|
||||||
|
For other latched buttons:
|
||||||
|
|
||||||
|
- The same pattern is likely: RCP emits a selector report, then expects the CCU
|
||||||
|
to mirror the accepted state back.
|
||||||
|
- A button that only reports "active" repeatedly may not be broken; it may be
|
||||||
|
waiting for the CCU to update the selector state that controls its next
|
||||||
|
toggle direction.
|
||||||
|
|
||||||
175
docs/pt2-knee-rom-trace.md
Normal file
175
docs/pt2-knee-rom-trace.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# PT2 KNEE ROM Trace
|
||||||
|
|
||||||
|
Date: 2026-05-26
|
||||||
|
|
||||||
|
## Short Version
|
||||||
|
|
||||||
|
KNEE is not a simple held selector bit. The ROM has a dedicated local panel-input handler at `loc_1795`, reached when the panel input word at `F104` changes. That handler uses CCU-side selector state as gates, then either shows a short timed KNEE page or reports a KNEE value change on selector `0x00BC`.
|
||||||
|
|
||||||
|
This explains the bench pattern where KNEE AUTO can light or flash and then clear even while serial traffic remains healthy: one ROM path is a timed display/page override, not a maintained lamp value.
|
||||||
|
|
||||||
|
## Input Path
|
||||||
|
|
||||||
|
The local panel path is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
F104 changes -> F692 updated -> F6F0.1 set -> main dispatcher calls loc_1795
|
||||||
|
```
|
||||||
|
|
||||||
|
The important code anchors are:
|
||||||
|
|
||||||
|
```text
|
||||||
|
3B33 MOV:G.W @H'F104, R0
|
||||||
|
3B37 CMP:G.W @H'F692, R0
|
||||||
|
3B3D BSET.B #1, @H'F6F0
|
||||||
|
3B41 MOV:G.W R0, @H'F692
|
||||||
|
|
||||||
|
1630 BCLR.B #1, @H'F6F0
|
||||||
|
1636 JSR @loc_1795
|
||||||
|
```
|
||||||
|
|
||||||
|
So serial writes can prepare the state table, but the main KNEE control branch appears to require the panel-side input lane to change.
|
||||||
|
|
||||||
|
## Handler Decision Table
|
||||||
|
|
||||||
|
`loc_1795` reads two words from the primary selector table:
|
||||||
|
|
||||||
|
| ROM read | Selector meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| `@H'E172` | `E000[0x00B9]` |
|
||||||
|
| `@H'E220` | `E000[0x0110]` |
|
||||||
|
|
||||||
|
The branch is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1795 if F731 > 2: skip
|
||||||
|
179C if E000[0x00B9].13 == 0: loc_2127()
|
||||||
|
17A7 else if E000[0x0110].15 == 1: loc_2127()
|
||||||
|
17B2 else:
|
||||||
|
delta = F692 - F6B2
|
||||||
|
report/update selector 0x00BC via loc_19A2
|
||||||
|
17C0 F6B2 = F692
|
||||||
|
```
|
||||||
|
|
||||||
|
Practical interpretation:
|
||||||
|
|
||||||
|
| Condition | ROM effect | Current meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `0x00B9.13 = 0` | timed KNEE page via `loc_2127` | KNEE value reporting not enabled |
|
||||||
|
| `0x00B9.13 = 1`, `0x0110.15 = 1` | timed KNEE page via `loc_2127` | override/inhibit mode |
|
||||||
|
| `0x00B9.13 = 1`, `0x0110.15 = 0` | value delta reported as selector `0x00BC` | likely live KNEE control/report lane |
|
||||||
|
|
||||||
|
This downgrades the earlier bench label that treated `0x00B9.15` as the main KNEE gate. The ROM gate for the panel-input handler is `0x00B9.13`.
|
||||||
|
|
||||||
|
## Timed KNEE Page
|
||||||
|
|
||||||
|
`loc_2127` is the timed page path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
2127 set FB03.7
|
||||||
|
212D save old F732 into F734, if this is a fresh override
|
||||||
|
2135 F732 = 0x1C03
|
||||||
|
213B FB02 = 0x14
|
||||||
|
2140 call loc_48FA
|
||||||
|
```
|
||||||
|
|
||||||
|
The timer path later decrements `FB02`; when it expires, `loc_48EF` restores `F732` from `F734` and redraws. That is the likely cause of "lights, then falls away" observations.
|
||||||
|
|
||||||
|
The LCD dispatcher confirms `F732=0x1C03` is a KNEE page:
|
||||||
|
|
||||||
|
```text
|
||||||
|
493E[0x1C] -> 0x92CC
|
||||||
|
0x92DE[3] -> 0x95CE
|
||||||
|
0x960B prints "KNEE"
|
||||||
|
```
|
||||||
|
|
||||||
|
On that KNEE page, the second line is selected like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
if E000[0x0110].15: "DL"
|
||||||
|
else if E000[0x00B9].15: "PRESET"
|
||||||
|
else: "AUTO"
|
||||||
|
```
|
||||||
|
|
||||||
|
So `0x00B9.15` still matters, but it appears to choose the KNEE page label (`PRESET` versus `AUTO`) rather than enabling the report path. `0x0110.15` selects the `DL` label and also forces the timed page path from `loc_1795`.
|
||||||
|
|
||||||
|
## Bench Implications
|
||||||
|
|
||||||
|
Useful frames to test this model:
|
||||||
|
|
||||||
|
| Purpose | Frame |
|
||||||
|
| --- | --- |
|
||||||
|
| clear `0x00B9` | `00 01 39 00 00 62` |
|
||||||
|
| set `0x00B9.13` gate | `00 01 39 20 00 42` |
|
||||||
|
| set `0x00B9.15` label bit | `00 01 39 80 00 E2` |
|
||||||
|
| set `0x00B9.15` and `.13` | `00 01 39 A0 00 C2` |
|
||||||
|
| clear `0x0110` | `00 01 90 00 00 CB` |
|
||||||
|
| set `0x0110.15` override | `00 01 90 80 00 4B` |
|
||||||
|
| read `0x00BC` | `01 01 3C 00 00 66` |
|
||||||
|
|
||||||
|
The most direct live-control test is:
|
||||||
|
|
||||||
|
1. Reach `CONNECT: OK`.
|
||||||
|
2. Send `00 01 39 20 00 42` to set `0x00B9.13`.
|
||||||
|
3. Send `00 01 90 00 00 CB` to clear `0x0110.15`.
|
||||||
|
4. Move or exercise the physical KNEE-related control, if available.
|
||||||
|
5. Watch for TX/report traffic around selector `0x00BC`.
|
||||||
|
|
||||||
|
If no physical KNEE input is moved, the ROM may not enter `loc_1795`, because the trigger is the `F104` panel input change path rather than the serial write itself.
|
||||||
|
|
||||||
|
## Bench Observation: DTL / KNEE
|
||||||
|
|
||||||
|
The first `knee-rom-gate-and-value-probe` bench run produced a new LCD state with `DTL` on the left and `KNEE` on the right. That is an important confirmation: page `0x1C` contains both a KNEE entry and a DETAIL entry.
|
||||||
|
|
||||||
|
Relevant ROM text and page entries:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x92DE[3] -> 0x95CE KNEE page entry
|
||||||
|
0x92DE[7] -> 0x97C8 DETAIL page entry
|
||||||
|
0x960B prints "KNEE"
|
||||||
|
0x9805 prints "DETAIL"
|
||||||
|
```
|
||||||
|
|
||||||
|
The bench display probably shows the page-0x1C menu neighborhood, with DETAIL abbreviated as `DTL` and KNEE as the selected/right-side item. The log does not encode LCD pixels, but the serial timing around the observation is consistent with the KNEE probe:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 01 39 20 00 42 ; set 0x00B9.13 gate
|
||||||
|
00 01 39 A0 00 C2 ; set 0x00B9.15 + 0x00B9.13
|
||||||
|
00 01 90 80 00 4B ; set 0x0110.15 timed KNEE/DL override
|
||||||
|
```
|
||||||
|
|
||||||
|
Next isolation target: determine whether `DTL/KNEE` appears from `0x00B9.13`, `0x00B9.A0`, `0x0110.15`, or only the combined sequence.
|
||||||
|
|
||||||
|
## Panel Correlation
|
||||||
|
|
||||||
|
The physical panel has both DETAIL and KNEE buttons near the LCD. That matches the ROM page-`0x1C` neighborhood and makes the observed `DTL/KNEE` state likely to be a local menu/button context rather than a generic lamp status.
|
||||||
|
|
||||||
|
Practical implication: once the CCU-side selector gates are prepared, pressing the physical DETAIL/KNEE buttons may be the missing `F104` panel-input transition that calls `loc_1795`. If so, the useful evidence should be:
|
||||||
|
|
||||||
|
- visible LCD movement between DETAIL and KNEE entries,
|
||||||
|
- possible KNEE timed page redraws,
|
||||||
|
- TX/report traffic for selector `0x00BC` when KNEE input movement is accepted,
|
||||||
|
- or other page-`0x1C` selector reports from neighboring DETAIL/KNEE controls.
|
||||||
|
|
||||||
|
## Follow-Up: Lamp Without LCD Movement
|
||||||
|
|
||||||
|
The follow-up run after discovering the DETAIL/KNEE buttons did not reproduce an LCD page change. The observed result was:
|
||||||
|
|
||||||
|
- only the later KNEE cases/windows illuminated the KNEE AUTO lamp,
|
||||||
|
- none of those cases changed the LCD.
|
||||||
|
|
||||||
|
Current interpretation:
|
||||||
|
|
||||||
|
- `0x0110.15` is still the strongest KNEE AUTO lamp/status source.
|
||||||
|
- Serial table writes can light KNEE AUTO without necessarily entering the LCD DETAIL/KNEE page.
|
||||||
|
- The LCD path probably needs an additional local-display/menu condition, not just the CCU-side selector bits.
|
||||||
|
- The physical DETAIL/KNEE buttons may be scanned by the local panel path, but in this test they did not create a visible LCD transition or new serial report evidence.
|
||||||
|
|
||||||
|
This separates two related but distinct paths:
|
||||||
|
|
||||||
|
```text
|
||||||
|
KNEE AUTO lamp/status: mostly selector/table driven, strongest source 0x0110.15
|
||||||
|
DETAIL/KNEE LCD page: local page/menu context, likely page 0x1C plus panel/menu state
|
||||||
|
```
|
||||||
|
|
||||||
|
Next ROM target: trace the DETAIL and KNEE button scan bits through the page-`0x1C` menu dispatcher, not just the `loc_1795` KNEE value handler.
|
||||||
157
docs/pt2-known-button-rom-trace.md
Normal file
157
docs/pt2-known-button-rom-trace.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# PT2 Known Button ROM Trace
|
||||||
|
|
||||||
|
This trace starts from known serial-visible button reports and walks backward into the panel input scanner.
|
||||||
|
|
||||||
|
Known reports:
|
||||||
|
|
||||||
|
- `00 00 07 80 00 DD` = selector `0x0007`, value `0x8000`, observed CAM POWER report.
|
||||||
|
- `00 00 15 80 00 CF` = selector `0x0015`, value `0x8000`, observed CALL active report.
|
||||||
|
- `00 00 15 00 00 4F` = selector `0x0015`, value `0x0000`, observed CALL inactive report.
|
||||||
|
|
||||||
|
## Shared Button Path
|
||||||
|
|
||||||
|
CALL and CAM POWER both use the same general panel edge machinery:
|
||||||
|
|
||||||
|
```text
|
||||||
|
external panel byte snapshot
|
||||||
|
-> shadow byte F6D*
|
||||||
|
-> dirty byte F6F2/F6F3
|
||||||
|
-> main scanner loc_15E0
|
||||||
|
-> bit dispatcher loc_1C0E
|
||||||
|
-> table H'2706
|
||||||
|
-> per-button handler
|
||||||
|
-> loc_3E54 serial report queue
|
||||||
|
```
|
||||||
|
|
||||||
|
That means the lamps and buttons are not just conceptually related. The ROM reads panel byte shadows and drives panel output masks through the same external panel-chip area, then uses per-button handlers to decide whether a press should become a serial report, a local menu action, or nothing.
|
||||||
|
|
||||||
|
## CAM POWER
|
||||||
|
|
||||||
|
Trace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
F105 external/panel byte changes
|
||||||
|
-> F6D4 shadow changes
|
||||||
|
-> F6F2.4 dirty
|
||||||
|
-> loc_1BA0
|
||||||
|
-> loc_1C0E table slot H'274C
|
||||||
|
-> handler H'1F40
|
||||||
|
-> writes E80E
|
||||||
|
-> queues selector 0x0007 through loc_3E54
|
||||||
|
```
|
||||||
|
|
||||||
|
Important handler behavior:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1F40: tests F6D4.3
|
||||||
|
1F4C: tests E000[0x0007] / E00E bit 15
|
||||||
|
1F52: writes E800[0x0007] / E80E = 0x8000
|
||||||
|
1F61: can write E80E = 0x0000 when already set and page gate allows
|
||||||
|
1F68: R3 = 0x0007
|
||||||
|
1F6B: BSR loc_3E54
|
||||||
|
```
|
||||||
|
|
||||||
|
So CAM POWER is a real local button path, not a host-only selector. The handler checks the current button level and local/session gates before emitting.
|
||||||
|
|
||||||
|
## CALL
|
||||||
|
|
||||||
|
Trace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
F006 external/panel byte changes
|
||||||
|
-> F6DB shadow changes
|
||||||
|
-> F6F3.3 dirty
|
||||||
|
-> loc_1BF8
|
||||||
|
-> loc_1C0E table slot H'27C0
|
||||||
|
-> handler H'20A1
|
||||||
|
-> writes E82A
|
||||||
|
-> queues selector 0x0015 through loc_3E54
|
||||||
|
```
|
||||||
|
|
||||||
|
Important handler behavior:
|
||||||
|
|
||||||
|
```text
|
||||||
|
20A1: reads E000[0x0015] / E02A
|
||||||
|
20A5: tests F6DB.5
|
||||||
|
20AB: if pressed, sets bit 15
|
||||||
|
20AF: if released, clears bit 15
|
||||||
|
20B1: writes E800[0x0015] / E82A
|
||||||
|
20B7: R3 = 0x0015
|
||||||
|
20BA: BSR loc_3E54
|
||||||
|
```
|
||||||
|
|
||||||
|
This cleanly matches the bench readings: CALL has both active and inactive reports because the handler explicitly sets or clears bit 15 from the current button level.
|
||||||
|
|
||||||
|
## Divergence
|
||||||
|
|
||||||
|
Other buttons do follow the same input edge path, but many diverge after `loc_1C0E`:
|
||||||
|
|
||||||
|
- Some handlers are `H'1C25`, an immediate `RTS`, so that matrix position is ignored.
|
||||||
|
- Some handlers only change local menu/page state such as `F731`, `F732`, `FB03`, or `F798`.
|
||||||
|
- Some handlers queue reports, but only if session/menu gates like `F731`, `F730`, `F791`, or selector bits in `E000/E400` allow it.
|
||||||
|
- Several handlers queue different selectors such as `0x0083`, `0x008F`, `0x0093`, `0x009A`, `0x00B9`, `0x00F8`, etc.
|
||||||
|
|
||||||
|
The generated report `build/panel_button_trace.md` lists the current table-derived map.
|
||||||
|
|
||||||
|
## Bench Correlation: Common Queue Gate
|
||||||
|
|
||||||
|
The first common-gate button-report bench set used the queued-report ACK stream that exposed SHUTTER ON/OFF.
|
||||||
|
|
||||||
|
Confirmed positives:
|
||||||
|
|
||||||
|
| Physical button | Report selectors/value | ROM match |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| BARS | `0x0017 = 0x8000`, `0x0018 = 0x8000` | `F105/F6D4.2 -> H'1EDE -> 0x0017/0x0018` |
|
||||||
|
| IRIS AUTO | `0x009A = 0x0800` | `F006/F6DB.3 -> H'20BE -> 0x009A` |
|
||||||
|
|
||||||
|
Common-gate runs for STANDARD, MASTER, SLAVE, KNEE AUTO, and BLACK/FLARE FLARE did not differ from the no-button baseline. That is evidence against one universal "all buttons emit now" gate. Some controls likely need additional menu/session/secondary-table state, or are not represented as simple edge reports in this mode.
|
||||||
|
|
||||||
|
## F109 Shutter/Menu Cluster
|
||||||
|
|
||||||
|
A focused trace of the `F109 -> F6D0` input byte found local-key handlers that are close to the OTHERS/shutter/menu machinery:
|
||||||
|
|
||||||
|
| Source bit | Handler | Behavior |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `F6D0.1` | `H'2390` | Queues selector `0x0083`, unless `E000[0x0088].15` diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.2` | `H'2408` | Queues selector `0x0083`, unless `E000[0x0088].15` diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.3` | `H'24A9` | Queues selector `0x0083`, unless `E000[0x0088].15` diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.7` | `H'24E8` | Queues selector `0x008F` with local mask `0x8000` or `0x0000`; `E000[0x0088].14` diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.6` | `H'252E` | Queues selector `0x008F` with local mask `0x2000` or `0x0000`; `E000[0x0088].14` diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.4/F6D0.5` | `H'2574/H'25D4` | Uses selectors `0x0091/0x0092` and modal byte `F6F6`; `E000[0x0088].14` can divert to timed page `F732=0x1C01` |
|
||||||
|
|
||||||
|
This cluster explains why some adjacent controls may look like menu/display keys rather than ordinary reports. A CCU-side bit in selector `0x0088` can retarget them into a timed local page overlay, currently matching the DETAIL/KNEE neighborhood rather than the OTHERS page.
|
||||||
|
|
||||||
|
## OTHERS Difference
|
||||||
|
|
||||||
|
OTHERS page `0x01` is not driven by one of the simple confirmed report handlers above. The page-1 handlers consume low action bits in `F770`:
|
||||||
|
|
||||||
|
- `H'6EE4` consumes `F770.0/F770.1` for OTHERS/SHUTTER local actions.
|
||||||
|
- `H'6FF0` consumes `F770.2` for COPY TO SLAVES.
|
||||||
|
- No decoded direct writer for these low bits has been found yet.
|
||||||
|
|
||||||
|
That is the current best explanation for the bench result where the OTHERS physical press matched the no-button baseline: the shared scanner may see the key, but the useful OTHERS action is gated behind a separate local page/action latch.
|
||||||
|
|
||||||
|
## Emulator Probe
|
||||||
|
|
||||||
|
The emulator can inject these as ROM-level panel edges:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe h8536_emulator_rx_probe.py --wait-heartbeats 1 --panel-press cam-power
|
||||||
|
.\.venv\Scripts\python.exe h8536_emulator_rx_probe.py --panel call=press --panel call=release --keep-listening
|
||||||
|
.\.venv\Scripts\python.exe h8536_emulator_rx_probe.py --panel F6D4.6=press
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a post-scan injection model. It sets the external source byte, the `F6D*` shadow byte, the matching `F6E*` previous-shadow byte to the opposite bit level, and the `F6F2/F6F3` dirty bit. The ROM then runs its normal `loc_15E0 -> loc_1C0E -> H'2706` dispatch path, so gates and no-op handlers still behave like firmware rather than like forced serial output.
|
||||||
|
|
||||||
|
## Tooling
|
||||||
|
|
||||||
|
Regenerate the map with:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe h8536_panel_button_trace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
- `build/panel_button_trace.md`
|
||||||
|
- `build/panel_button_trace.json`
|
||||||
241
docs/pt2-lamp-selector-map.md
Normal file
241
docs/pt2-lamp-selector-map.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# PT2 Lamp And Panel Output Selector Map
|
||||||
|
|
||||||
|
This note tracks bench-visible lamp/readout effects from CCU-to-RCP selector writes.
|
||||||
|
|
||||||
|
## Current Model
|
||||||
|
|
||||||
|
The panel lamps and seven-segment displays are driven by selector-table state, not by one monolithic "connected" flag. Command 0 writes into the primary/current tables, and several selectors immediately affect visible panel outputs while `CONNECT: OK` is alive.
|
||||||
|
|
||||||
|
Known active-state foundation:
|
||||||
|
|
||||||
|
- `E000[0x0000] = 0x8080` wakes/holds `CONNECT: OK`.
|
||||||
|
- `E000[0x008F]` drives shutter `EVS`/`OFF` style display state and iris AUTO side effects.
|
||||||
|
- `E000[0x0093]` drives at least white-balance and black/flare lamp state.
|
||||||
|
|
||||||
|
## Bench Observations 2026-05-26
|
||||||
|
|
||||||
|
### `lamp-0093-lowbyte-sweep`
|
||||||
|
|
||||||
|
Result: no new visible behavior beyond the already-known `0x0093` family.
|
||||||
|
|
||||||
|
Interpretation:
|
||||||
|
|
||||||
|
- The earlier `0x0093` mapping still stands.
|
||||||
|
- Individual low-byte probes inside a streamed `0x90xx` context did not reveal a new lamp in this run.
|
||||||
|
- Keep `0x9020` as a useful manual/baseline context and `0x90FF` / `0xFFFF` as black/flare AUTO positive controls.
|
||||||
|
|
||||||
|
### `lamp-known-button-selector-probe`
|
||||||
|
|
||||||
|
Visible result:
|
||||||
|
|
||||||
|
- CAM button/lamp flashed on/off.
|
||||||
|
- CALL lamp flashed on/off.
|
||||||
|
- BARS and MASTER lamps flashed on/off.
|
||||||
|
- Camera tally changed red, then later green.
|
||||||
|
- Each visible output illuminated by itself, not as a broad all-lamps blast.
|
||||||
|
|
||||||
|
Serial result:
|
||||||
|
|
||||||
|
- The run stayed in normal `CONNECT: OK` response cadence.
|
||||||
|
- Each command-0 write produced an immediate `04 ...` readback-style frame and repeated `02 00 02 00 00 5A` active responses.
|
||||||
|
|
||||||
|
Candidate mapping:
|
||||||
|
|
||||||
|
| Selector/value pair | Current meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| `0x0007 = 0x8000/0x0000` | CAM POWER lamp blink confirmed by isolated run |
|
||||||
|
| `0x0015 = 0x8000/0x0000` | CALL lamp blink confirmed; red tally also blinked in the same phase |
|
||||||
|
| `0x0012`, `0x0013`, `0x0016`, `0x0017`, `0x0018`, `0x001A` | ordered candidates for SLAVE, green tally, BARS, MASTER, and neighboring lamp states |
|
||||||
|
|
||||||
|
Follow-up `lamp-isolate-cam-call` result:
|
||||||
|
|
||||||
|
- First phase blinked CAM POWER.
|
||||||
|
- Second phase blinked CALL and red tally.
|
||||||
|
|
||||||
|
Follow-up `lamp-isolate-known-neighbors` result:
|
||||||
|
|
||||||
|
- Visible order was SLAVE, then green tally, then BARS.
|
||||||
|
- The pattern repeated, and at one point SLAVE and BARS were visible together.
|
||||||
|
- Treat the ordered mapping as likely but not final until a fresh-boot single-selector run separates latch/persistence effects from the selector under test.
|
||||||
|
|
||||||
|
Follow-up `lamp-isolate-neighbor-single-boot` result:
|
||||||
|
|
||||||
|
| Fresh-boot candidate | Visible result |
|
||||||
|
| --- | --- |
|
||||||
|
| `0x0012 = 0x8000/0x0000` | no visible change reported |
|
||||||
|
| `0x0013 = 0x8000/0x0000` | SLAVE lamp |
|
||||||
|
| `0x0016 = 0x8000/0x0000` | camera tally green |
|
||||||
|
| `0x0017 = 0x8000/0x0000` | BARS lamp |
|
||||||
|
| `0x0018 = 0x8000/0x0000` | no visible result reported yet |
|
||||||
|
| `0x001A = 0x8000/0x0000` | no visible result reported yet |
|
||||||
|
|
||||||
|
This confirms that the host/CCU can directly drive panel lamps through selector-table writes. It also validates using the ROM dispatch-neighbor list around `0x0007` and `0x0015` as a high-value lamp map.
|
||||||
|
|
||||||
|
Follow-up `panel-atlas-standard-master-*` webcam runs:
|
||||||
|
|
||||||
|
- `0x0012`, `0x0013`, and `0x0014` high-nibble/selected-bit sweeps did not
|
||||||
|
produce a clean STANDARD or MASTER lamp trigger. The `0x0013=0x8000` SLAVE
|
||||||
|
positive control still worked.
|
||||||
|
- `0x0010`, `0x0011`, `0x0015`, `0x0016`, `0x0017`, `0x0018`, `0x0019`, and
|
||||||
|
`0x001A` high-nibble sweeps did not produce a clean STANDARD or MASTER lamp
|
||||||
|
trigger.
|
||||||
|
- `0x0008` through `0x000F` high-nibble sweeps did not produce a clean
|
||||||
|
STANDARD or MASTER lamp trigger.
|
||||||
|
- `0x0017=0x4000` lit the same far-right bottom BARS lamp/latch as the known
|
||||||
|
`0x0017=0x8000` family. Later `0x0018` rows in that run were latch-contaminated
|
||||||
|
and need fresh-boot isolation before assigning a separate meaning.
|
||||||
|
|
||||||
|
Current implication: STANDARD and MASTER are probably not simple direct
|
||||||
|
command-0 high-nibble writes in the `0x0008`-`0x001A` pocket, despite the older
|
||||||
|
broad run that made MASTER flash. Treat that older observation as a real
|
||||||
|
bench-visible event but not yet an isolated selector mapping.
|
||||||
|
|
||||||
|
Later refinement: the ROM-derived fresh-boot output sweep found a clean
|
||||||
|
STANDARD trigger at `0x006B = 0x8000` (`00 00 6B 80 00 B1`). It also reclassed
|
||||||
|
the formerly vague `0x001A` pocket as the MONITOR selector cluster:
|
||||||
|
|
||||||
|
| Selector/value | Visible effect |
|
||||||
|
| --- | --- |
|
||||||
|
| `0x001A = 0x0808` | MONITOR ENC |
|
||||||
|
| `0x001A = 0x2020` | MONITOR B |
|
||||||
|
| `0x001A = 0x4040` | MONITOR G |
|
||||||
|
| `0x001A = 0x8080` | MONITOR R |
|
||||||
|
| `0x006B = 0x8000` | STANDARD |
|
||||||
|
|
||||||
|
### `panel-atlas-big-visual-sweep-0001-017f-highbits`
|
||||||
|
|
||||||
|
The broad webcam sweep and fresh-boot isolation pass produced these confirmed
|
||||||
|
or near-confirmed panel-output mappings:
|
||||||
|
|
||||||
|
| Selector/value | Current meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| `0x0013 = 0x4000` | `IRIS/M.BLACK LINK` lamp |
|
||||||
|
| `0x0024 = 0x8000` | LCD selector-button lamp |
|
||||||
|
| `0x0024 = 0x0000` | LCD selector-button lamp remained visible at 0.5 s; not a simple clear in this timing window |
|
||||||
|
| `0x0082 = 0x8000` | IRIS readout `OP` |
|
||||||
|
| `0x0082 = 0x4000` | IRIS readout `1.4` |
|
||||||
|
| `0x0082 = 0x0000` | IRIS readout blank |
|
||||||
|
| `0x0083 = 0x8000` | MASTER GAIN `-3`, SHUTTER `OFF`, and IRIS AUTO |
|
||||||
|
| `0x0083 = 0x0000` | same visible state remained at 0.5 s; likely latched/copied elsewhere |
|
||||||
|
| `0x0093 = 0x8000` | BLACK/FLARE MANUAL plus white-balance PRESET |
|
||||||
|
| `0x0093 = 0x4000` | BLACK/FLARE MANUAL plus white-balance AUTO |
|
||||||
|
| `0x0093 = 0x2000` | BLACK/FLARE MANUAL plus white-balance MANUAL |
|
||||||
|
| `0x0093 = 0x0000` | BLACK/FLARE MANUAL plus white-balance MANUAL |
|
||||||
|
|
||||||
|
Run health was good: no resync events, no dropped bytes, and command-4
|
||||||
|
readbacks appeared for the listed writes.
|
||||||
|
|
||||||
|
ROM trace now confirms the `0x0013` bit split:
|
||||||
|
|
||||||
|
- `E800[0x0013]` is `H'E826`.
|
||||||
|
- Selector `0x0013` dispatches to `H'2E06`.
|
||||||
|
- `H'E826.15` sets/clears `F791.6` and `F713.4`, matching SLAVE.
|
||||||
|
- `H'E826.14` sets/clears `F791.5` and `F716.7`, matching
|
||||||
|
`IRIS/M.BLACK LINK`.
|
||||||
|
- Local handler `H'200E` can also toggle `H'E826.14` from panel input
|
||||||
|
`F006.7/F6DB.7` when its session gate allows it.
|
||||||
|
|
||||||
|
Interpretation:
|
||||||
|
|
||||||
|
- `0x0082` is the cleanest direct readout lane found so far: set values update
|
||||||
|
the IRIS display and `0x0000` blanks it.
|
||||||
|
- `0x0083` appears to be a combined status/display lane rather than only the
|
||||||
|
MASTER GAIN display. Its `0x8000` state also brings up SHUTTER `OFF` and IRIS
|
||||||
|
AUTO, and the clear write did not visually clear it at 0.5 s.
|
||||||
|
- `0x0093` selects white-balance mode. In this run, `0x2000` and `0x0000` both
|
||||||
|
presented white-balance MANUAL, so bit 13 may be redundant in this context or
|
||||||
|
may need another gate to show a distinct state.
|
||||||
|
|
||||||
|
### `lamp-broad-status-selector-sweep`
|
||||||
|
|
||||||
|
Visible result:
|
||||||
|
|
||||||
|
- KNEE AUTO lamp flashed a few times.
|
||||||
|
- No other new visible result was reported.
|
||||||
|
- Follow-up isolation saw KNEE AUTO toward the end of the run, then blinking.
|
||||||
|
|
||||||
|
Candidate selectors in that run:
|
||||||
|
|
||||||
|
`0x0003`, `0x0040`, `0x0081`, `0x0092`, `0x00A7`, `0x00B7`, `0x00B9`, `0x0110`
|
||||||
|
|
||||||
|
Interpretation:
|
||||||
|
|
||||||
|
- KNEE AUTO is likely in this broader status cluster.
|
||||||
|
- Because the visible change happened toward the end, strongest next candidates are `0x00A7`, `0x00B7`, `0x00B9`, and `0x0110`, with `0x0092` kept as a guard candidate.
|
||||||
|
- Exact selector/value still needs isolation because the broad sweep changed several selectors in sequence.
|
||||||
|
|
||||||
|
Follow-up `lamp-isolate-knee-tail-single-boot` result:
|
||||||
|
|
||||||
|
| Fresh-boot candidate | Visible result |
|
||||||
|
| --- | --- |
|
||||||
|
| `0x0092` | iris AUTO/OFF behavior |
|
||||||
|
| `0x00A7` | no visible result reported |
|
||||||
|
| `0x00B7` | no visible result reported |
|
||||||
|
| `0x00B9` | KNEE AUTO |
|
||||||
|
| `0x0110` | KNEE AUTO |
|
||||||
|
|
||||||
|
Interpretation:
|
||||||
|
|
||||||
|
- `0x00B9` and `0x0110` are real KNEE-related selectors, but a ROM trace now shows they are not a simple OR-held lamp pair.
|
||||||
|
- `loc_1795` is the local KNEE/panel-input handler. It is reached from `F104 -> F692 -> F6F0.1`, then reads `E000[0x00B9]` and `E000[0x0110]`.
|
||||||
|
- The ROM gate for the live KNEE value/report branch is `0x00B9.13`, not `0x00B9.15`.
|
||||||
|
- `0x0110.15` forces a timed KNEE page/display override. That fits the observed "lights, then clears" behavior.
|
||||||
|
- `0x00B9.15` still matters on the timed KNEE LCD page: it selects the `PRESET` label when `0x0110.15` is clear. With both clear, the same page labels KNEE as `AUTO`.
|
||||||
|
- When `0x00B9.13` is set and `0x0110.15` is clear, the ROM computes `F692 - F6B2` and reports/updates selector `0x00BC`. That is now the strongest candidate for the KNEE control value lane.
|
||||||
|
|
||||||
|
Follow-up `lamp-knee-or-precedence` result:
|
||||||
|
|
||||||
|
- Case 1 (`0x00B9.15` set, then `0x0110.15` set, then `0x00B9` cleared) kept KNEE AUTO on until near the end, when `0x0110` was cleared.
|
||||||
|
- Case 2 (`0x0110.15` set, then `0x00B9.15` set, then `0x0110` cleared) turned KNEE AUTO off well before the end, even though `0x00B9` had been set.
|
||||||
|
- This argues against a simple OR model. Current best interpretation: `0x0110.15` is the stronger live display/control source for KNEE AUTO; `0x00B9.15` is related, but may be a transient, secondary status source, or only meaningful with another gate active.
|
||||||
|
|
||||||
|
Follow-up `lamp-knee-sustain-compare` result:
|
||||||
|
|
||||||
|
- Repeated `0x00B9.15` refresh never lit KNEE AUTO.
|
||||||
|
- Repeated `0x0110.15` refresh lit KNEE AUTO, but it turned off again around the middle of the repeated-refresh window.
|
||||||
|
- This makes `0x00B9.15` look like a context-sensitive or stale interpretation rather than a maintained lamp source.
|
||||||
|
- `0x0110.15` remains the best KNEE AUTO source, but it is not sufficient by itself to hold the lamp. It likely needs a surrounding CCU status refresh, or another selector periodically clears/rebuilds the visible lamp bank.
|
||||||
|
|
||||||
|
Follow-up `lamp-knee-context-hold` result:
|
||||||
|
|
||||||
|
- Pairing `0x00B9.15` with the known `0x0093=0x9020` active refresh did not light KNEE AUTO.
|
||||||
|
- Pairing `0x0110.15` with `0x0093=0x9020` did light KNEE AUTO, but it still turned off around the middle of the run.
|
||||||
|
- The serial log stayed clean: repeated table readbacks and `02 00 02 00 00 5A` active responses continued, with no resync or NOT ACT-style serial collapse.
|
||||||
|
- Current best hypothesis: `0x0110.15` behaves more like an edge/pulse or consumed display request than a pure level-held lamp bit. Repeating the same high value may not retrigger it after the display task has consumed the state.
|
||||||
|
|
||||||
|
ROM follow-up:
|
||||||
|
|
||||||
|
- Detailed notes: `docs/pt2-knee-rom-trace.md`.
|
||||||
|
- The timed path sets `F732=0x1C03`, `FB02=0x14`, calls `loc_48FA`, and later restores the previous page through `loc_48EF`.
|
||||||
|
- `F732=0x1C03` dispatches to a KNEE LCD page. Its second line is `DL` when `0x0110.15` is set, `PRESET` when `0x0110.15` is clear and `0x00B9.15` is set, otherwise `AUTO`.
|
||||||
|
- `knee-rom-gate-and-value-probe` produced a new bench LCD state with `DTL` on the left and `KNEE` on the right. This matches the ROM page-0x1C neighborhood: KNEE entry at `0x95CE`, DETAIL entry at `0x97C8`.
|
||||||
|
- The panel physically has DETAIL and KNEE buttons near the LCD, so this is likely a local menu/button context. Pressing those buttons during the prepared gate window may supply the `F104` transition that the ROM needs before it calls `loc_1795`.
|
||||||
|
- Follow-up isolation lit KNEE AUTO in the later KNEE cases/windows but did not change the LCD. Treat the KNEE AUTO lamp and the DETAIL/KNEE LCD page as related but separate paths: `0x0110.15` remains the strongest lamp/status source, while LCD movement likely needs an additional local menu/display gate.
|
||||||
|
- Next bench retest should include `0x00B9.13` (`00 01 39 20 00 42`) and the `0x00BC` report/read lane, not only the older `0x00B9.15` / `0x0110.15` pair.
|
||||||
|
|
||||||
|
## Follow-Up Isolation Scenarios
|
||||||
|
|
||||||
|
Run these with the console visible and record the exact label shown when each lamp changes:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-isolate-cam-call.json --parity E --log captures\lamp-isolate-cam-call.txt --result-json captures\lamp-isolate-cam-call-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-isolate-known-neighbors.json --parity E --log captures\lamp-isolate-known-neighbors.txt --result-json captures\lamp-isolate-known-neighbors-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-isolate-knee-status-selectors.json --parity E --log captures\lamp-isolate-knee-status-selectors.txt --result-json captures\lamp-isolate-knee-status-selectors-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-isolate-neighbor-single-boot.json --parity E --log captures\lamp-isolate-neighbor-single-boot.txt --result-json captures\lamp-isolate-neighbor-single-boot-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-isolate-knee-tail-single-boot.json --parity E --log captures\lamp-isolate-knee-tail-single-boot.txt --result-json captures\lamp-isolate-knee-tail-single-boot-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-isolate-knee-bit-scan.json --parity E --log captures\lamp-isolate-knee-bit-scan.txt --result-json captures\lamp-isolate-knee-bit-scan-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-knee-or-precedence.json --parity E --log captures\lamp-knee-or-precedence.txt --result-json captures\lamp-knee-or-precedence-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-knee-sustain-compare.json --parity E --log captures\lamp-knee-sustain-compare.txt --result-json captures\lamp-knee-sustain-compare-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-knee-context-hold.json --parity E --log captures\lamp-knee-context-hold.txt --result-json captures\lamp-knee-context-hold-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\lamp-knee-edge-refresh.json --parity E --log captures\lamp-knee-edge-refresh.txt --result-json captures\lamp-knee-edge-refresh-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\knee-rom-gate-and-value-probe.json --parity E --log captures\knee-rom-gate-and-value-probe.txt --result-json captures\knee-rom-gate-and-value-probe-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\knee-rom-dtl-knee-isolation.json --parity E --log captures\knee-rom-dtl-knee-isolation.txt --result-json captures\knee-rom-dtl-knee-isolation-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\knee-detail-physical-button-watch.json --parity E --log captures\knee-detail-physical-button-watch.txt --result-json captures\knee-detail-physical-button-watch-result.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Method notes:
|
||||||
|
|
||||||
|
- Record visible changes immediately during each labeled hold. Later `CONNECT: NOT ACT` cleanup is not selector evidence.
|
||||||
|
- If a selector causes a latch or unexpected mode, stop and keep the log instead of continuing the whole sweep.
|
||||||
|
- Prefer exact notes like `selector_0018_high -> tally red`, because the logs already preserve send timestamps and readback frames.
|
||||||
|
- For the single-boot follow-ups, each candidate gets a fresh power cycle. That is deliberate: it tests whether a lamp is truly driven by that selector rather than retained from a previous write.
|
||||||
276
docs/pt2-menu-state-machine.md
Normal file
276
docs/pt2-menu-state-machine.md
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
# PT2 Menu State Machine
|
||||||
|
|
||||||
|
This is a focused reference for the ROM menu/display selection machinery that drives LCD pages and panel soft-key activity.
|
||||||
|
|
||||||
|
## Page Dispatch
|
||||||
|
|
||||||
|
Primary display selector:
|
||||||
|
|
||||||
|
- `F732` holds the current display/page selector.
|
||||||
|
- The high byte of `F732` selects a page wrapper through `493E`.
|
||||||
|
- The low byte is used as a page-local substate/selection, commonly through `F733`.
|
||||||
|
|
||||||
|
Important dispatcher:
|
||||||
|
|
||||||
|
- `48FA` bridges table/report state into LCD page dispatch.
|
||||||
|
- `493E[page]` points at a wrapper.
|
||||||
|
- Wrappers pass a local table pointer to `5FD2`.
|
||||||
|
|
||||||
|
For COPY status:
|
||||||
|
|
||||||
|
- `F732=H'1903` selects page `0x19`, substate `0x03`, `COPY IN PROGRESS`.
|
||||||
|
- `F732=H'1904` selects page `0x19`, substate `0x04`, `COPY COMPLETED`.
|
||||||
|
|
||||||
|
For OTHERS:
|
||||||
|
|
||||||
|
- `493E[0x01] -> H'631C`.
|
||||||
|
- Local table `H'632E` includes the OTHERS pages.
|
||||||
|
|
||||||
|
## Local Entry Selection
|
||||||
|
|
||||||
|
`loc_5FD2` is the local menu chooser.
|
||||||
|
|
||||||
|
Important RAM:
|
||||||
|
|
||||||
|
| RAM | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `F72C` | visible/selectable entry bitmask |
|
||||||
|
| `F72E` | count of visible/selectable entries |
|
||||||
|
| `F72F` | cached page high byte |
|
||||||
|
| `F733` | selected local entry index |
|
||||||
|
| `FB03.7` | no-entry/error/display suppression flag |
|
||||||
|
| `FB02` | display/session timer or message timer |
|
||||||
|
|
||||||
|
Observed algorithm shape:
|
||||||
|
|
||||||
|
1. If the page changed, clear `F72C/F72E` and cache the new page in `F72F`.
|
||||||
|
2. Walk the page-local handler table from the last entry down.
|
||||||
|
3. For each handler, read descriptor words immediately before the handler.
|
||||||
|
4. Compare the entry state descriptor against `F731`.
|
||||||
|
5. If the entry has required selector descriptors, test `E400 + selector*2`.
|
||||||
|
6. If requirements pass, set the entry bit in `F72C` and increment `F72E`.
|
||||||
|
7. Clamp or reset `F733` so it points at a visible entry.
|
||||||
|
8. If no entries are visible, set `FB03.7`, set `FB02=H'14`, clear `F72F`, and return `R4=H'FFFE`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- A page can exist in ROM but be invisible/inactive until the CCU seeds the right `E400` feature/status selector.
|
||||||
|
- Command 6 writes the `E400-E7FF` secondary table, but only on the continuation side of the protocol.
|
||||||
|
|
||||||
|
## OTHERS / COPY TO SLAVES Gates
|
||||||
|
|
||||||
|
OTHERS page map:
|
||||||
|
|
||||||
|
| Local entry | Handler | Visible text | Required secondary selector |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | `H'6FF0` | `OTHERS` / `COPY TO SLAVES` | `E400[0x0015] != 0` |
|
||||||
|
| 2 | `H'70F6` | `OTHERS` / `CAM ID SET` | `E400[0x0043] != 0` |
|
||||||
|
| 3 | `H'7188` | `OTHERS` / `CAM ID IND` | `E400[0x0037] != 0` |
|
||||||
|
| 4 | `H'7258` | `OTHERS` / `CAM BARS` | `E400[0x0038] != 0` |
|
||||||
|
| 5-6 | `H'7328/H'73D8` | marker/percentage pages | `E400[0x0027] != 0` |
|
||||||
|
|
||||||
|
COPY TO SLAVES local action gate:
|
||||||
|
|
||||||
|
- Handler `H'6FF0` watches `F770.2`.
|
||||||
|
- If `F770.2` is clear, it only displays the OTHERS/COPY page.
|
||||||
|
- If `F770.2` is set and `F791.7` is set, it enters the local copy-start branch.
|
||||||
|
- If `F770.2` is set and `F791.7` is clear, it diverts to `SET RCP` / `MASTER`.
|
||||||
|
|
||||||
|
Root OTHERS soft-key bits:
|
||||||
|
|
||||||
|
- Root handler `H'6EE4` tests `E000[0x008F]` at `H'E11E`.
|
||||||
|
- Bit 11 sets `F711.6`.
|
||||||
|
- Bit 12 sets `F711.4`.
|
||||||
|
|
||||||
|
These bits are not only OTHERS soft-key enables. Bench tests show `E000[0x008F]` directly changes the shutter seven-segment display and iris AUTO lamp, so treat it as a packed camera/display status selector that also feeds the OTHERS root handler.
|
||||||
|
|
||||||
|
## Button / Lamp Masks
|
||||||
|
|
||||||
|
Important RAM:
|
||||||
|
|
||||||
|
| RAM | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `F711-F718` | panel output masks used by external panel chips |
|
||||||
|
| `F711.4-F711.7` | soft-key/menu-related bits seen around OTHERS/COPY |
|
||||||
|
| `F726` | countdown that temporarily preserves some soft-key bits |
|
||||||
|
| `F770` | local panel action/change code |
|
||||||
|
|
||||||
|
Relevant ROM behavior:
|
||||||
|
|
||||||
|
- Init clears `F711-F717` and sets `F718=H'FF`.
|
||||||
|
- `5A7A` clears `F711.4-F711.7` if `F726 == 0`.
|
||||||
|
- The FRT timer path decrements `F726`; when it expires, it clears `F713.6` and `F711.4-F711.7`.
|
||||||
|
- The OTHERS/COPY branch sets `F711.7` and `F726=H'64` to keep the local key/display state alive briefly.
|
||||||
|
|
||||||
|
## OTHERS Action Latch Trace
|
||||||
|
|
||||||
|
The missing OTHERS mechanism appears to be `F770`, not another simple command-0 selector.
|
||||||
|
|
||||||
|
Root OTHERS/SHUTTER handler `H'6EE4`:
|
||||||
|
|
||||||
|
- Reads `F770`, clears it, and only keeps low bits `0x01/0x02`.
|
||||||
|
- If the first action path is present and `E400[0x008F].11` is enabled, writes `E800[0x008F]=0x0800` and queues selector `0x008F`.
|
||||||
|
- If the second action path is present and `E400[0x008F].12` is enabled, writes `E800[0x008F]=0x1000` and queues selector `0x008F`.
|
||||||
|
|
||||||
|
COPY TO SLAVES handler `H'6FF0`:
|
||||||
|
|
||||||
|
- Reads `F770`, clears it, and only keeps bit `0x04`.
|
||||||
|
- If `F770.2` is clear, it only displays the OTHERS/COPY page.
|
||||||
|
- If `F770.2` is set and `F791.7` is set, it enters the local copy-start branch.
|
||||||
|
- If `F770.2` is set and `F791.7` is clear, it diverts to `SET RCP` / `MASTER` and stages restore selector `F734=0x0101`.
|
||||||
|
|
||||||
|
Static direct writers found so far only write high bits into `F770`:
|
||||||
|
|
||||||
|
| Writer family | Value | Meaning candidate |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `H'4394` | `0x80` | value/dial page redraw or change latch |
|
||||||
|
| `H'4457` | `0x40` | value/dial page redraw or change latch |
|
||||||
|
| `H'451A` | `0x20` | value/dial page redraw or change latch |
|
||||||
|
|
||||||
|
No decoded direct instruction has been found that writes `F770=0x01`, `0x02`, or `0x04`.
|
||||||
|
|
||||||
|
Updated interpretation:
|
||||||
|
|
||||||
|
- `E000[0x008F]` and `E400[0x0015]` can make OTHERS-adjacent UI state visible, but they do not synthesize the local OTHERS action.
|
||||||
|
- The physical OTHERS/COPY/menu action likely arrives through a separate local key latch or indirect panel-chip path that produces the low `F770` bits only when the page machinery is in the right context.
|
||||||
|
- Pressing OTHERS under the generic queued-report gate can therefore legitimately produce no new serial report.
|
||||||
|
|
||||||
|
## Adjacent F109 Menu/Shutter Key Trace
|
||||||
|
|
||||||
|
A focused trace of the `F109 -> F6D0` local-key handlers found a neighboring menu/shutter cluster. These are useful context because they share the same local-page style, but they do not directly prove the OTHERS low-bit latch.
|
||||||
|
|
||||||
|
| Source bit | Handler | Selector/page effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `F6D0.7` | `H'24E8` | Writes `E800[0x008F]=0x8000` or `0x0000`; if `E000[0x0088].14` is set, diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.6` | `H'252E` | Writes `E800[0x008F]=0x2000` or `0x0000`; if `E000[0x0088].14` is set, diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.4/F6D0.5` | `H'2574/H'25D4` | Uses selectors `0x0091/0x0092` and modal byte `F6F6`; if `E000[0x0088].14` is set, diverts to timed page `F732=0x1C01` |
|
||||||
|
| `F6D0.1/F6D0.2/F6D0.3` | `H'2390/H'2408/H'24A9` | Uses selector `0x0083`; if `E000[0x0088].15` is set, diverts to timed page `F732=0x1C01` |
|
||||||
|
|
||||||
|
`F732=0x1C01` is in the DETAIL/KNEE neighborhood seen in earlier traces, not the OTHERS page `0x01`. This suggests selector `0x0088` can retarget adjacent local keys into a timed menu overlay, while OTHERS itself still depends on the page-1 wrapper and the `F770` low-bit action latch.
|
||||||
|
|
||||||
|
## Bench Implications
|
||||||
|
|
||||||
|
To make the local COPY path available from the panel, the fake CCU probably needs to:
|
||||||
|
|
||||||
|
1. Recover to a live `CONNECT: OK` style session.
|
||||||
|
2. Seed root OTHERS soft-key bits:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 01 0F 18 00 4C ; E000[0x008F] bits 11+12
|
||||||
|
```
|
||||||
|
|
||||||
|
3. During a live continuation/report window, seed the COPY page visibility bit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
06 00 15 00 01 48 ; E400[0x0015] nonzero
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Still satisfy the `F791.7` local copy-start gate.
|
||||||
|
|
||||||
|
The current hardest unknown is step 4: the ROM uses `F791.7` in several places, but the source that sets it has not yet been identified.
|
||||||
|
|
||||||
|
## Bench Observation: OTHERS Gate Probe
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 00 00 80 00 DA ; recover/seed CONNECT OK
|
||||||
|
00 01 0F 18 00 4C ; E000[0x008F] = 0x1800
|
||||||
|
06 00 15 00 01 48 ; E400[0x0015] = 0x0001, sent in active window
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed on the real panel without touching the controls:
|
||||||
|
|
||||||
|
- LCD stayed at `CONNECT: OK`.
|
||||||
|
- SHUTTER seven-segment display changed to something like `EUS`; manuals make this likely `EVS` rendered on a seven-segment display.
|
||||||
|
- Iris AUTO lamp illuminated.
|
||||||
|
- OTHERS menu did not appear by itself.
|
||||||
|
|
||||||
|
Interpretation:
|
||||||
|
|
||||||
|
- The sequence reached real UI state, not only serial parser state.
|
||||||
|
- `E000[0x008F]=0x1800` is now a candidate shutter/mode-status value as well as an OTHERS soft-key source. Treat the earlier "soft-key bits" interpretation as incomplete.
|
||||||
|
- `E400[0x0015]=0x0001` may be the OTHERS/COPY visibility bit, but it may also affect an iris/auto feature path. Isolate before assigning a final meaning.
|
||||||
|
|
||||||
|
Recommended isolation probes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 01 0F 08 00 5C ; E000[0x008F] bit 11 only
|
||||||
|
00 01 0F 10 00 44 ; E000[0x008F] bit 12 only
|
||||||
|
00 01 0F 18 00 4C ; E000[0x008F] bits 11+12
|
||||||
|
06 00 15 00 00 49 ; E400[0x0015] clear/zero
|
||||||
|
06 00 15 00 01 48 ; E400[0x0015] low nonzero
|
||||||
|
06 00 15 80 00 C9 ; E400[0x0015] high nonzero
|
||||||
|
```
|
||||||
|
|
||||||
|
Scenario files:
|
||||||
|
|
||||||
|
- `scenarios/others-isolate-008f-bit11.json`
|
||||||
|
- `scenarios/others-isolate-008f-bit12.json`
|
||||||
|
- `scenarios/others-isolate-008f-bits11-12.json`
|
||||||
|
- `scenarios/others-isolate-e400-0015-low.json`
|
||||||
|
- `scenarios/others-isolate-e400-0015-high.json`
|
||||||
|
- `scenarios/others-isolate-008f-then-e400-clear.json`
|
||||||
|
|
||||||
|
Isolation results:
|
||||||
|
|
||||||
|
| Scenario | Visible panel result | Serial result |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `others-isolate-008f-bit11` | Iris AUTO lamp on, SHUTTER seven-segment shows observed `EUS`, likely manual `EVS` | `04 01 0F 08 00 58`, then repeated `02 00 02 00 00 5A` |
|
||||||
|
| `others-isolate-008f-bit12` | Iris AUTO lamp on, SHUTTER seven-segment shows literal letters `OFF` | `04 01 0F 10 00 40`, then repeated `02 00 02 00 00 5A` |
|
||||||
|
| `others-isolate-008f-bits11-12` | Iris AUTO lamp on, SHUTTER seven-segment shows observed `EUS`, likely manual `EVS` | `04 01 0F 18 00 48`, then repeated `02 00 02 00 00 5A` |
|
||||||
|
| `others-isolate-e400-0015-low` | LCD stays `CONNECT: OK` only | repeated `01 00 02 00 00 59` after command 6 |
|
||||||
|
| `others-isolate-e400-0015-high` | LCD stays `CONNECT: OK` only | repeated `01 00 02 00 00 59` after command 6 |
|
||||||
|
| `others-isolate-008f-then-e400-clear` | Iris AUTO lamp on, SHUTTER seven-segment shows observed `EUS`, likely manual `EVS` | repeated `01 00 02 00 00 59` after command 6 |
|
||||||
|
|
||||||
|
Updated interpretation:
|
||||||
|
|
||||||
|
- `E000[0x008F]` directly affects visible shutter/iris UI state.
|
||||||
|
- Bit 11 selects the observed `EUS` shutter display, probably manual `EVS`; bit 12 selects literal shutter-display text `OFF`; when both are present, the likely `EVS` display appears to win.
|
||||||
|
- The iris AUTO lamp turns on for either bit 11 or bit 12, so it may be tied to the same status selector or to the resulting display mode.
|
||||||
|
- `E400[0x0015]` does not visibly change the panel by itself, even though command 6 does alter the report stream from `02 00 02 00 00 5A` to `01 00 02 00 00 59`.
|
||||||
|
- Keep `E400[0x0015]` as a probable OTHERS/COPY visibility/report-gate candidate, but do not assign the shutter/iris effect to it.
|
||||||
|
|
||||||
|
Manual correlation:
|
||||||
|
|
||||||
|
- The RCP-TX7 operating instructions list `OTHERS (1/6: SHUTTER)` with an `EVS` button, and say to use the shutter block `C.SCAN` or `SHUTTER ON/OFF` buttons when not using EVS.
|
||||||
|
- The same RCP-TX7 manual lists `EVS/ECS` under OTHERS for DXC-D30/D30P normal settings.
|
||||||
|
- A later CCU/RCP manual states that when EVS is on, `EVS` is displayed; when the shutter switch is off, `OFF` is displayed.
|
||||||
|
- Therefore `E000[0x008F].11` is best labeled `shutter_evs_display_or_mode`, and `E000[0x008F].12` is best labeled `shutter_off_display_or_mode` until ROM traces split display-only status from actual camera setting.
|
||||||
|
|
||||||
|
## Bench Observation: OTHERS Common-Gate Button Press
|
||||||
|
|
||||||
|
After the queued-report button tests found BARS and IRIS AUTO reports, the physical OTHERS button was tested with the same common queue-service gate.
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
- `button-common-OTHERS-result.json` matched `button-common-baseline-result.json`.
|
||||||
|
- No extra ACK-target frames appeared.
|
||||||
|
- The queued selector stream still included baseline selector `0x0015 = 0x0000`, but OTHERS did not turn that into a visible/new report.
|
||||||
|
|
||||||
|
Interpretation:
|
||||||
|
|
||||||
|
- OTHERS does not behave like BARS or IRIS AUTO under the simple common queue-service gate.
|
||||||
|
- This supports the ROM model that OTHERS is a local menu/display key whose useful effect depends on additional page/menu state, not just the generic report queue being serviced.
|
||||||
|
- The next fair comparison needs an OTHERS-specific no-button baseline because the OTHERS seeds themselves alter display/report state.
|
||||||
|
|
||||||
|
New focused scenarios:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\others-menu-gated-baseline.json --parity E --quiet-console --log captures\others-menu-gated-baseline.txt --result-json captures\others-menu-gated-baseline-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\others-menu-gated-press.json --parity E --quiet-console --log captures\others-menu-gated-press.txt --result-json captures\others-menu-gated-press-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario_compare.py captures\others-menu-gated-baseline-result.json captures\others-menu-gated-press-result.json --show-labels
|
||||||
|
```
|
||||||
|
|
||||||
|
Result of the first OTHERS-gated comparison:
|
||||||
|
|
||||||
|
- The current baseline rerun overwrote the accidental first baseline capture; `serial_scenario.py` writes logs/results fresh when explicit paths are reused.
|
||||||
|
- `others-menu-gated-baseline-result.json` and `others-menu-gated-press-result.json` were identical at the ACK-target level.
|
||||||
|
- Both had `rx_frames=153`, `ack_sent=64`, and no label-count increase in the press run.
|
||||||
|
- The seeded state was visible in the queued stream as `0x008F = 0x1800`, but pressing OTHERS still did not create an extra serial report.
|
||||||
|
|
||||||
|
Updated interpretation:
|
||||||
|
|
||||||
|
- The simple common queue gate is not enough for OTHERS.
|
||||||
|
- The current OTHERS-adjacent seeds are also not enough for OTHERS to emit a report.
|
||||||
|
- OTHERS is increasingly likely to be a local menu/page navigation key whose effect is visible only if the ROM has selected the right page/context (`F732/F733/F72C/F731/F770`), rather than a host-visible selector-report button like BARS or IRIS AUTO.
|
||||||
338
docs/pt2-panel-atlas.md
Normal file
338
docs/pt2-panel-atlas.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
# PT2 Panel Atlas
|
||||||
|
|
||||||
|
This note tracks bench tests that intentionally drive visible panel outputs from
|
||||||
|
PT2/command-0 table writes.
|
||||||
|
|
||||||
|
## Scenario Files
|
||||||
|
|
||||||
|
Current compact atlas scenarios:
|
||||||
|
|
||||||
|
```text
|
||||||
|
scenarios/panel-atlas-operator-lamps-v1.json
|
||||||
|
scenarios/panel-atlas-readout-status-v1.json
|
||||||
|
scenarios/panel-atlas-right-stack-isolation-v1.json
|
||||||
|
scenarios/panel-atlas-right-stack-fresh-latch-v1.json
|
||||||
|
scenarios/panel-atlas-standard-master-bit-sweep-v1.json
|
||||||
|
scenarios/panel-atlas-standard-master-neighbor-sweep-v2.json
|
||||||
|
scenarios/panel-atlas-standard-master-lower-neighbor-sweep-v3.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Both are designed for webcam capture with the calibrated bench settings:
|
||||||
|
|
||||||
|
```text
|
||||||
|
--parity E --camera-index 4 --snapshot-delays 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run: 2026-05-27
|
||||||
|
|
||||||
|
Logs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
captures/panel-atlas-operator-lamps-v1-webcam.txt
|
||||||
|
captures/panel-atlas-readout-status-v1-webcam.txt
|
||||||
|
captures/panel-atlas-right-stack-isolation-v1-webcam.txt
|
||||||
|
captures/panel-atlas-right-stack-fresh-latch-v1-webcam.txt
|
||||||
|
captures/panel-atlas-standard-master-bit-sweep-v1-webcam.txt
|
||||||
|
captures/panel-atlas-standard-master-neighbor-sweep-v2-webcam.txt
|
||||||
|
captures/panel-atlas-standard-master-lower-neighbor-sweep-v3-webcam.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Snapshots:
|
||||||
|
|
||||||
|
```text
|
||||||
|
captures/panel-atlas-operator-lamps-v1-webcam-shots/
|
||||||
|
captures/panel-atlas-readout-status-v1-webcam-shots/
|
||||||
|
captures/panel-atlas-right-stack-isolation-v1-webcam-shots/
|
||||||
|
captures/panel-atlas-right-stack-fresh-latch-v1-webcam-shots/
|
||||||
|
captures/panel-atlas-standard-master-bit-sweep-v1-webcam-shots/
|
||||||
|
captures/panel-atlas-standard-master-neighbor-sweep-v2-webcam-shots/
|
||||||
|
captures/panel-atlas-standard-master-lower-neighbor-sweep-v3-webcam-shots/
|
||||||
|
```
|
||||||
|
|
||||||
|
Serial health:
|
||||||
|
|
||||||
|
| Run | RX frames | TX frames | Resync | Dropped bytes |
|
||||||
|
| --- | ---: | ---: | ---: | ---: |
|
||||||
|
| operator lamps | 82 | 19 | 0 | 0 |
|
||||||
|
| readout/status | 76 | 16 | 0 | 0 |
|
||||||
|
| right-stack isolation | 115 | 26 | 0 | 0 |
|
||||||
|
| right-stack fresh latch | 20 | 12 | 0 | 0 |
|
||||||
|
| standard/master bit sweep v1 | 144 | 34 | 0 | 0 |
|
||||||
|
| standard/master neighbor sweep v2 | 188 | 47 | 0 | 0 |
|
||||||
|
| standard/master lower-neighbor sweep v3 | 188 | 47 | 0 | 0 |
|
||||||
|
|
||||||
|
The compact and isolation runs stayed in the expected table-readback plus
|
||||||
|
`02 00 02 00 00 5A` CONNECT-OK response rhythm.
|
||||||
|
|
||||||
|
The fresh-latch run ended with one trailing unframed byte after the final send.
|
||||||
|
There were no resync events or dropped bytes; this is consistent with the run
|
||||||
|
ending while the next response frame was just beginning.
|
||||||
|
|
||||||
|
## Confirmed Visible Effects
|
||||||
|
|
||||||
|
## Far-Right Stack Reference
|
||||||
|
|
||||||
|
The physical far-right stack, top to bottom, is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TALLY light, with camera number
|
||||||
|
STANDARD
|
||||||
|
MASTER
|
||||||
|
SLAVE
|
||||||
|
CAM POWER
|
||||||
|
BARS
|
||||||
|
```
|
||||||
|
|
||||||
|
Use these names instead of generic "right-side status" labels when reviewing
|
||||||
|
webcam crops.
|
||||||
|
|
||||||
|
Readout/status run:
|
||||||
|
|
||||||
|
| Frame | Selector/value | Visible effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `00 01 0F 08 00 5C` | `E000[0x008F]=0x0800` | SHUTTER display shows `EUS`/likely `EVS`; iris AUTO lamp is lit |
|
||||||
|
| `00 01 0F 10 00 44` | `E000[0x008F]=0x1000` | SHUTTER display shows `OFF`; iris AUTO lamp remains lit |
|
||||||
|
| `00 01 13 80 00 C8` | `E000[0x0093]=0x8000` | white-balance / black-flare lamp cluster changes, consistent with prior preset/manual observation |
|
||||||
|
| `00 01 13 90 20 F8` | `E000[0x0093]=0x9020` | black-flare manual-context candidate remains visible |
|
||||||
|
| `00 01 13 90 FF 27` | `E000[0x0093]=0x90FF` | black-flare auto-context candidate remains visible |
|
||||||
|
| `00 01 90 80 00 4B` | `E000[0x0110]=0x8000` | KNEE AUTO lamp lights |
|
||||||
|
|
||||||
|
The `0x00B9` gate writes in this compact run did not light KNEE AUTO by
|
||||||
|
themselves at the 0.5 s snapshot point. That matches the ROM model where
|
||||||
|
`0x00B9.13` is more of a report-path gate, while `0x0110.15` is the stronger
|
||||||
|
visible KNEE AUTO source.
|
||||||
|
|
||||||
|
Operator-lamp run:
|
||||||
|
|
||||||
|
| Frame | Selector/value | Visible effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `00 00 13 80 00 C9` | `E000[0x0013]=0x8000` | far-right SLAVE lamp lights, based on stack order and crop position |
|
||||||
|
| `00 00 17 80 00 CD` | `E000[0x0017]=0x8000` | far-right bottom white BARS lamp lights |
|
||||||
|
| `00 00 1A 80 00 C0` | `E000[0x001A]=0x8000` | lower right white lamp appeared lit in the compact run, later refined as likely `0x0017` carryover |
|
||||||
|
|
||||||
|
Right-stack isolation/fresh-latch refinement:
|
||||||
|
|
||||||
|
| Frame | Selector/value | Visible effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `00 00 15 80 00 CF` | `E000[0x0015]=0x8000` | CALL lamp lights |
|
||||||
|
| `00 00 15 00 00 4F` | `E000[0x0015]=0x0000` | CALL lamp clears |
|
||||||
|
| `00 00 13 80 00 C9` | `E000[0x0013]=0x8000` | far-right SLAVE lamp lights |
|
||||||
|
| `00 00 13 00 00 49` | `E000[0x0013]=0x0000` | SLAVE lamp clears |
|
||||||
|
| `00 00 17 80 00 CD` | `E000[0x0017]=0x8000` | far-right bottom white BARS lamp lights |
|
||||||
|
| `00 00 17 00 00 4D` | `E000[0x0017]=0x0000` | lamp remains lit; low write does not clear it |
|
||||||
|
| `00 00 1A 80 00 C0` | `E000[0x001A]=0x8000` | no independent BARS-lamp effect from fresh boot |
|
||||||
|
| `00 00 07 80 00 DD` / `00 00 07 00 00 5D` | `E000[0x0007]` high/low | no clear visible delta from CONNECT-OK baseline |
|
||||||
|
|
||||||
|
So `0x0017` is currently the strongest BARS lamp-on/latch selector.
|
||||||
|
`0x001A` should not be labeled as the same lamp source from the prior compact
|
||||||
|
run; that was likely carryover after `0x0017` had latched the white lamp on.
|
||||||
|
`0x0007` is still protocol-relevant because the real panel emits the matching
|
||||||
|
CAM POWER event frame, but the host-write visible lamp mapping is not separated
|
||||||
|
from the CONNECT-OK baseline yet.
|
||||||
|
|
||||||
|
ROM trace refinement: selector `0x0013` dispatches to `H'2E06`, reads current
|
||||||
|
table word `E800[0x0013]` at `H'E826`, and maps bit 15 to SLAVE while bit 14
|
||||||
|
maps to `IRIS/M.BLACK LINK`. See `docs/pt2-iris-mblack-link-rom-trace.md`.
|
||||||
|
|
||||||
|
STANDARD/MASTER hunt:
|
||||||
|
|
||||||
|
| Run | Tested selector/value pocket | Result |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `panel-atlas-standard-master-bit-sweep-v1` | `0x0012`, `0x0013`, `0x0014` with high-bit/high-nibble candidates | no clean STANDARD or MASTER lamp trigger; positive SLAVE control worked |
|
||||||
|
| `panel-atlas-standard-master-neighbor-sweep-v2` | `0x0010`, `0x0011`, `0x0015`, `0x0016`, `0x0017`, `0x0018`, `0x0019`, `0x001A` high-nibble candidates | no clean STANDARD or MASTER trigger |
|
||||||
|
| `panel-atlas-standard-master-lower-neighbor-sweep-v3` | `0x0008` through `0x000F` high-nibble candidates | no clean STANDARD or MASTER trigger |
|
||||||
|
|
||||||
|
The `v2` sweep did show the far-right bottom BARS lamp/latch from the
|
||||||
|
`0x0017` family when using non-`0x8000` high-nibble values. The clearest
|
||||||
|
transition is `00 00 17 40 00 0D` (`E000[0x0017]=0x4000`) lighting the same
|
||||||
|
bottom white BARS lamp that `0x0017=0x8000` can light. The lamp persisted after
|
||||||
|
`0x0017=0x0000`, matching the existing BARS latch behavior. Later `0x0018`
|
||||||
|
rows are contaminated by that latch and need a fresh-boot isolation run before
|
||||||
|
labeling `0x0018` as a separate BARS source.
|
||||||
|
|
||||||
|
## Next Atlas Step
|
||||||
|
|
||||||
|
The next useful run is still a clear/ack/state-transition probe for the
|
||||||
|
`0x0017` BARS latch, now including `0x0017=0x4000`:
|
||||||
|
|
||||||
|
- test likely sibling selectors around `0x0016`, `0x0018`, `0x0019`, and `0x001A`
|
||||||
|
from a fresh boot after `0x0017` has latched on,
|
||||||
|
- try command-7 repeat/ack and selector-zero refreshes to see whether the lamp
|
||||||
|
clears through a state-machine transition rather than a simple low write,
|
||||||
|
- test `0x0007` with an alternate baseline, because CONNECT OK already lights
|
||||||
|
CAM POWER and masks any host-write lamp delta.
|
||||||
|
- do not re-run `0x0008` through `0x0014` high-nibble values for
|
||||||
|
STANDARD/MASTER unless the ROM trace points back there; the webcam sweeps did
|
||||||
|
not show those lamps in that pocket.
|
||||||
|
|
||||||
|
## ROM-Derived Button Output Sweep
|
||||||
|
|
||||||
|
To skip the physical RCP button-press side and directly test likely "on" states
|
||||||
|
from ROM handlers, use:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\build_rom_button_output_sweep.py
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\panel-atlas-rom-button-output-candidates-v1.json --parity E --quiet-console --log captures\panel-atlas-rom-button-output-candidates-v1-webcam.txt --result-json captures\panel-atlas-rom-button-output-candidates-v1-webcam-result.json --snapshot-dir captures\panel-atlas-rom-button-output-candidates-v1-webcam-shots --camera-index 4 --snapshot-delays 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
This scenario power-cycles before every candidate, seeds `CONNECT: OK`, sends
|
||||||
|
one command-0 selector/value from the ROM-derived candidate list, and captures a
|
||||||
|
single webcam image 0.5 s later. It is intentionally slower than a continuous
|
||||||
|
sweep but should avoid most latch/carryover ambiguity.
|
||||||
|
|
||||||
|
Run result notes from `captures/panel-atlas-rom-button-output-candidates-v1-webcam-shots/`:
|
||||||
|
|
||||||
|
| Case | Frame | Selector/value | Visible result |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 001 | `00 00 13 40 00 09` | `0x0013 = 0x4000` | IRIS/M.BLACK LINK |
|
||||||
|
| 002 | `00 00 13 80 00 C9` | `0x0013 = 0x8000` | SLAVE |
|
||||||
|
| 003 | `00 00 15 80 00 CF` | `0x0015 = 0x8000` | CALL and red tally |
|
||||||
|
| 004 | `00 00 17 80 00 CD` | `0x0017 = 0x8000` | BARS |
|
||||||
|
| 005 | `00 01 90 80 00 4B` | `0x0110 = 0x8000` | KNEE AUTO |
|
||||||
|
| 006 | `00 00 1A 08 08 40` | `0x001A = 0x0808` | MONITOR ENC |
|
||||||
|
| 007 | `00 00 1A 20 20 40` | `0x001A = 0x2020` | MONITOR B |
|
||||||
|
| 008 | `00 00 1A 40 40 40` | `0x001A = 0x4040` | MONITOR G |
|
||||||
|
| 009 | `00 00 1A 80 80 40` | `0x001A = 0x8080` | MONITOR R |
|
||||||
|
| 010 | `00 00 6B 80 00 B1` | `0x006B = 0x8000` | STANDARD |
|
||||||
|
| 011 | `00 01 03 00 04 5C` | `0x0083 = 0x0004` | IRIS AUTO, SHUTTER OFF, MASTER GAIN HP |
|
||||||
|
| 012 | `00 01 03 40 00 18` | `0x0083 = 0x4000` | IRIS AUTO, SHUTTER OFF, MASTER GAIN 0 |
|
||||||
|
| 013 | `00 01 03 20 00 78` | `0x0083 = 0x2000` | IRIS AUTO, SHUTTER OFF, MASTER GAIN 3 |
|
||||||
|
| 014 | `00 01 0F 80 00 D4` | `0x008F = 0x8000` | IRIS AUTO, SHUTTER begins with `1...` |
|
||||||
|
| 015 | `00 01 0F 20 00 74` | `0x008F = 0x2000` | IRIS AUTO, SHUTTER `00.0` |
|
||||||
|
| 016 | `00 01 0F 08 00 5C` | `0x008F = 0x0800` | IRIS AUTO, SHUTTER EVS |
|
||||||
|
| 017 | `00 01 0F 10 00 44` | `0x008F = 0x1000` | IRIS AUTO, SHUTTER OFF |
|
||||||
|
| 018 | `00 01 13 10 20 78` | `0x0093 = 0x1020` | BLACK/FLARE MANUAL, white balance MANUAL |
|
||||||
|
| 019 | `00 01 13 40 40 48` | `0x0093 = 0x4040` | BLACK/FLARE AUTO, white balance AUTO |
|
||||||
|
| 020 | `00 01 13 80 40 88` | `0x0093 = 0x8040` | BLACK/FLARE AUTO, white balance PRESET |
|
||||||
|
| 021 | `00 01 13 00 20 68` | `0x0093 = 0x0020` | BLACK/FLARE MANUAL, white balance MANUAL |
|
||||||
|
| 022 | `00 01 13 00 40 08` | `0x0093 = 0x0040` | BLACK/FLARE AUTO, white balance MANUAL |
|
||||||
|
| 023 | `00 01 1A 08 00 49` | `0x009A = 0x0800` | no panel change observed |
|
||||||
|
| 024 | `00 01 37 20 00 4C` | `0x00B7 = 0x2000` | no panel change observed |
|
||||||
|
|
||||||
|
The run directory contains 28 candidate photos. The user-supplied ordered notes
|
||||||
|
covered the first 24, so cases 025-028 still need visual review before assigning
|
||||||
|
meanings:
|
||||||
|
|
||||||
|
| Case | Frame | Selector/value | Candidate |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 025 | `00 01 39 40 00 22` | `0x00B9 = 0x4000` | F6DC.7 handler value candidate |
|
||||||
|
| 026 | `00 01 44 80 00 9F` | `0x00C4 = 0x8000` | F6D4.0 bundle selector candidate |
|
||||||
|
| 027 | `00 01 46 80 00 9D` | `0x00C6 = 0x8000` | F6D4.0 bundle selector candidate |
|
||||||
|
| 028 | `00 01 78 80 00 A3` | `0x00F8 = 0x8000` | F6D4.1 handler candidate |
|
||||||
|
|
||||||
|
Key refinements from this run:
|
||||||
|
|
||||||
|
- `0x001A` is now best labeled as the MONITOR selector cluster: `ENC`, `B`, `G`,
|
||||||
|
and `R` appeared cleanly from the four packed values.
|
||||||
|
- `0x006B = 0x8000` is the first clean STANDARD lamp trigger.
|
||||||
|
- `0x0083` is a MASTER GAIN/status display word, with values observed for `HP`,
|
||||||
|
`0`, and `3`, while also lighting IRIS AUTO and showing SHUTTER OFF.
|
||||||
|
- `0x008F` carries local shutter display/value states beyond the earlier
|
||||||
|
EVS/OFF bits.
|
||||||
|
- `0x0093` now has stronger white-balance plus BLACK/FLARE field mapping:
|
||||||
|
high/mid bit combinations select WB AUTO/PRESET/MANUAL and BLACK/FLARE
|
||||||
|
AUTO/MANUAL together.
|
||||||
|
|
||||||
|
## Broad Visual Sweep Workflow
|
||||||
|
|
||||||
|
For exploratory lamp/readout mining, use the generated big sweep rather than
|
||||||
|
hand-writing thousands of frames:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\build_panel_visual_sweep.py scenarios\panel-atlas-big-visual-sweep-0001-017f-highbits.json --start 0x0001 --end 0x017F --values 0x8000,0x4000,0x2000,0x1000,0x0800 --power-cycle-every 32 --ok-every 8 --listen 0.65 --clear-listen 0.15 --ok-listen 0.30
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the generated scenario with webcam snapshots:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\panel-atlas-big-visual-sweep-0001-017f-highbits.json --parity E --quiet-console --log captures\panel-atlas-big-visual-sweep-0001-017f-highbits-webcam.txt --result-json captures\panel-atlas-big-visual-sweep-0001-017f-highbits-webcam-result.json --snapshot-dir captures\panel-atlas-big-visual-sweep-0001-017f-highbits-webcam-shots --camera-index 4 --snapshot-delays 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated high-bit sweep covers selectors `0x0001` through `0x017F` with
|
||||||
|
five candidate values per selector, for 1,915 candidate snapshots. It power
|
||||||
|
cycles every 32 selectors to reduce latch contamination. Selector zero is
|
||||||
|
omitted because it controls the CONNECT OK baseline.
|
||||||
|
|
||||||
|
After the run, create labeled review sheets:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\make_panel_sweep_contact_sheets.py captures\panel-atlas-big-visual-sweep-0001-017f-highbits-webcam-shots --output-dir captures\panel-atlas-big-visual-sweep-0001-017f-highbits-sheets --only-candidates --crop panel --cols 4 --rows 5 --thumb-width 360
|
||||||
|
```
|
||||||
|
|
||||||
|
If a sheet shows a visible change, the image label has the exact trigger form:
|
||||||
|
`candidate_XXXX_YYYY` means command-0 wrote `E000[0xXXXX]=0xYYYY`. Use that
|
||||||
|
selector/value in a smaller fresh-boot isolation scenario.
|
||||||
|
|
||||||
|
## Broad Sweep Findings
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```text
|
||||||
|
captures/panel-atlas-big-visual-sweep-0001-017f-highbits-webcam.txt
|
||||||
|
captures/panel-atlas-big-visual-sweep-0001-017f-highbits-webcam-result.json
|
||||||
|
captures/panel-atlas-big-visual-sweep-0001-017f-highbits-webcam-shots/
|
||||||
|
```
|
||||||
|
|
||||||
|
Serial health:
|
||||||
|
|
||||||
|
| RX frames | TX frames | Resync | Dropped bytes | Snapshots |
|
||||||
|
| ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| 706 | 2372 | 0 | 0 | 1916 |
|
||||||
|
|
||||||
|
User-reviewed new visible hits from the broad sweep:
|
||||||
|
|
||||||
|
| Candidate label | Frame | Selector/value | Reported visible effect |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `candidate_0013_4000` | `00 00 13 40 00 09` | `E000[0x0013]=0x4000` | `IRIS/M.BLACK LINK` area/lamp |
|
||||||
|
| `candidate_0024_8000` | `00 00 24 80 00 FE` | `E000[0x0024]=0x8000` | LCD selector button/lamp |
|
||||||
|
| `candidate_0082_8000` | `00 01 02 80 00 D9` | `E000[0x0082]=0x8000` | IRIS readout shows `OP` |
|
||||||
|
| `candidate_0082_4000` | `00 01 02 40 00 19` | `E000[0x0082]=0x4000` | IRIS readout shows `1.4` |
|
||||||
|
| `candidate_0083_8000` | `00 01 03 80 00 D8` | `E000[0x0083]=0x8000` | MASTER GAIN readout shows `-3` |
|
||||||
|
| `candidate_0093_8000` | `00 01 13 80 00 C8` | `E000[0x0093]=0x8000` | white-balance PRESET lamp |
|
||||||
|
| `candidate_0093_4000` | `00 01 13 40 00 08` | `E000[0x0093]=0x4000` | white-balance AUTO lamp |
|
||||||
|
| `candidate_0093_2000` | `00 01 13 20 00 68` | `E000[0x0093]=0x2000` | white-balance MANUAL lamp |
|
||||||
|
|
||||||
|
The serial log shows immediate command-4 table readback frames for these writes,
|
||||||
|
so the RCP accepted the selector updates.
|
||||||
|
|
||||||
|
Fresh-boot isolation scenario:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\panel-atlas-big-hits-isolation-v1.json --parity E --quiet-console --log captures\panel-atlas-big-hits-isolation-v1-webcam.txt --result-json captures\panel-atlas-big-hits-isolation-v1-webcam-result.json --snapshot-dir captures\panel-atlas-big-hits-isolation-v1-webcam-shots --camera-index 4 --snapshot-delays 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Create review sheets for that isolation run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\make_panel_sweep_contact_sheets.py captures\panel-atlas-big-hits-isolation-v1-webcam-shots --output-dir captures\panel-atlas-big-hits-isolation-v1-sheets --crop panel --cols 3 --rows 4 --thumb-width 420
|
||||||
|
```
|
||||||
|
|
||||||
|
Fresh-boot isolation results:
|
||||||
|
|
||||||
|
| Frame | Selector/value | Confirmed visible effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `00 00 13 40 00 09` | `E000[0x0013]=0x4000` | `IRIS/M.BLACK LINK` lamp |
|
||||||
|
| `00 00 24 80 00 FE` | `E000[0x0024]=0x8000` | LCD selector-button lamp |
|
||||||
|
| `00 00 24 00 00 7E` | `E000[0x0024]=0x0000` | same LCD selector-button lamp remained visible at 0.5 s |
|
||||||
|
| `00 01 02 80 00 D9` | `E000[0x0082]=0x8000` | IRIS readout `OP` |
|
||||||
|
| `00 01 02 40 00 19` | `E000[0x0082]=0x4000` | IRIS readout `1.4` |
|
||||||
|
| `00 01 02 00 00 59` | `E000[0x0082]=0x0000` | IRIS readout blank |
|
||||||
|
| `00 01 03 80 00 D8` | `E000[0x0083]=0x8000` | IRIS AUTO lamp, SHUTTER `OFF`, MASTER GAIN `-3` |
|
||||||
|
| `00 01 03 00 00 58` | `E000[0x0083]=0x0000` | same IRIS AUTO / SHUTTER `OFF` / MASTER GAIN `-3` state remained visible at 0.5 s |
|
||||||
|
| `00 01 13 80 00 C8` | `E000[0x0093]=0x8000` | BLACK/FLARE MANUAL plus white-balance PRESET |
|
||||||
|
| `00 01 13 40 00 08` | `E000[0x0093]=0x4000` | BLACK/FLARE MANUAL plus white-balance AUTO |
|
||||||
|
| `00 01 13 20 00 68` | `E000[0x0093]=0x2000` | BLACK/FLARE MANUAL plus white-balance MANUAL |
|
||||||
|
| `00 01 13 00 00 48` | `E000[0x0093]=0x0000` | BLACK/FLARE MANUAL plus white-balance MANUAL |
|
||||||
|
|
||||||
|
Interpretation:
|
||||||
|
|
||||||
|
- `0x0082` is a direct IRIS display/status selector. Clearing it blanks the IRIS
|
||||||
|
readout, so this one behaves like a simple display source.
|
||||||
|
- `0x0083=0x8000` drives a combined state: MASTER GAIN `-3`, SHUTTER `OFF`, and
|
||||||
|
IRIS AUTO. Clearing the selector did not visibly clear that state in the
|
||||||
|
isolation run, so it may be latched or copied into another display bank.
|
||||||
|
- `0x0093` is now a confirmed white-balance mode selector with
|
||||||
|
`0x8000=PRESET`, `0x4000=AUTO`, and `0x0000/0x2000=MANUAL` under this test
|
||||||
|
context. BLACK/FLARE MANUAL was also present for all tested `0x0093` states.
|
||||||
|
- `0x0024=0x8000` lights an LCD selector-button lamp, but `0x0024=0x0000` did
|
||||||
|
not clear it in this timing window.
|
||||||
@@ -4,6 +4,16 @@ This document is the current working model for the serial protocol spoken by the
|
|||||||
|
|
||||||
A later RCP manual mentions a "PT2 compatibility mode" for controlling the same CCU family this panel was made for. We are using "PT2" here as a practical label for this six-byte SCI1 protocol model. It is not yet a claim that every field name matches Sony's official PT2 terminology.
|
A later RCP manual mentions a "PT2 compatibility mode" for controlling the same CCU family this panel was made for. We are using "PT2" here as a practical label for this six-byte SCI1 protocol model. It is not yet a claim that every field name matches Sony's official PT2 terminology.
|
||||||
|
|
||||||
|
Focused companion notes:
|
||||||
|
|
||||||
|
- [PT2 Copy State Machine](pt2-copy-state-machine.md)
|
||||||
|
- [PT2 Menu State Machine](pt2-menu-state-machine.md)
|
||||||
|
- [PT2 Session Rhythm ROM Trace](pt2-session-rhythm-trace.md)
|
||||||
|
- [PT2 Continuation Command Trace](pt2-continuation-command-trace.md)
|
||||||
|
- [PT2 Report Aftermath ROM Trace](pt2-report-aftermath-trace.md)
|
||||||
|
- [PT2 Shutter Display Trace](pt2-shutter-display-trace.md)
|
||||||
|
- [PT2 Button Report Bench Plan](pt2-button-report-bench-plan.md)
|
||||||
|
|
||||||
## Current High-Confidence Facts
|
## Current High-Confidence Facts
|
||||||
|
|
||||||
- The real bench link is `38400 8E1`, not `38400 8N1`.
|
- The real bench link is `38400 8E1`, not `38400 8N1`.
|
||||||
@@ -428,6 +438,9 @@ ROM report-source update:
|
|||||||
- The active `02/01 ...` frames seen during CONNECT OK attempts are best modeled as `F870 -> BAF2 -> BA26` report-queue transmissions.
|
- The active `02/01 ...` frames seen during CONNECT OK attempts are best modeled as `F870 -> BAF2 -> BA26` report-queue transmissions.
|
||||||
- `BAF2` dequeues a report word, encodes the first three TX bytes, reads the payload from `E800 + 2*selector`, and `BA26` appends the `0x5A` XOR checksum.
|
- `BAF2` dequeues a report word, encodes the first three TX bytes, reads the payload from `E800 + 2*selector`, and `BA26` appends the `0x5A` XOR checksum.
|
||||||
- After sending a queued report, the ROM sets `FAA2.3` and `FAA3.7`; command `4`, `5`, or `6` can then consume/advance the report only while that continuation latch is live.
|
- After sending a queued report, the ROM sets `FAA2.3` and `FAA3.7`; command `4`, `5`, or `6` can then consume/advance the report only while that continuation latch is live.
|
||||||
|
- The emitted report does not appear to encode a required ACK selector. The ROM consume test is `FAA2.3`, so a generic command-5 continuation ACK such as `05 00 40 00 00 1F` is the cleanest report-cursor consume candidate.
|
||||||
|
- Low commands `0/1/2` during the report wait can clear the continuation latch and re-enter initial dispatch, but they do not advance `F9B5`. Command `7` retransmits and also does not advance `F9B5`.
|
||||||
|
- Emulator testing now supports the reactive ACK model: waiting for a finished RCP report frame, then sending `05 00 40 00 00 1F`, repeatedly advanced `F9B5`, cleared `FAA2/FAA3`, and kept the emulated LCD at `CONNECT: OK`.
|
||||||
- This makes a reactive fake-CCU test more valuable than another blind fixed-delay matrix: recover to OK, wait for the first active report, then send one candidate continuation/ACK frame.
|
- This makes a reactive fake-CCU test more valuable than another blind fixed-delay matrix: recover to OK, wait for the first active report, then send one candidate continuation/ACK frame.
|
||||||
|
|
||||||
## Candidate CCU Seed Values
|
## Candidate CCU Seed Values
|
||||||
@@ -560,10 +573,31 @@ The `0x006D` copy path is now confirmed outside the earlier all-suite ordering c
|
|||||||
- Command 5 compares `R5` against `0x006C`, `0x006D`, and `0x006E`; those selectors call `BE70` to append the selector to the `F970` processing queue.
|
- Command 5 compares `R5` against `0x006C`, `0x006D`, and `0x006E`; those selectors call `BE70` to append the selector to the `F970` processing queue.
|
||||||
- Selector `0x006C` dispatches to `H'2FAF`. Forced decoding shows it manipulates `F76E`, `F795`, `F797`, `F799`, can set display selector `F732=H'1904`, sets `FB02=H'14`, and calls the `48FA` display/report bridge.
|
- Selector `0x006C` dispatches to `H'2FAF`. Forced decoding shows it manipulates `F76E`, `F795`, `F797`, `F799`, can set display selector `F732=H'1904`, sets `FB02=H'14`, and calls the `48FA` display/report bridge.
|
||||||
- Selector `0x006D` dispatches to `H'3015`. Forced decoding shows it sets `F731.7`, loads `F798=H'C8`, sets `F795.6/F795.7`, sets display selector `F732=H'1903`, sets `FB02=H'64`, calls `48FA`, then sets `F76E.6`.
|
- Selector `0x006D` dispatches to `H'3015`. Forced decoding shows it sets `F731.7`, loads `F798=H'C8`, sets `F795.6/F795.7`, sets display selector `F732=H'1903`, sets `FB02=H'64`, calls `48FA`, then sets `F76E.6`.
|
||||||
|
- The LCD dispatch for these states is now traced: `loc_48FA` reads the high byte at `F732`, so `F732=H'1903/H'1904` selects display page `0x19`, not direct page `0x03/0x04`. `493E[0x19] -> H'930A`; that page's local table at `H'931C` includes `H'9F6A` for `COPY` / `IN PROGRESS` and `H'9FDA` for `COPY` / `COMPLETED`. The low byte at `F733` is the substate selector: `0x03` is in-progress and `0x04` is completed.
|
||||||
|
- This makes the likely copy handshake: `0x006D` starts copy and sets the `F795.6/F795.7` in-progress flags; `0x006C` is the completion/exit sibling only when those flags are live. Sending `0x006C` alone can therefore blank or clear state instead of displaying `COPY COMPLETED`.
|
||||||
|
- Bench step-through confirmed the sequence model: `006C` alone produced `CONNECT OK -> blank`; `006D` alone produced `CONNECT OK -> COPY IN PROGRESS`; `006D` followed by `006C` after 250 ms produced a brief `COPY IN PROGRESS` then `COPY COMPLETED`; the same after 1.0 s and 1.5 s produced a longer `COPY IN PROGRESS` then `COPY COMPLETED`; after 2.0 s or 2.5 s it fell to `CONNECT:NOT ACT` instead of completing. Repeating `006D` before `006C` also completed successfully in the 2x and 3x repeat tests. A longer `006D` hold test kept `COPY IN PROGRESS` active for several seconds and then completed when `006C` arrived, while the same hold without `006C` timed out from `COPY IN PROGRESS` to `CONNECT:NOT ACT`. This points to `006D` as an in-progress/progress-window refresh selector and `006C` as the explicit completion/exit selector, not a stateless command pair.
|
||||||
- The FRT1 timer path decrements `F797` and `F798`; when either reaches zero, it clears `F731.7`. This matches the observed transient display modes falling back to `CONNECT:NOT ACT`.
|
- The FRT1 timer path decrements `F797` and `F798`; when either reaches zero, it clears `F731.7`. This matches the observed transient display modes falling back to `CONNECT:NOT ACT`.
|
||||||
- The string `COPY IN PROGRESS` is present in the ROM LCD resources, so the `006D` result is not a generic serial artifact.
|
- The string `COPY IN PROGRESS` is present in the ROM LCD resources, so the `006D` result is not a generic serial artifact.
|
||||||
- Manual interpretation: the RCP-TX7 operating manual describes `COPY IN PROGRESS` as the LCD state shown during the multi-camera `COPY TO SLAVES` data-transfer operation over the RS232C command-link system. During that state, all linked RCP units display the message and their buttons/knobs are locked until `COPY COMPLETED`. Therefore selector `0x006D` is best treated as entering a command-link copy/data-transfer state, not as a normal CCU connection ACK.
|
- Manual interpretation: the RCP-TX7 operating manual describes `COPY IN PROGRESS` as the LCD state shown during the multi-camera `COPY TO SLAVES` data-transfer operation over the RS232C command-link system. During that state, all linked RCP units display the message and their buttons/knobs are locked until `COPY COMPLETED`. Therefore selector `0x006D` is best treated as entering a command-link copy/data-transfer state, not as a normal CCU connection ACK.
|
||||||
|
|
||||||
|
RCP-side OTHERS/COPY menu trace:
|
||||||
|
|
||||||
|
- The OTHERS menu is page `0x01`: `493E[0x01] -> H'631C`, local table `H'632E`.
|
||||||
|
- Local table entry 1 points to `H'6FF0`, the page that renders `OTHERS` / `COPY TO SLAVES`.
|
||||||
|
- The entry descriptor immediately before `H'6FF0` requires selector `0x0015` in the secondary table: `E400[0x0015] != 0`. Because command 6 writes `E400`, this is probably a CCU-provided feature/visibility bit, and command 6 must be sent in a live continuation/report window to have an effect.
|
||||||
|
- The root OTHERS/SHUTTER page `H'6EE4` consumes `F770.0/F770.1`; the COPY page `H'6FF0` consumes `F770.2`. Both handlers read `F770`, clear it, and then act on those low bits.
|
||||||
|
- The only decoded direct writers to `F770` found so far write high bits `0x80`, `0x40`, and `0x20` from value/dial redraw paths. No direct decoded write to `F770=0x01/0x02/0x04` has been found yet, so the physical OTHERS/COPY action probably arrives through an indirect panel-key latch or page-local mechanism.
|
||||||
|
- When the page sees `F770.2` set, it only follows the local copy-start branch if `F791.7` is already set. That branch sets `F76E.6`, `F795.7`, `F731.7`, `F798=H'C8`, `F711.7`, `F726=H'64`, calls `loc_5500`, then displays `COPY TO SLAVES`. This is the RCP-side equivalent of the serial `0x006D` copy-start effect.
|
||||||
|
- If `F770.2` is set while `F791.7` is clear, the ROM diverts through `H'704C` to a `SET RCP` / `MASTER` display path instead of starting copy. That makes `F791.7` a second, likely master/link/session gate for the physical COPY operation.
|
||||||
|
- The OTHERS root handler at `H'6EE4` also tests primary selector `E000[0x008F]` (`H'E11E`) bits 11 and 12, and uses them to set `F711.6` and `F711.4`. Bench isolation shows this selector is broader than an OTHERS gate: bit 11 makes the SHUTTER seven-segment display show observed `EUS`, probably the manual's `EVS` display, and bit 12 makes it show the literal letters `OFF`; either bit also illuminates the iris AUTO lamp.
|
||||||
|
- Candidate gate probes, not final protocol truth:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 01 0F 18 00 4C ; command 0, set E000[0x008F] bits 11+12
|
||||||
|
06 00 15 00 01 48 ; command 6, set E400[0x0015] nonzero; requires live continuation
|
||||||
|
06 00 15 80 00 C9 ; command 6, alternate nonzero visibility value; requires live continuation
|
||||||
|
```
|
||||||
|
|
||||||
Read table state:
|
Read table state:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
@@ -586,6 +620,106 @@ This fits the real panel behavior:
|
|||||||
- Correct fake-CCU traffic can wake it to `CONNECT: OK`.
|
- Correct fake-CCU traffic can wake it to `CONNECT: OK`.
|
||||||
- Without richer CCU state, readouts illuminate but show placeholders like `----`.
|
- Without richer CCU state, readouts illuminate but show placeholders like `----`.
|
||||||
|
|
||||||
|
## Active-State Local-Control Watch
|
||||||
|
|
||||||
|
Bench evidence now suggests `CONNECT: OK` is maintained by an ongoing CCU refresh stream, not by one magic wake frame. This creates a useful local-control test: keep the RCP in the active state and press/turn physical controls while logging device TX.
|
||||||
|
|
||||||
|
Two JSON scenarios are set up for that:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\active-control-report-watch-quiet.json --parity E --log captures\active-control-report-watch-quiet.txt --result-json captures\active-control-report-watch-quiet-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\active-control-report-watch-broad.json --parity E --log captures\active-control-report-watch-broad.txt --result-json captures\active-control-report-watch-broad-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\active-control-report-watch-gated.json --parity E --log captures\active-control-report-watch-gated.txt --result-json captures\active-control-report-watch-gated-result.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The quiet scenario sends the selector-zero OK seed, then refreshes `E000[0x0093]=0x9020` every 0.60 s. This is the lowest-noise active hold that has already kept `CONNECT: OK` alive.
|
||||||
|
|
||||||
|
The broad scenario streams `E000[0x008F]=0x1800` and `E000[0x0093]=0xFFFF` every cycle. This is noisier, but may open more local-control/status gates.
|
||||||
|
|
||||||
|
The gated scenario first seeds candidate secondary-table feature bits with command 6:
|
||||||
|
|
||||||
|
```text
|
||||||
|
06 00 15 80 00 C9 ; E400[0x0015] OTHERS/COPY visibility candidate
|
||||||
|
06 01 0F 18 00 4A ; E400[0x008F] shutter/OTHERS bits 11+12
|
||||||
|
06 01 13 FF FF 4E ; E400[0x0093] broad local-report/status gate candidate
|
||||||
|
```
|
||||||
|
|
||||||
|
Then it streams `E000[0x008F]=0x1800` and `E000[0x0093]=0x90FF`. This is the better next watch if quiet/broad E000-only refreshes do not produce local-control TX.
|
||||||
|
|
||||||
|
## Bench Webcam Capture
|
||||||
|
|
||||||
|
For tests where LCD timing/state matters, `serial_scenario.py` can take webcam
|
||||||
|
snapshots tied to exact TX commands. Current bench calibration:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
--camera-index 4 --snapshot-delays 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Camera index `4` is the working panel-facing webcam on the current PC. A single
|
||||||
|
`0.5` second post-TX delay captures readable LCD changes without creating the
|
||||||
|
large image sets produced by `0,0.25,1.0`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\copy-step-006d-006c-1000ms.json --parity E --quiet-console --log captures\copy-webcam.txt --result-json captures\copy-webcam-result.json --snapshot-dir captures\copy-webcam-shots --camera-index 4 --snapshot-delays 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
After a run, summarize unexpected device frames with:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario_unexpected.py captures\active-control-report-watch-quiet.txt --show-all
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario_unexpected.py captures\active-control-report-watch-broad.txt --show-all
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario_unexpected.py captures\active-control-report-watch-gated.txt --show-all
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected refresh responses are ignored by default: heartbeat, table readbacks, and `02 00 02 00 00 5A`. Any remaining checksum-valid frame is a candidate local-control report. This is where physical button/dial reports should appear if the RCP only talks while the CCU keeps the selector tables active.
|
||||||
|
|
||||||
|
The unexpected-frame summarizer also labels known autonomous button frames so they are not mistaken for newly unlocked controls:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 00 15 80 00 CF ; known CALL active report
|
||||||
|
00 00 15 00 00 4F ; known CALL inactive report
|
||||||
|
00 00 07 80 00 DD ; known CAM POWER report
|
||||||
|
```
|
||||||
|
|
||||||
|
First quiet/broad watch captures on 2026-05-26 showed no unexpected checksum-valid frames in the observed window:
|
||||||
|
|
||||||
|
```text
|
||||||
|
active-control-report-watch-quiet: 114 detected frames, all expected refresh/heartbeat/readback
|
||||||
|
active-control-report-watch-broad: 133 detected frames, all expected refresh/heartbeat/readback
|
||||||
|
```
|
||||||
|
|
||||||
|
Those runs stopped mid-scenario, so this is not a full negative proof. The practical interpretation is narrower: E000 active-state refresh alone did not make the pressed controls emit visible TX in the watched window. The gated command-6 version is the next ROM-supported test.
|
||||||
|
|
||||||
|
The first gated watch capture produced a new periodic active-state response:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02 00 04 00 00 5C ; gated 0x0004 transition candidate, seen twice
|
||||||
|
01 00 04 00 00 5F ; gated 0x0004 active response candidate, seen 328 times
|
||||||
|
```
|
||||||
|
|
||||||
|
After treating those as expected gated refresh traffic, the capture had zero novel frames. A direct search found no known CALL/CAM POWER frames in that log. So the gated setup changed the session/status response shape, but it did not yet prove that physical controls beyond the already-known autonomous buttons are reporting.
|
||||||
|
|
||||||
|
## Lamp Selector Mapping
|
||||||
|
|
||||||
|
Bench lamp sweeps now prove that several panel outputs are directly driven by command-0 selector writes while `CONNECT: OK` is alive. The current detailed map lives in `docs/pt2-lamp-selector-map.md`.
|
||||||
|
|
||||||
|
Newest confirmed behavior:
|
||||||
|
|
||||||
|
- `lamp-known-button-selector-probe` made CAM, CALL, BARS, MASTER, and camera tally outputs flash individually.
|
||||||
|
- `0x0007 = 0x8000/0x0000` blinked CAM POWER in isolation.
|
||||||
|
- `0x0015 = 0x8000/0x0000` blinked CALL and red tally in isolation.
|
||||||
|
- Fresh-boot isolation maps `0x0013` to SLAVE, `0x0016` to green tally, and `0x0017` to BARS for the `0x8000/0x0000` value pair.
|
||||||
|
- Fresh-boot isolation maps `0x0092` to iris AUTO/OFF behavior, and maps both `0x00B9` and `0x0110` to KNEE-related behavior.
|
||||||
|
- A ROM trace now explains why the KNEE tests looked non-level-held. `loc_1795` is reached from the panel input lane (`F104 -> F692 -> F6F0.1`) and reads `E000[0x00B9]` plus `E000[0x0110]`.
|
||||||
|
- The live KNEE value/report gate is `0x00B9.13`, not the earlier bench-only `0x00B9.15` guess.
|
||||||
|
- `0x0110.15` forces a timed KNEE page/display override (`F732=0x1C03`, `FB02=0x14`), which matches the observed "lights, then clears" behavior.
|
||||||
|
- On that KNEE LCD page, `0x0110.15` selects `DL`, `0x00B9.15` selects `PRESET`, and both clear selects `AUTO`.
|
||||||
|
- When `0x00B9.13` is set and `0x0110.15` is clear, the ROM reports/updates selector `0x00BC` from the `F692 - F6B2` panel-input delta. See `docs/pt2-knee-rom-trace.md`.
|
||||||
|
- The first KNEE ROM probe produced a new bench LCD state with `DTL` on the left and `KNEE` on the right, matching the ROM page-0x1C DETAIL/KNEE neighborhood.
|
||||||
|
- A follow-up isolation lit the KNEE AUTO lamp in later KNEE windows but did not change the LCD. Current model: KNEE AUTO lamp/status is mostly selector driven, while the DETAIL/KNEE LCD page needs an additional local menu/display condition.
|
||||||
|
|
||||||
## What Is Still Unknown
|
## What Is Still Unknown
|
||||||
|
|
||||||
- The official PT2 names for commands and selectors.
|
- The official PT2 names for commands and selectors.
|
||||||
@@ -610,7 +744,7 @@ This fits the real panel behavior:
|
|||||||
5. Dump selector table state before and after CONNECT OK.
|
5. Dump selector table state before and after CONNECT OK.
|
||||||
6. Seed selectors `0x003`, `0x040`, and `0x0F6` after selector-zero OK and watch lamps/readouts.
|
6. Seed selectors `0x003`, `0x040`, and `0x0F6` after selector-zero OK and watch lamps/readouts.
|
||||||
7. Mine selector dispatch handlers for known UI text terms: `IRIS`, `GAIN`, `SHUTTER`, `BARS`, `BLACK`, `CALL`, `AUTO`, `DIAG`.
|
7. Mine selector dispatch handlers for known UI text terms: `IRIS`, `GAIN`, `SHUTTER`, `BARS`, `BLACK`, `CALL`, `AUTO`, `DIAG`.
|
||||||
8. Build a fake-CCU streamer that repeatedly writes a small selector set and logs which RCP reports appear.
|
8. Run the active-control watch scenarios and map any unexpected frames back to physical controls.
|
||||||
|
|
||||||
## Source Files And Reports
|
## Source Files And Reports
|
||||||
|
|
||||||
|
|||||||
215
docs/pt2-report-aftermath-trace.md
Normal file
215
docs/pt2-report-aftermath-trace.md
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# PT2 Report Aftermath ROM Trace
|
||||||
|
|
||||||
|
This note tracks what the ROM does after the RCP emits an autonomous serial report from the `F870` report queue.
|
||||||
|
|
||||||
|
## Core Model
|
||||||
|
|
||||||
|
The RCP report path is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
3E54 / 4046 enqueue selector into F870
|
||||||
|
3FD3 checks send gates
|
||||||
|
BAF2 dequeues/stages a report from F870 and E800
|
||||||
|
BA26 sends the six-byte SCI1 frame
|
||||||
|
BB46-BB51 arms the continuation/resend aftermath
|
||||||
|
```
|
||||||
|
|
||||||
|
The important consequence is that `BAF2` does not advance the report consumer cursor. The emitted report remains outstanding until the host sends a valid continuation command.
|
||||||
|
|
||||||
|
## Report Queue
|
||||||
|
|
||||||
|
| RAM | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `F870` | autonomous serial-report word ring |
|
||||||
|
| `F9B0` | producer cursor |
|
||||||
|
| `F9B5` | consumer/outstanding cursor |
|
||||||
|
|
||||||
|
Producer paths:
|
||||||
|
|
||||||
|
- `H'3E54`: when `R2.7` is set, deduplicates and appends `R3` to `F870`, then advances `F9B0`.
|
||||||
|
- `H'4046-H'4070`: when heartbeat/report cadence expires and the report queue is empty, appends selector `0x0000` to `F870`.
|
||||||
|
|
||||||
|
Consumer path:
|
||||||
|
|
||||||
|
- `H'BAF2` compares `F9B5` against `F9B0`.
|
||||||
|
- If equal, no report is pending.
|
||||||
|
- If different, it reads selector `F870[ F9B5 ]`, reads the report value from `E800[selector]`, stages the outgoing frame, and calls `BA26`.
|
||||||
|
- `BAF2` leaves `F9B5` unchanged.
|
||||||
|
|
||||||
|
Only continuation command `4`, `5`, or `6` advances `F9B5`, and only when `FAA2.3` is still set.
|
||||||
|
|
||||||
|
## Report Send Gates
|
||||||
|
|
||||||
|
`H'3FD3` allows `BAF2` only when:
|
||||||
|
|
||||||
|
- `FAA2 == 0`.
|
||||||
|
- `F9C0 == 0`.
|
||||||
|
- If `FAA5.7` is set, `F9C3 == 0`, meaning no partial RX frame is currently being assembled.
|
||||||
|
|
||||||
|
This means host RX traffic can block report sending while a session is active.
|
||||||
|
|
||||||
|
## Aftermath Window
|
||||||
|
|
||||||
|
After `BA26` sends the report frame, `H'BB46-H'BB51` arms the report aftermath:
|
||||||
|
|
||||||
|
| Address | Write | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `H'BB00` | set `FAA2.3` | queued report needs continuation |
|
||||||
|
| `H'BB46` | `F9C6 = 0x01F4` | resend spacing countdown |
|
||||||
|
| `H'BB4C` | `F9C8 = 0x14` | resend/retry budget |
|
||||||
|
| `H'BB51` | `FAA3 = 0x80` | live resend/window marker |
|
||||||
|
|
||||||
|
While this is live, valid RX frames enter the continuation dispatcher because `FAA2 != 0`.
|
||||||
|
|
||||||
|
## Clean Continuation Consume
|
||||||
|
|
||||||
|
Commands `4`, `5`, and `6` all share the same report-consume pattern:
|
||||||
|
|
||||||
|
```text
|
||||||
|
if FAA2.3 set:
|
||||||
|
F9B5 += 1
|
||||||
|
clear F9B5.7
|
||||||
|
clear FAA3
|
||||||
|
clear FAA2
|
||||||
|
```
|
||||||
|
|
||||||
|
Handler addresses:
|
||||||
|
|
||||||
|
| Command | Handler | Consume block |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `4` | `H'BD0E` | `H'BD67-H'BD79` |
|
||||||
|
| `5` | `H'BD80` | `H'BDC2-H'BDD4` |
|
||||||
|
| `6` | `H'BDDB` | `H'BDED-H'BDFF` |
|
||||||
|
|
||||||
|
The ROM does not appear to require the continuation ACK selector to match the emitted report selector. The consume test is `FAA2.3`, not selector equality.
|
||||||
|
|
||||||
|
## Non-Clean Paths
|
||||||
|
|
||||||
|
Low commands `0/1/2/3` while `FAA2.3` is live:
|
||||||
|
|
||||||
|
- `H'BC5C` clears `FAA2.3`.
|
||||||
|
- If the bit was set, `H'BC63` clears `FAA3`.
|
||||||
|
- `H'BC67` re-enters initial dispatch.
|
||||||
|
- This does not advance `F9B5`.
|
||||||
|
|
||||||
|
Command `7`:
|
||||||
|
|
||||||
|
- Retransmits the previous finalized TX frame through `H'BE05-H'BE25`.
|
||||||
|
- Does not clear `FAA2/FAA3`.
|
||||||
|
- Does not advance `F9B5`.
|
||||||
|
|
||||||
|
TX/RX overlap interlock:
|
||||||
|
|
||||||
|
- At `H'BA84-H'BAA7`, TXI checks `FAA2.3 && FAA5.7 && F9C3 != 0`.
|
||||||
|
- If true, it clears `FAA2.3`, clears `FAA3`, disables TXI, and loads `F9C0=0x1F`.
|
||||||
|
- This collapses the report window without consuming `F9B5`.
|
||||||
|
- Bench implication: starting a host frame before the RCP has finished TX can destroy the very continuation window being targeted.
|
||||||
|
|
||||||
|
## Resend And Expiry
|
||||||
|
|
||||||
|
`H'BE9E-H'BEE8` manages the unconsumed report aftermath:
|
||||||
|
|
||||||
|
- Masks `FAA3` with `FAA5.7`.
|
||||||
|
- If the session gate is gone, `FAA3` becomes zero and `FAA2` is cleared.
|
||||||
|
- If `FAA3.7` is live and `F9C6 != 0`, it waits.
|
||||||
|
- If `F9C6 == 0` and `F9C8 != 0`, it decrements `F9C8`, reloads `F9C6=0x01F4`, clears `F9C3`, and calls `BA26` to resend the same finalized frame.
|
||||||
|
- If `F9C8 == 0`, it clears `F9C5`, forcing the broader session timeout path.
|
||||||
|
|
||||||
|
So an unconsumed report can be resent repeatedly, then eventually force the session watchdog to expire.
|
||||||
|
|
||||||
|
## Session Timeout
|
||||||
|
|
||||||
|
`F9C5` is the broad active-session watchdog:
|
||||||
|
|
||||||
|
- A complete six-byte RX frame reloads `F9C5=0x14` at `H'BB9E`.
|
||||||
|
- FRT2 decrements `F9C5` at `H'BF31-H'BF37`.
|
||||||
|
- `H'3FEF` observes expiry, clears `F9B5/F9B0`, clears `FAA5.7`, and if the gate was previously live calls `H'400C`.
|
||||||
|
- `H'400C -> H'4075 -> H'4217` clears broad session/display state and redraws `CONNECT:NOT ACT`.
|
||||||
|
|
||||||
|
Related timers:
|
||||||
|
|
||||||
|
| RAM | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `F9C0` | post-TX/report-send gate; decremented by FRT1 |
|
||||||
|
| `F9C1` | RX inter-byte timer; partial-frame timeout |
|
||||||
|
| `F9C4` | heartbeat/report enqueue cadence |
|
||||||
|
| `F9C5` | active serial-session watchdog |
|
||||||
|
| `F9C6/F9C8` | unconsumed report resend spacing/budget |
|
||||||
|
|
||||||
|
## Fake-CCU Implications
|
||||||
|
|
||||||
|
Best pure report ACK:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 40 00 00 1F
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale:
|
||||||
|
|
||||||
|
- Command `5` consumes the report cursor when `FAA2.3` is live.
|
||||||
|
- Selector `0x0040` is not one of the known command-5 special side-effect selectors.
|
||||||
|
- Value bytes are ignored by the generic command-5 consume path.
|
||||||
|
|
||||||
|
Same-selector command-5 ACKs are plausible for human readability, but not ROM-required:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 00 00 00 5F ; ACK selector 0x0000
|
||||||
|
05 00 02 00 00 5D ; ACK selector 0x0002
|
||||||
|
05 00 07 00 00 58 ; ACK selector 0x0007
|
||||||
|
05 00 15 00 00 4A ; ACK selector 0x0015
|
||||||
|
05 01 0F 00 00 51 ; ACK selector 0x008F
|
||||||
|
```
|
||||||
|
|
||||||
|
Use command `4` only when the host intentionally wants to refresh primary/current state while consuming:
|
||||||
|
|
||||||
|
```text
|
||||||
|
04 00 00 80 00 DE ; selector-zero active refresh
|
||||||
|
```
|
||||||
|
|
||||||
|
Use command `6` only when the host intentionally wants to refresh secondary feature gates while consuming:
|
||||||
|
|
||||||
|
```text
|
||||||
|
06 00 15 00 01 48 ; E400[0x0015] = 1
|
||||||
|
06 01 0F 18 00 4A ; E400[0x008F] = 0x1800
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid command-5 special selectors as generic ACKs unless the side effect is desired:
|
||||||
|
|
||||||
|
```text
|
||||||
|
05 00 6C 00 00 33 ; COPY completion/exit sibling
|
||||||
|
05 00 6D 00 00 32 ; COPY IN PROGRESS/start
|
||||||
|
05 00 6E 00 00 31 ; special accepted, later handler is cleanup/no-op-like
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proposed Normal Loop
|
||||||
|
|
||||||
|
A plausible fake-CCU report loop is:
|
||||||
|
|
||||||
|
1. Keep the serial format at `38400 8E1`.
|
||||||
|
2. Seed active state, especially selector zero.
|
||||||
|
3. Listen for a complete RCP TX frame.
|
||||||
|
4. Wait until the RCP frame is fully transmitted.
|
||||||
|
5. Quickly send a command-5 generic ACK before `F9C6/F9C8` resend handling starts.
|
||||||
|
6. Continue sending enough valid six-byte frames to reload `F9C5` before session expiry.
|
||||||
|
7. Add command-4/command-6 state refreshes only when we intentionally want to update lamps, displays, or menu gates.
|
||||||
|
|
||||||
|
This is the current best ROM-supported path for maintaining `CONNECT: OK` without accidentally consuming the report incorrectly or triggering COPY/menu side effects.
|
||||||
|
|
||||||
|
## Emulator Check
|
||||||
|
|
||||||
|
Reactive emulator testing supports this model:
|
||||||
|
|
||||||
|
- Seeded active state with command `0`, selector zero value `0x8080`.
|
||||||
|
- Waited for a report window where `FAA2.3` and `FAA3.7` were set and SCI1 TXI was idle.
|
||||||
|
- Sent `05 00 40 00 00 1F`.
|
||||||
|
- The ACK path reached the command-5 consume block and advanced `F9B5`, then cleared `FAA3` and `FAA2`.
|
||||||
|
- Repeating that reactive loop 12 times kept the emulated LCD at `CONNECT: OK`.
|
||||||
|
|
||||||
|
Observed clean consume shape after each ACK:
|
||||||
|
|
||||||
|
```text
|
||||||
|
before: FAA2=08 FAA3=80 F9B5=N
|
||||||
|
after: FAA2=00 FAA3=00 F9B5=N+1
|
||||||
|
```
|
||||||
|
|
||||||
|
A fixed-delay or extra-guard strategy was less reliable. In one run, waiting an additional 1 ms after TX idle before sending the ACK only consumed two reports before falling back to `CONNECT:NOT ACT`. That result should not be overfit as an exact real-device limit, but it reinforces the ROM timing lesson: ACK as a reaction to a finished frame, not as a loose periodic delay.
|
||||||
186
docs/pt2-session-rhythm-trace.md
Normal file
186
docs/pt2-session-rhythm-trace.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# PT2 Session Rhythm ROM Trace
|
||||||
|
|
||||||
|
This note tracks ROM evidence for the CCU/RCP "session rhythm": which received commands update selector state, which internal queues process those selectors, and which timers expire visible states back toward `CONNECT: NOT ACT`.
|
||||||
|
|
||||||
|
## RX Command Front Door
|
||||||
|
|
||||||
|
The serial command dispatcher reads `F860 & 0x07` at `H'BC08-H'BC0C`.
|
||||||
|
|
||||||
|
Dispatch is split by `FAA2`:
|
||||||
|
|
||||||
|
| Command | Dispatcher state | Handler | Main effect |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `0` | initial/idle, `FAA2 == 0` | `H'BC69` | writes primary `E000` and current/report `E800`, sets `EC00` dirty bit 7, appends selector to `F970`, replies with command-4 style echo |
|
||||||
|
| `1` | initial/idle, `FAA2 == 0`, `F861.7 == 0` | `H'BCD7` | reads primary `E000` and replies |
|
||||||
|
| `2` | initial/idle, `FAA2 == 0` | `H'BD04` | clears `FAA2.7`, likely abort/clear |
|
||||||
|
| `4` | continuation, `FAA2 != 0` | `H'BD0E` | writes primary `E000`, sets `EC00` dirty bit 7, appends selector to `F970`; selector zero also writes `E800`; no immediate reply |
|
||||||
|
| `5` | continuation, `FAA2 != 0` | `H'BD80` | ACK/selector side-effect path; special selectors can append to `F970` or clear latches |
|
||||||
|
| `6` | continuation, `FAA2 != 0` | `H'BDDB` | writes secondary `E400`, sets `EC00` dirty bit 6; no immediate reply |
|
||||||
|
| `7` | both states | `H'BE05` | retransmits previous frame or emits retry/error echo |
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- The normal active link is stateful. Commands `4/5/6` only do their intended work in the continuation side where `FAA2 != 0`.
|
||||||
|
- Command `0` is both a value update and a continuation opener because it sets `FAA2.7`, writes tables, appends the selector to `F970`, and emits a reply.
|
||||||
|
- Command `4` is not identical to command `0`: for nonzero selectors it updates `E000` but does not directly update `E800` in this handler. That matters because autonomous reports read values from `E800`.
|
||||||
|
- Command `6` does not directly display anything. It changes the secondary feature/visibility table consumed later by local page code such as `5FD2`.
|
||||||
|
|
||||||
|
## Serial Session Timeout
|
||||||
|
|
||||||
|
The broad CCU-traffic watchdog is `F9C5`.
|
||||||
|
|
||||||
|
RXI frame capture path:
|
||||||
|
|
||||||
|
- `H'BB90-H'BB96` stores received bytes into `F868-F86D` and increments `F9C3`.
|
||||||
|
- When `F9C3 == 6`, `H'BB9E` loads `F9C5=0x14`.
|
||||||
|
- Main-loop handler `H'BBAB` only validates/dispatches a frame once `F9C3 == 6`.
|
||||||
|
|
||||||
|
FRT2 OCIA timer path:
|
||||||
|
|
||||||
|
- `H'BF31-H'BF37` decrements `F9C5` when nonzero.
|
||||||
|
- `H'3FEF` observes `F9C5`; once it is zero, the main loop can clear `F9B5/F9B0`, clear `FAA5.7`, then call `H'400C`.
|
||||||
|
- `H'400C` clears the broad session/display state and calls `H'4217`, which redraws `CONNECT:NOT ACT`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- Any complete six-byte RX frame refreshes the short serial-session watchdog, independent of whether it ultimately produces the desired command-side effect.
|
||||||
|
- This is probably the main `CONNECT: OK -> CONNECT:NOT ACT` timer when CCU-like traffic stops.
|
||||||
|
- Separate display overlays still use `FB02`, so a page can expire even while serial traffic is otherwise alive.
|
||||||
|
|
||||||
|
## Selector Queue
|
||||||
|
|
||||||
|
`H'BE70` appends selectors to the processing queue at `F970`. `H'3E54` can also append to this queue when its mode byte has `R2.6` set.
|
||||||
|
|
||||||
|
Important queue RAM:
|
||||||
|
|
||||||
|
| RAM | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `F970` | selector-processing ring |
|
||||||
|
| `F9B4` | queue write cursor |
|
||||||
|
| `F9B9` | queue read cursor |
|
||||||
|
|
||||||
|
Queue processing happens at `H'2806` during the main loop:
|
||||||
|
|
||||||
|
1. If `F9B9 == F9B4`, no selector is pending.
|
||||||
|
2. Otherwise it reads the next selector from `F970`, increments `F9B9`, and masks the selector to `0x01FF`.
|
||||||
|
3. If the selector matches any active display slot (`F736/F738/F73A/F73C/F73E/F740/F742/F754`), it calls `H'48FA` before the selector-specific dispatch.
|
||||||
|
4. It then jumps through the selector handler table at `H'28A6`.
|
||||||
|
|
||||||
|
This explains why the same incoming selector can either look inert or redraw the LCD: the selector must match the current active display/page slots before `48FA` is called from the queue bridge.
|
||||||
|
|
||||||
|
## Autonomous Report Queue
|
||||||
|
|
||||||
|
The report queue is separate from the selector-processing queue.
|
||||||
|
|
||||||
|
| RAM | Role |
|
||||||
|
| --- | --- |
|
||||||
|
| `F870` | autonomous outbound report queue |
|
||||||
|
| `F9B0` | report producer cursor |
|
||||||
|
| `F9B5` | report consumer cursor |
|
||||||
|
|
||||||
|
Important report path:
|
||||||
|
|
||||||
|
- `H'3E54` appends report selectors to `F870` when its mode byte has `R2.7` set.
|
||||||
|
- `H'3FD3` sends reports only when `FAA2 == 0`, `F9C0 == 0`, and if `FAA5.7` is set then `F9C3 == 0`.
|
||||||
|
- `H'BAF2` drains `F870`, reads the report value from `E800 + 2*selector`, stages a six-byte TX frame, and sends it through `H'BA26`.
|
||||||
|
- `H'BB00` sets `FAA2.3` when a queued report is sent.
|
||||||
|
- After a report send, the ROM creates a continuation window with `F9C6=0x01F4`, `F9C8=0x14`, and `FAA3=0x80`.
|
||||||
|
- Continuation commands `4`, `5`, or `6` advance `F9B5` when `FAA2.3` was set, then clear `FAA3/FAA2`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- A fake CCU probably needs to consume autonomous reports, not only stream status words.
|
||||||
|
- If a report is sent and the CCU does not answer during the continuation window, the RCP can repeat, retry, or let the broader session gates decay.
|
||||||
|
- Because report values come from `E800`, command `0` and local RCP report handlers are stronger report-value refreshes than nonzero command `4` writes.
|
||||||
|
|
||||||
|
## TX / Heartbeat Timing Gates
|
||||||
|
|
||||||
|
`H'BA26` is the common TX-finalize helper.
|
||||||
|
|
||||||
|
Observed reloads:
|
||||||
|
|
||||||
|
- `BA26` sets `F9C0=0x64` and `F9C4=0x07` after send finalization.
|
||||||
|
- TX completion later sets `F9C0=0x09`, or `F9C0=0xF0` when `F795.6` is set.
|
||||||
|
- The FRT2 path decrements `F9C4`; `H'4046` can enqueue heartbeat/report selector `0x0000` when the queue is empty and the gate is open.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- The roughly 700 ms heartbeat cadence is the `F9C4=0x07` post-send countdown with the current FRT2 tick model.
|
||||||
|
- COPY state can slow or alter TX pacing through `F795.6 -> F9C0=0xF0`.
|
||||||
|
|
||||||
|
## Display Restore Timer
|
||||||
|
|
||||||
|
`FB03.7` marks a temporary/timed display override. `FB02` is its countdown.
|
||||||
|
|
||||||
|
Writers that start timed display overrides:
|
||||||
|
|
||||||
|
| Address | State written | Visible candidate |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `H'1726` | `F732=0x1C07`, `FB02=0x14`, `FB03.7=1` | DETAIL/KNEE-style timed page |
|
||||||
|
| `H'176E` | `F732=0x1C06`, `FB02=0x14`, `FB03.7=1` | DETAIL/KNEE-style timed page |
|
||||||
|
| `H'2135` | `F732=0x1C03`, `FB02=0x14`, `FB03.7=1` | KNEE page seen in bench probes |
|
||||||
|
| `H'26F6` | `F732=0x1C01`, `FB02=0x14`, `FB03.7=1` | adjacent local menu overlay |
|
||||||
|
| `H'3004` | `F732=0x1904`, `FB02=0x14`, `FB03.7=1` | `COPY COMPLETED` |
|
||||||
|
| `H'3038` | `F732=0x1903`, `FB02=0x64`, `FB03.7=1` | `COPY IN PROGRESS` |
|
||||||
|
| `H'7092` | `FB03.7=1`, `FB02=0x14` | `SET RCP` / `MASTER` fallback from local COPY gate |
|
||||||
|
|
||||||
|
FRT2 OCIA timer path `H'BF50-H'BF6B`:
|
||||||
|
|
||||||
|
- If `FB03.7` is clear, nothing happens.
|
||||||
|
- If `FB02 != 0`, the timer decrements `FB02`.
|
||||||
|
- If `FB02 == 0`, it clears `FB03.7` and calls `H'48EF`.
|
||||||
|
- `H'48EF` restores `F732` from `F734`, then calls `H'48FA`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- Many visible pages are deliberate timed overlays, not durable states.
|
||||||
|
- A scenario can appear to "drop to NOT ACT" simply because the overlay expires and restores the saved page.
|
||||||
|
|
||||||
|
## COPY / Activity Latch Timers
|
||||||
|
|
||||||
|
COPY-related selectors are handled through command 5 and the `F970` selector queue:
|
||||||
|
|
||||||
|
| Selector | Handler | Timer/latch effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `0x006C` | `H'2FAF` | completion/exit sibling; can clear `F731.7`, load `F797=0x1E` or `0x14`, and display `COPY COMPLETED` |
|
||||||
|
| `0x006D` | `H'3015` | start/progress; sets `F731.7`, `F795.6`, `F795.7`, `F798=0xC8`, displays `COPY IN PROGRESS` |
|
||||||
|
| `0x006E` | via command-5 special path | appended to `F970`; exact selector handler still needs labeling |
|
||||||
|
|
||||||
|
FRT2 OCIA timeout path:
|
||||||
|
|
||||||
|
- `H'BFA3-H'BFAF`: if `F797` is nonzero, decrement it; when it reaches zero, clear `F731.7`.
|
||||||
|
- `H'BFB3-H'BFBF`: if `F798` is nonzero, decrement it; when it reaches zero, clear `F731.7`.
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- `F731.7` is an activity/copy/session latch with explicit timeout clear paths.
|
||||||
|
- `0x006D` refreshes the long progress window (`F798=0xC8` and `FB02=0x64`).
|
||||||
|
- `0x006C` only makes sense as a completion/exit frame when the copy/progress flags are live.
|
||||||
|
|
||||||
|
## Reset / NOT ACT Baseline
|
||||||
|
|
||||||
|
`H'400C` clears broad session/display state:
|
||||||
|
|
||||||
|
- clears `F732`, `FB03`, `F791`, `F795`, `F76E`
|
||||||
|
- calls `H'4217`
|
||||||
|
|
||||||
|
`H'4217`:
|
||||||
|
|
||||||
|
- clears `F798`
|
||||||
|
- sets `F731.7`
|
||||||
|
- clears output masks and writes the LCD text `CONNECT:NOT ACT`
|
||||||
|
|
||||||
|
This looks like the firmware's explicit inactive-session baseline.
|
||||||
|
|
||||||
|
## Current Rhythm Hypothesis
|
||||||
|
|
||||||
|
The session rhythm is probably not one magic keepalive frame. The ROM suggests a loop with three layers:
|
||||||
|
|
||||||
|
1. **Any complete six-byte RX frame** refreshes the broad `F9C5` session watchdog.
|
||||||
|
2. **Command-0 primary selector updates** keep both `E000` and `E800` current, append active selectors to `F970`, and reply.
|
||||||
|
3. **Command-4 continuation primary updates** keep `E000` current and append selectors to `F970`, but only selector zero directly refreshes `E800`.
|
||||||
|
4. **Autonomous reports from `F870`** must be consumed by continuation commands so the report cursor advances.
|
||||||
|
5. **Command-6 secondary selector updates** advertise which features/menu entries are valid through `E400`.
|
||||||
|
6. **Command-5 ACK/special selectors** advance report windows and drive side-effect selectors such as `0x006C/0x006D`.
|
||||||
|
|
||||||
|
The panel stays active when enough selector updates hit the right active slots and refresh the timed/latch counters before FRT2 expires them.
|
||||||
214
docs/pt2-shutter-display-trace.md
Normal file
214
docs/pt2-shutter-display-trace.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# PT2 Shutter Display Trace
|
||||||
|
|
||||||
|
This note collects the current ROM evidence for the shutter seven-segment display and related selector traffic.
|
||||||
|
|
||||||
|
## Confirmed Bench Mapping
|
||||||
|
|
||||||
|
The real panel responds to primary selector `0x008F`:
|
||||||
|
|
||||||
|
| Command 0 write | Meaning candidate | Observed panel result |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `00 01 0F 08 00 5C` | `E000[0x008F].11` | SHUTTER shows observed `EUS`, likely manual `EVS`; iris AUTO lamp on |
|
||||||
|
| `00 01 0F 10 00 44` | `E000[0x008F].12` | SHUTTER shows literal `OFF`; iris AUTO lamp on |
|
||||||
|
| `00 01 0F 18 00 4C` | bits 11 and 12 | likely `EVS` wins over `OFF`; iris AUTO lamp on |
|
||||||
|
| `00 01 0F 00 00 54` | live default/clear | in the short clear-control run, SHUTTER swapped to `OFF`, then back to `EVS` when bit 11 was restored |
|
||||||
|
|
||||||
|
Manual correlation:
|
||||||
|
|
||||||
|
- The RCP-TX7 manual has `OTHERS (1/6: SHUTTER)` with an `EVS` button.
|
||||||
|
- The RCP-TX7 menu table lists `EVS/ECS` under OTHERS for DXC-D30/D30P.
|
||||||
|
- A later CCU/RCP manual says the shutter display shows `EVS` when EVS is on and `OFF` when the shutter switch is off.
|
||||||
|
|
||||||
|
So `0x008F.11` is currently best labeled `shutter_evs_display_or_mode`. `0x008F.12` and `0x008F=0` both have `OFF` display evidence, so treat `OFF` as the live default/fallback shutter display unless a later bit-isolation run splits forced OFF from default OFF.
|
||||||
|
|
||||||
|
## Selector 0x008F ROM Path
|
||||||
|
|
||||||
|
Primary table address:
|
||||||
|
|
||||||
|
```text
|
||||||
|
selector 0x008F -> E000 + 2 * 0x008F = H'E11E
|
||||||
|
```
|
||||||
|
|
||||||
|
The OTHERS/SHUTTER menu handler is `H'6EE4`.
|
||||||
|
|
||||||
|
Important instructions:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6F6E: BTST.W #11, @H'E11E
|
||||||
|
6F74: BSET.B #6, R0
|
||||||
|
6F76: BTST.W #12, @H'E11E
|
||||||
|
6F7C: BSET.B #4, R0
|
||||||
|
6F7E: MOV:G.B R0, @H'F711
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the direct consumer seen so far. The current decompile does not show another static read of `H'E11E` outside this handler.
|
||||||
|
|
||||||
|
`H'6EE4` also programs local descriptor RAM before this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6EFD: F73E = H'088F
|
||||||
|
6F09: F74E = H'0800
|
||||||
|
6F0F: F742 = H'088F
|
||||||
|
6F1B: F752 = H'1000
|
||||||
|
```
|
||||||
|
|
||||||
|
Those descriptors match selector `0x008F` plus the two observed masks `0x0800` and `0x1000`, so the generic panel-output updater also appears to know about the same selector.
|
||||||
|
|
||||||
|
## Local Panel Trigger Path
|
||||||
|
|
||||||
|
`H'6EE4` can also generate a selector `0x008F` report/update from local OTHERS/SHUTTER controls:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6F26: read F770 local action byte
|
||||||
|
6F2E: keep low two action bits
|
||||||
|
6F37: if action bit path and E400[0x008F].11 is enabled, set E800[0x008F] = 0x0800
|
||||||
|
6F45: if alternate action path and E400[0x008F].12 is enabled, set E800[0x008F] = 0x1000
|
||||||
|
6F53: R3 = 0x008F
|
||||||
|
6F64: call 3E54 report/target-frame builder
|
||||||
|
```
|
||||||
|
|
||||||
|
Practical meaning:
|
||||||
|
|
||||||
|
- The CCU can force the display state by writing `E000[0x008F]`.
|
||||||
|
- The RCP can also try to report local EVS/OFF changes through selector `0x008F`, but only when the secondary-table feature bits `E400[0x008F].11/.12` allow it.
|
||||||
|
|
||||||
|
Additional local-key trace:
|
||||||
|
|
||||||
|
- `F109 -> F6D0.7` handler `H'24E8` writes `E800[0x008F]=0x8000` or `0x0000`, then queues selector `0x008F`.
|
||||||
|
- `F109 -> F6D0.6` handler `H'252E` writes `E800[0x008F]=0x2000` or `0x0000`, then queues selector `0x008F`.
|
||||||
|
- Both handlers first check the current session/page gate (`F731 <= 2`) and both can divert to timed page `F732=0x1C01` when `E000[0x0088].14` is set.
|
||||||
|
|
||||||
|
This splits selector `0x008F` into at least two roles:
|
||||||
|
|
||||||
|
- CCU-visible display/status bits `0x0800` and `0x1000`, bench-mapped to `EVS` and `OFF`.
|
||||||
|
- Local key/report bits `0x8000` and `0x2000`, emitted by the physical panel path when gates allow.
|
||||||
|
|
||||||
|
So the display bits that light `EVS/OFF` are not necessarily the same bits the local SHUTTER/OTHERS-adjacent buttons emit.
|
||||||
|
|
||||||
|
## Adjacent Shutter Value Family
|
||||||
|
|
||||||
|
The periodic/control-change scanner at `H'15E0` processes changed input words collected by IRQ3 from the external panel chips. Around the shutter path it calls `H'19A2`, which compares a local delta against the current primary table value and, if changed, writes the `E800` current table and calls `H'3E54`.
|
||||||
|
|
||||||
|
Candidate selectors:
|
||||||
|
|
||||||
|
| Handler | Source RAM | Gate | Selector sent | Meaning candidate |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `H'17C9` | `F6AE/F6CE` | `E000[0x0093].12` | `0x00A3` | clear-scan/shutter value lane |
|
||||||
|
| `H'17FB` | `F6AC/F6CC` | `E000[0x0093].12` | `0x00A4` | clear-scan/shutter value lane |
|
||||||
|
| `H'182D` | `F6AA/F6CA` | `E000[0x0093].5`, `F717.2` clear | `0x00A5` | shutter value lane |
|
||||||
|
| `H'182D` | `F6AA/F6CA` | `F717.2` set | `0x00D8` | alternate/DXC-637 shutter lane |
|
||||||
|
| `H'1891` | `F6A8/F6C8` | `E000[0x0093].5`, `F717.2` clear | `0x0080` | shutter value lane |
|
||||||
|
| `H'1891` | `F6A8/F6C8` | `F717.2` set | `0x00D9` | alternate/DXC-637 shutter lane |
|
||||||
|
| `H'18E7` | `F6A6/F6C6` | `E000[0x0093].5`, `F717.2` clear | `0x00A6` | shutter value lane |
|
||||||
|
| `H'18E7` | `F6A6/F6C6` | `F717.2` set | `0x00DA` | alternate/DXC-637 shutter lane |
|
||||||
|
| `H'194A` | `F6A4/F6C4` | `F731 <= 3` | `0x0080` | shared value lane |
|
||||||
|
| `H'1979` | `F6A2/F6C2` plus `F68C` scale | `F731 <= 3` | `0x0081` | scaled analog value lane |
|
||||||
|
|
||||||
|
This cluster is a strong candidate for the numeric shutter speed / clear-scan frequency side of the display. It is not yet bench-confirmed.
|
||||||
|
|
||||||
|
ROM refinement:
|
||||||
|
|
||||||
|
- `F6AE/F6AC` are sampled from the external panel bus in the IRQ3 `A8` branch (`H'3CCB`), from `F00C/F00A`.
|
||||||
|
- `F6AA/F6A8/F6A6/F6A4/F6A2` are sampled from the IRQ3 `A9` branch (`H'3C49`), from `F00C/F00A/F008/F006/F004`.
|
||||||
|
- Those IRQ3 branches set dirty bits in `F6F1`, then the `H'15E0` scanner fans changes into the selector reports above.
|
||||||
|
- Several lanes can OR bit 14 into the report selector when `F791` and `F404` feature bits are live. So emitted report traffic may carry a tagged variant of the base selector even though the table readback probes below use the base selector.
|
||||||
|
- This makes the adjacent selector family more likely to be live local-panel state than a simple CCU-write display latch.
|
||||||
|
|
||||||
|
## Candidate Probe Frames
|
||||||
|
|
||||||
|
Readback:
|
||||||
|
|
||||||
|
```text
|
||||||
|
01 01 0F 00 00 55 ; read 0x008F
|
||||||
|
01 01 13 00 00 49 ; read 0x0093 gate/status
|
||||||
|
01 01 23 00 00 79 ; read 0x00A3
|
||||||
|
01 01 24 00 00 7E ; read 0x00A4
|
||||||
|
01 01 25 00 00 7F ; read 0x00A5
|
||||||
|
01 01 58 00 00 02 ; read 0x00D8
|
||||||
|
01 01 00 00 00 5A ; read 0x0080
|
||||||
|
01 01 59 00 00 03 ; read 0x00D9
|
||||||
|
01 01 26 00 00 7C ; read 0x00A6
|
||||||
|
01 01 5A 00 00 00 ; read 0x00DA
|
||||||
|
01 01 01 00 00 5B ; read 0x0081
|
||||||
|
```
|
||||||
|
|
||||||
|
Potential display probes after `CONNECT: OK`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
00 01 0F 00 00 54 ; clear selector 0x008F
|
||||||
|
00 01 13 80 00 C8 ; set 0x0093 high bit candidate
|
||||||
|
00 01 13 FF FF 48 ; enable all 0x0093 gates candidate
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the value-lane selectors cautiously. They may represent shutter/clear-scan numeric values, but they are also part of the local control-report path, so bench probing should change one selector at a time and record LCD, shutter display, iris AUTO, and emitted report frames.
|
||||||
|
|
||||||
|
Important methodology caveat:
|
||||||
|
|
||||||
|
- `CONNECT:NOT ACT` globally clears volatile panel presentation. A blank LCD/segment result after a long wait is not evidence that a tested selector cleared the panel.
|
||||||
|
- For selector-display tests, record the visible state inside the short post-write window, before the session can fall back to `NOT ACT`.
|
||||||
|
- Use the timeout-control scenario below to measure the current bench's natural EVS-to-NOT-ACT cleanup time. Treat results after that point as timeout-contaminated.
|
||||||
|
- Re-seed `CONNECT: OK` immediately before gate writes when a prior readback sweep may have consumed enough time for the visible state to expire.
|
||||||
|
|
||||||
|
## Adjacent Selector Bench Scenarios
|
||||||
|
|
||||||
|
These JSON scenarios are set up for the current bench runner and default to `38400 8E1`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-adjacent-readback.json --parity E --log captures\shutter-adjacent-readback.txt --result-json captures\shutter-adjacent-readback-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-008f-evs-timeout-control.json --parity E --log captures\shutter-008f-evs-timeout-control.txt --result-json captures\shutter-008f-evs-timeout-control-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-008f-evs-clear-control.json --parity E --log captures\shutter-008f-evs-clear-control.txt --result-json captures\shutter-008f-evs-clear-control-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-gate-8000.json --parity E --log captures\shutter-0093-gate-8000.txt --result-json captures\shutter-0093-gate-8000-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-gate-ffff.json --parity E --log captures\shutter-0093-gate-ffff.txt --result-json captures\shutter-0093-gate-ffff-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-bit-isolation.json --parity E --log captures\shutter-0093-bit-isolation.txt --result-json captures\shutter-0093-bit-isolation-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-auto-candidates.json --parity E --log captures\shutter-0093-blackflare-auto-candidates.txt --result-json captures\shutter-0093-blackflare-auto-candidates-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-slow-marked.json --parity E --log captures\shutter-0093-blackflare-slow-marked.txt --result-json captures\shutter-0093-blackflare-slow-marked-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-field-groups.json --parity E --log captures\shutter-0093-blackflare-field-groups.txt --result-json captures\shutter-0093-blackflare-field-groups-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-field-confirm.json --parity E --log captures\shutter-0093-blackflare-field-confirm.txt --result-json captures\shutter-0093-blackflare-field-confirm-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-field-stream-confirm.json --parity E --log captures\shutter-0093-blackflare-field-stream-confirm.txt --result-json captures\shutter-0093-blackflare-field-stream-confirm-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-confirm-f020.json --parity E --log captures\shutter-0093-blackflare-confirm-f020.txt --result-json captures\shutter-0093-blackflare-confirm-f020-result.json
|
||||||
|
-Manual-auto-manual
|
||||||
|
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-confirm-ff20.json --parity E --log captures\shutter-0093-blackflare-confirm-ff20.txt --result-json captures\shutter-0093-blackflare-confirm-ff20-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-confirm-9f20.json --parity E --log captures\shutter-0093-blackflare-confirm-9f20.txt --result-json captures\shutter-0093-blackflare-confirm-9f20-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-confirm-90ff.json --parity E --log captures\shutter-0093-blackflare-confirm-90ff.txt --result-json captures\shutter-0093-blackflare-confirm-90ff-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-confirm-9fff.json --parity E --log captures\shutter-0093-blackflare-confirm-9fff.txt --result-json captures\shutter-0093-blackflare-confirm-9fff-result.json
|
||||||
|
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\shutter-0093-blackflare-lowbyte-groups.json --parity E --log captures\shutter-0093-blackflare-lowbyte-groups.txt --result-json captures\shutter-0093-blackflare-lowbyte-groups-result.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Run order:
|
||||||
|
|
||||||
|
1. `shutter-adjacent-readback.json`: read-only baseline for the adjacent shutter cluster after the selector-zero `CONNECT: OK` seed.
|
||||||
|
2. `shutter-008f-evs-timeout-control.json`: measures how long EVS survives before `CONNECT:NOT ACT` clears it with no explicit clear command.
|
||||||
|
3. `shutter-008f-evs-clear-control.json`: checks whether the EVS/OFF display selector can be cleared live inside the safe pre-timeout window.
|
||||||
|
4. `shutter-0093-gate-8000.json`: tests the ROM-observed `0x0093` high-bit gate without enabling every unknown bit.
|
||||||
|
5. `shutter-0093-gate-ffff.json`: broader gate test if the targeted high-bit run is uneventful.
|
||||||
|
6. `shutter-0093-bit-isolation.json`: isolates the `0x0093` bits after bench evidence showed this selector also controls white-balance and black/flare lamps.
|
||||||
|
7. `shutter-0093-blackflare-auto-candidates.json`: uses the known `0x9020` black/flare-manual context and adds each remaining bit to hunt for the `0xFFFF` black/flare-AUTO effect.
|
||||||
|
8. `shutter-0093-blackflare-slow-marked.json`: repeats the hunt without clear/OK resets between candidates, so white-balance should flicker less and black/flare AUTO transitions can be matched to candidate holds.
|
||||||
|
9. `shutter-0093-blackflare-field-groups.json`: tests multi-bit field groups after the slow-marked run suggested black/flare AUTO is not one single extra bit over `0x9020`.
|
||||||
|
10. `shutter-0093-blackflare-field-confirm.json`: shorter, longer-hold confirmation of the broad field groups after the first field-groups run produced three MANUAL->AUTO->MANUAL swaps.
|
||||||
|
11. `shutter-0093-blackflare-field-stream-confirm.json`: repeats each candidate every 0.60 s after the longer-hold confirmation fell into `CONNECT:NOT ACT`, testing the CCU-refresh model directly.
|
||||||
|
12. `shutter-0093-blackflare-confirm-*.json`: one-candidate streamed confirmations. Each run includes a known `0xFFFF` AUTO positive control, then brackets one candidate with `0x9020` manual context. After the positive-control section, the expected candidate pattern is MANUAL -> AUTO -> MANUAL -> AUTO -> MANUAL only if that candidate drives black/flare AUTO.
|
||||||
|
13. `shutter-0093-blackflare-lowbyte-groups.json`: keeps the high byte fixed at `0x90` and varies only the low byte, after `0x90FF` and `0x9FFF` both toggled black/flare AUTO.
|
||||||
|
|
||||||
|
## Bench Observations 2026-05-26
|
||||||
|
|
||||||
|
Observed visible effects from the first adjacent-selector run set:
|
||||||
|
|
||||||
|
| Scenario | Serial evidence | Observed panel result | Current interpretation |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `shutter-adjacent-readback` | all read selectors returned value `0x0000` | no specific visible note | baseline cluster was clear |
|
||||||
|
| `shutter-0093-gate-8000` | `E000[0x0093]` read back as `0x8000` | white-balance PRESET lamp on, black/flare MANUAL lamp on | `0x0093.15` is a live panel-status/lamp bit, not just a shutter gate |
|
||||||
|
| `shutter-0093-gate-ffff` | `E000[0x0093]` read back as `0xFFFF` | white-balance PRESET lamp on, black/flare AUTO lamp on | another `0x0093` bit overrides/selects black/flare AUTO when all bits are set |
|
||||||
|
| `shutter-0093-bit-isolation` | stepped through `0x1000`, `0x0020`, `0x1020`, `0x8000`, `0x8020`, `0x9000`, `0x9020` | white-balance lamps swapped between MANUAL and PRESET; black/flare stayed MANUAL | bit 15 remains the prime white-balance PRESET candidate; black/flare AUTO is likely outside bits 15/12/5 |
|
||||||
|
| `shutter-0093-blackflare-auto-candidates` | stepped through `0x9020`, `0xFFFF`, then `0x9020` plus each remaining bit | black/flare swapped AUTO/MANUAL more slowly than white-balance MANUAL/PRESET | the clear/OK reset before each candidate likely caused extra white-balance flicker; use the slow-marked follow-up to map black/flare transitions to exact bits |
|
||||||
|
| `shutter-0093-blackflare-slow-marked` | held `0x9020`, `0xFFFF`, then alternated `0x9020` with each candidate | white-balance stayed stable, black/flare swapped twice with large pauses; panel stayed `CONNECT: OK` throughout | likely only the manual-reference to `0xFFFF` and `0xFFFF` back to `0x9020` changed black/flare, so AUTO is probably a multi-bit field rather than one added bit |
|
||||||
|
| `shutter-0093-blackflare-field-groups` | held broad grouped values such as `0xF020`, `0xFF20`, `0x9F20`, `0x90FF`, `0x9FFF` | black/flare made three MANUAL->AUTO->MANUAL swaps | broad field combinations can produce black/flare AUTO; use the shorter confirmation run to identify which grouped windows caused the swaps |
|
||||||
|
| `shutter-0093-blackflare-field-confirm` | held `0xFFFF` for 2.8 s, then candidate groups | one MANUAL->AUTO transition at the start, then `CONNECT:NOT ACT`; PRESET white-balance lamp briefly flashed a few times while inactive | a silent 2.8 s hold is too long; traffic must be refreshed, and some lamp latch/update paths can still flash during inactive display cleanup |
|
||||||
|
| `shutter-0093-blackflare-field-stream-confirm` | streamed every candidate at 0.60 s and saw 148 `02 00 02 00 00 5A` OK-path responses with no resync errors | manual/auto/manual groups were visible, then `CONNECT:NOT ACT` after the stream ended | the stream itself kept OK alive; the final inactive state is likely just the refresh stopping. Candidate isolation needs streamed, one-candidate tests |
|
||||||
|
| `shutter-0093-blackflare-confirm-f020`, `ff20`, `9f20` | each completed cleanly with 137 OK-path responses and no resync errors | MANUAL->AUTO->MANUAL only | only the built-in `0xFFFF` positive control produced AUTO; these candidates did not drive black/flare AUTO by themselves |
|
||||||
|
| `shutter-0093-blackflare-confirm-90ff`, `9fff` | each completed cleanly with 137 OK-path responses and no resync errors | black/flare went back and forth a couple of times | `0x90FF` is sufficient to drive the AUTO effect, so the black/flare field is likely in the low byte of `0x0093`; `0x9FFF` is a superset rather than a separate high-byte requirement |
|
||||||
|
| `shutter-008f-evs-clear-control` | `0x008F` writes acknowledged as `0x0800`, `0x0000`, `0x0800` | SHUTTER swapped between EVS and OFF while iris AUTO lamp stayed on | `0x008F` is a packed shutter/display status word; clearing it does not simply mean "blank" |
|
||||||
|
| `shutter-008f-evs-timeout-control` | only `0x008F=0x0800` was sent after OK seed | EVS/iris AUTO, then `CONNECT:NOT ACT` | later blanking is timeout cleanup, not selector-clear evidence |
|
||||||
|
|
||||||
|
This changes the local naming: the "adjacent shutter" group should be treated as a broader camera-status and panel-output word cluster. `0x008F` currently covers shutter EVS/OFF plus iris AUTO side effects; `0x0093` covers at least white-balance manual/preset and black/flare manual/auto side effects, and still gates some ROM-observed shutter/clear-scan report lanes. Current black/flare evidence points to `0x0093` high byte `0x90` as enough context, with the low byte selecting the black/flare MANUAL/AUTO state.
|
||||||
@@ -141,6 +141,9 @@ def format_frame(data: bytes) -> str:
|
|||||||
def label_frame(frame: bytes) -> str:
|
def label_frame(frame: bytes) -> str:
|
||||||
labels = {
|
labels = {
|
||||||
bytes.fromhex("0000000080DA"): "heartbeat",
|
bytes.fromhex("0000000080DA"): "heartbeat",
|
||||||
|
bytes.fromhex("00000080805A"): "active_selector0_keepalive_report",
|
||||||
|
bytes.fromhex("00006C000036"): "copy_completion_exit_selector_006c_candidate",
|
||||||
|
bytes.fromhex("00006D000037"): "copy_in_progress_selector_006d_candidate",
|
||||||
bytes.fromhex("02000200005A"): "connect_ok_path_response_candidate",
|
bytes.fromhex("02000200005A"): "connect_ok_path_response_candidate",
|
||||||
bytes.fromhex("010002000059"): "connect_c0_path_response_candidate",
|
bytes.fromhex("010002000059"): "connect_c0_path_response_candidate",
|
||||||
bytes.fromhex("07804040A07D"): "visible_40A0_family_40",
|
bytes.fromhex("07804040A07D"): "visible_40A0_family_40",
|
||||||
@@ -148,6 +151,38 @@ def label_frame(frame: bytes) -> str:
|
|||||||
bytes.fromhex("0780C040A0FD"): "visible_40A0_family_C0",
|
bytes.fromhex("0780C040A0FD"): "visible_40A0_family_C0",
|
||||||
bytes.fromhex("07804020902D"): "visible_retry_0040_2090_candidate",
|
bytes.fromhex("07804020902D"): "visible_retry_0040_2090_candidate",
|
||||||
bytes.fromhex("0780C060205D"): "visible_C0_6020_family_candidate",
|
bytes.fromhex("0780C060205D"): "visible_C0_6020_family_candidate",
|
||||||
|
bytes.fromhex("0000158000CF"): "known_call_button_active_report",
|
||||||
|
bytes.fromhex("00001500004F"): "known_call_button_inactive_report",
|
||||||
|
bytes.fromhex("0000078000DD"): "known_cam_power_button_report",
|
||||||
|
bytes.fromhex("000013000049"): "known_iris_mblack_link_clear_report_candidate",
|
||||||
|
bytes.fromhex("000013400009"): "known_iris_mblack_link_active_report_candidate",
|
||||||
|
bytes.fromhex("0000138000C9"): "known_selector_0013_bit15_report_candidate",
|
||||||
|
bytes.fromhex("000013C00089"): "known_selector_0013_bit15_plus_iris_mblack_link_report_candidate",
|
||||||
|
bytes.fromhex("00010F8000D4"): "known_shutter_onoff_bit7_report_candidate",
|
||||||
|
bytes.fromhex("00010F200074"): "known_shutter_onoff_bit6_report_candidate",
|
||||||
|
bytes.fromhex("00010F000054"): "known_shutter_onoff_clear_report_candidate",
|
||||||
|
bytes.fromhex("010013000048"): "queued_iris_mblack_link_clear_report_candidate",
|
||||||
|
bytes.fromhex("02001300004B"): "queued_iris_mblack_link_clear_report_candidate",
|
||||||
|
bytes.fromhex("010013400008"): "queued_iris_mblack_link_active_report_candidate",
|
||||||
|
bytes.fromhex("02001340000B"): "queued_iris_mblack_link_active_report_candidate",
|
||||||
|
bytes.fromhex("0100138000C8"): "queued_selector_0013_bit15_report_candidate",
|
||||||
|
bytes.fromhex("0200138000CB"): "queued_selector_0013_bit15_report_candidate",
|
||||||
|
bytes.fromhex("010013C00088"): "queued_selector_0013_bit15_plus_iris_mblack_link_report_candidate",
|
||||||
|
bytes.fromhex("020013C0008B"): "queued_selector_0013_bit15_plus_iris_mblack_link_report_candidate",
|
||||||
|
bytes.fromhex("01010F8000D5"): "queued_shutter_onoff_bit7_report_candidate",
|
||||||
|
bytes.fromhex("02010F8000D6"): "queued_shutter_onoff_bit7_report_candidate",
|
||||||
|
bytes.fromhex("01010F200075"): "queued_shutter_onoff_bit6_report_candidate",
|
||||||
|
bytes.fromhex("02010F200076"): "queued_shutter_onoff_bit6_report_candidate",
|
||||||
|
bytes.fromhex("01010F000055"): "queued_shutter_onoff_clear_report_candidate",
|
||||||
|
bytes.fromhex("02010F000056"): "queued_shutter_onoff_clear_report_candidate",
|
||||||
|
bytes.fromhex("0100178000CC"): "queued_bars_button_selector_0017_active_candidate",
|
||||||
|
bytes.fromhex("0200178000CF"): "queued_bars_button_selector_0017_active_candidate",
|
||||||
|
bytes.fromhex("0100188000C3"): "queued_bars_button_selector_0018_active_candidate",
|
||||||
|
bytes.fromhex("0200188000C0"): "queued_bars_button_selector_0018_active_candidate",
|
||||||
|
bytes.fromhex("01011A080048"): "queued_iris_auto_button_selector_009a_active_candidate",
|
||||||
|
bytes.fromhex("02011A08004B"): "queued_iris_auto_button_selector_009a_active_candidate",
|
||||||
|
bytes.fromhex("01000400005F"): "gated_active_0004_response_candidate",
|
||||||
|
bytes.fromhex("02000400005C"): "gated_active_0004_transition_candidate",
|
||||||
}
|
}
|
||||||
label = labels.get(frame, "")
|
label = labels.get(frame, "")
|
||||||
if label:
|
if label:
|
||||||
|
|||||||
168
h8536/camera_snapshots.py
Normal file
168
h8536/camera_snapshots.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import heapq
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
_SAFE_NAME_RE = re.compile(r"[^A-Za-z0-9_.-]+")
|
||||||
|
|
||||||
|
|
||||||
|
class CameraSnapshots:
|
||||||
|
"""Small optional OpenCV webcam wrapper for bench-test snapshots."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
camera_index: int,
|
||||||
|
output_dir: Path,
|
||||||
|
warmup_seconds: float = 0.5,
|
||||||
|
width: int | None = None,
|
||||||
|
height: int | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.camera_index = camera_index
|
||||||
|
self.output_dir = output_dir
|
||||||
|
self.warmup_seconds = max(0.0, warmup_seconds)
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.cv2: Any | None = None
|
||||||
|
self.device: Any | None = None
|
||||||
|
self._condition = threading.Condition()
|
||||||
|
self._tasks: list[tuple[float, int, dict[str, Any]]] = []
|
||||||
|
self._records: list[dict[str, Any]] = []
|
||||||
|
self._thread: threading.Thread | None = None
|
||||||
|
self._seq = 0
|
||||||
|
self._closing = False
|
||||||
|
|
||||||
|
def open(self) -> None:
|
||||||
|
try:
|
||||||
|
import cv2
|
||||||
|
except ImportError as exc: # pragma: no cover - depends on local bench environment.
|
||||||
|
raise SystemExit(
|
||||||
|
"OpenCV is required for webcam snapshots. Install it with: "
|
||||||
|
".\\.venv\\Scripts\\python.exe -m pip install opencv-python"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.cv2 = cv2
|
||||||
|
self.device = cv2.VideoCapture(self.camera_index)
|
||||||
|
if not self.device.isOpened():
|
||||||
|
raise SystemExit(f"could not open webcam index {self.camera_index}")
|
||||||
|
if self.width:
|
||||||
|
self.device.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
|
||||||
|
if self.height:
|
||||||
|
self.device.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
|
||||||
|
self._warm_up()
|
||||||
|
self._thread = threading.Thread(target=self._run, name="camera-snapshots", daemon=True)
|
||||||
|
self._thread.start()
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
with self._condition:
|
||||||
|
self._closing = True
|
||||||
|
self._condition.notify_all()
|
||||||
|
if self._thread is not None:
|
||||||
|
self._thread.join()
|
||||||
|
self._thread = None
|
||||||
|
if self.device is not None:
|
||||||
|
self.device.release()
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
def schedule(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
label: str,
|
||||||
|
frame_text: str,
|
||||||
|
phase: str,
|
||||||
|
delay_seconds: float = 0.0,
|
||||||
|
step_index: int | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if self.cv2 is None or self.device is None:
|
||||||
|
raise RuntimeError("camera snapshotter is not open")
|
||||||
|
|
||||||
|
due_monotonic = time.monotonic() + max(0.0, delay_seconds)
|
||||||
|
due_timestamp = datetime.now() + timedelta(seconds=max(0.0, delay_seconds))
|
||||||
|
timestamp = due_timestamp.strftime("%Y%m%d-%H%M%S-%f")[:-3]
|
||||||
|
step_part = f"step{step_index:03d}_" if step_index is not None else ""
|
||||||
|
frame_part = frame_text.replace(" ", "")
|
||||||
|
filename = _safe_name(f"{timestamp}_{step_part}{phase}_{label}_{frame_part}.jpg")
|
||||||
|
path = self.output_dir / filename
|
||||||
|
record: dict[str, Any] = {
|
||||||
|
"path": str(path),
|
||||||
|
"scheduled_timestamp": timestamp,
|
||||||
|
"camera_index": self.camera_index,
|
||||||
|
"step_index": step_index,
|
||||||
|
"phase": phase,
|
||||||
|
"delay_seconds": delay_seconds,
|
||||||
|
"label": label,
|
||||||
|
"frame": frame_text,
|
||||||
|
"status": "scheduled",
|
||||||
|
}
|
||||||
|
with self._condition:
|
||||||
|
self._seq += 1
|
||||||
|
heapq.heappush(self._tasks, (due_monotonic, self._seq, record))
|
||||||
|
self._records.append(record)
|
||||||
|
self._condition.notify_all()
|
||||||
|
return record
|
||||||
|
|
||||||
|
def records(self) -> list[dict[str, Any]]:
|
||||||
|
with self._condition:
|
||||||
|
return [dict(record) for record in self._records]
|
||||||
|
|
||||||
|
def _run(self) -> None:
|
||||||
|
while True:
|
||||||
|
with self._condition:
|
||||||
|
while not self._tasks and not self._closing:
|
||||||
|
self._condition.wait()
|
||||||
|
if not self._tasks and self._closing:
|
||||||
|
return
|
||||||
|
due_monotonic, _seq, record = self._tasks[0]
|
||||||
|
wait_seconds = due_monotonic - time.monotonic()
|
||||||
|
if wait_seconds > 0:
|
||||||
|
self._condition.wait(wait_seconds)
|
||||||
|
continue
|
||||||
|
heapq.heappop(self._tasks)
|
||||||
|
self._write_snapshot(record)
|
||||||
|
|
||||||
|
def _write_snapshot(self, record: dict[str, Any]) -> None:
|
||||||
|
if self.cv2 is None or self.device is None:
|
||||||
|
record["status"] = "error"
|
||||||
|
record["error"] = "camera closed before capture"
|
||||||
|
return
|
||||||
|
|
||||||
|
image = None
|
||||||
|
ok = False
|
||||||
|
for _attempt in range(3):
|
||||||
|
ok, image = self.device.read()
|
||||||
|
if ok:
|
||||||
|
break
|
||||||
|
time.sleep(0.020)
|
||||||
|
if not ok or image is None:
|
||||||
|
record["status"] = "error"
|
||||||
|
record["error"] = f"webcam index {self.camera_index} did not return an image"
|
||||||
|
return
|
||||||
|
|
||||||
|
path = Path(str(record["path"]))
|
||||||
|
if not self.cv2.imwrite(str(path), image):
|
||||||
|
record["status"] = "error"
|
||||||
|
record["error"] = f"failed to write webcam snapshot {path}"
|
||||||
|
return
|
||||||
|
record["status"] = "written"
|
||||||
|
record["captured_timestamp"] = datetime.now().strftime("%Y%m%d-%H%M%S-%f")[:-3]
|
||||||
|
|
||||||
|
def _warm_up(self) -> None:
|
||||||
|
if self.device is None:
|
||||||
|
return
|
||||||
|
deadline = time.monotonic() + self.warmup_seconds
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
self.device.read()
|
||||||
|
time.sleep(0.020)
|
||||||
|
self.device.read()
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_name(text: str) -> str:
|
||||||
|
cleaned = _SAFE_NAME_RE.sub("_", text.strip()).strip("._")
|
||||||
|
return cleaned or "snapshot.jpg"
|
||||||
@@ -9,6 +9,7 @@ from typing import Any
|
|||||||
|
|
||||||
from .formatting import h16
|
from .formatting import h16
|
||||||
from .lcd_text import analyze_lcd_text
|
from .lcd_text import analyze_lcd_text
|
||||||
|
from .panel_selectors import panel_selector_semantics_payload
|
||||||
from .rom import Rom
|
from .rom import Rom
|
||||||
from .serial_semantics import OBSERVED_TX_REPORT_OVERLAY
|
from .serial_semantics import OBSERVED_TX_REPORT_OVERLAY
|
||||||
from .table_xrefs import analyze_table_xrefs
|
from .table_xrefs import analyze_table_xrefs
|
||||||
@@ -125,6 +126,7 @@ def analyze_ccu_seed_hints(payload: Mapping[str, Any], *, rom_path: Path | None
|
|||||||
selector_hints = _selector_hints_from_tables(table_analysis)
|
selector_hints = _selector_hints_from_tables(table_analysis)
|
||||||
_merge_special_selectors(selector_hints)
|
_merge_special_selectors(selector_hints)
|
||||||
_merge_observed_reports(selector_hints)
|
_merge_observed_reports(selector_hints)
|
||||||
|
_merge_panel_selector_semantics(selector_hints)
|
||||||
|
|
||||||
dispatch = _dispatch_table_summary(payload, rom_path)
|
dispatch = _dispatch_table_summary(payload, rom_path)
|
||||||
for entry in dispatch.get("interesting_entries", []):
|
for entry in dispatch.get("interesting_entries", []):
|
||||||
@@ -362,6 +364,30 @@ def _merge_observed_reports(hints: dict[int, JsonObject]) -> None:
|
|||||||
hint["reasons"].append(f"observed RCP autonomous report frame(s): {frames}")
|
hint["reasons"].append(f"observed RCP autonomous report frame(s): {frames}")
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_panel_selector_semantics(hints: dict[int, JsonObject]) -> None:
|
||||||
|
for item in panel_selector_semantics_payload():
|
||||||
|
selector = int(item["selector"])
|
||||||
|
hint = hints.setdefault(selector, _new_selector_hint(selector))
|
||||||
|
hint["score"] += 4
|
||||||
|
hint["name"] = str(item.get("name") or hint["name"])
|
||||||
|
summary = str(item.get("summary") or "").strip()
|
||||||
|
if summary:
|
||||||
|
hint["reasons"].append(summary)
|
||||||
|
for effect in item.get("effects", []):
|
||||||
|
if not isinstance(effect, Mapping):
|
||||||
|
continue
|
||||||
|
name = effect.get("name") or "panel effect"
|
||||||
|
mask = effect.get("mask_hex") or "mask?"
|
||||||
|
when_set = effect.get("when_set") or "set"
|
||||||
|
hint["reasons"].append(f"{mask} {name}: {when_set}")
|
||||||
|
for meaning in item.get("value_meanings", []):
|
||||||
|
if not isinstance(meaning, Mapping):
|
||||||
|
continue
|
||||||
|
value = meaning.get("value")
|
||||||
|
if isinstance(value, int):
|
||||||
|
_add_seed_value(hint, value)
|
||||||
|
|
||||||
|
|
||||||
def _seed_plan(hints: Mapping[int, JsonObject]) -> JsonObject:
|
def _seed_plan(hints: Mapping[int, JsonObject]) -> JsonObject:
|
||||||
planned = [
|
planned = [
|
||||||
(0x000, 0x8080, "selector zero active/connect candidate from emulator state search"),
|
(0x000, 0x8080, "selector zero active/connect candidate from emulator state search"),
|
||||||
@@ -595,3 +621,7 @@ __all__ = [
|
|||||||
"selector_bytes",
|
"selector_bytes",
|
||||||
"write_ccu_seed_hints",
|
"write_ccu_seed_hints",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ from .cpu import CPUState
|
|||||||
from .errors import EmulatorError, UnsupportedInstruction
|
from .errors import EmulatorError, UnsupportedInstruction
|
||||||
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
|
from .fast_paths import P9FastPath, P9FastPathConfig, P9FastPathEvent
|
||||||
from .memory import MemoryAccess, MemoryMap, describe_regions
|
from .memory import MemoryAccess, MemoryMap, describe_regions
|
||||||
|
from .panel import PanelAction, PanelInjection, PanelInput, parse_panel_action, resolve_panel_input
|
||||||
from .peripherals import LCD, P9TraceEvent, X24164Bus, X24164Device, X24164TraceEvent, factory_default_words_from_rom
|
from .peripherals import LCD, P9TraceEvent, X24164Bus, X24164Device, X24164TraceEvent, factory_default_words_from_rom
|
||||||
from .runner import H8536Emulator, RunReport
|
from .runner import H8536Emulator, RunReport
|
||||||
from .sci import SCI1, SciTxEvent
|
from .sci import SCI1, SciTxEvent
|
||||||
@@ -97,6 +98,9 @@ __all__ = [
|
|||||||
"P9FastPath",
|
"P9FastPath",
|
||||||
"P9FastPathConfig",
|
"P9FastPathConfig",
|
||||||
"P9FastPathEvent",
|
"P9FastPathEvent",
|
||||||
|
"PanelAction",
|
||||||
|
"PanelInjection",
|
||||||
|
"PanelInput",
|
||||||
"P9TraceEvent",
|
"P9TraceEvent",
|
||||||
"RAMCR",
|
"RAMCR",
|
||||||
"REGISTER_FIELD_END",
|
"REGISTER_FIELD_END",
|
||||||
@@ -137,4 +141,6 @@ __all__ = [
|
|||||||
"factory_default_words_from_rom",
|
"factory_default_words_from_rom",
|
||||||
"load_rom",
|
"load_rom",
|
||||||
"main",
|
"main",
|
||||||
|
"parse_panel_action",
|
||||||
|
"resolve_panel_input",
|
||||||
]
|
]
|
||||||
|
|||||||
203
h8536/emulator/panel.py
Normal file
203
h8536/emulator/panel.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from ..formatting import h16
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PanelInput:
|
||||||
|
name: str
|
||||||
|
source: int
|
||||||
|
shadow: int
|
||||||
|
previous: int
|
||||||
|
bit: int
|
||||||
|
dirty: int
|
||||||
|
dirty_bit: int
|
||||||
|
selector: int | None = None
|
||||||
|
note: str = ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def spec(self) -> str:
|
||||||
|
return f"{h16(self.shadow)}.{self.bit}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dirty_spec(self) -> str:
|
||||||
|
return f"{h16(self.dirty)}.{self.dirty_bit}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PanelAction:
|
||||||
|
panel_input: PanelInput
|
||||||
|
pressed: bool
|
||||||
|
raw: str = ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self) -> str:
|
||||||
|
state = "press" if self.pressed else "release"
|
||||||
|
return f"{self.panel_input.name}:{state}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PanelInjection:
|
||||||
|
action: PanelAction
|
||||||
|
source_before: int
|
||||||
|
source_after: int
|
||||||
|
shadow_before: int
|
||||||
|
shadow_after: int
|
||||||
|
previous_before: int
|
||||||
|
previous_after: int
|
||||||
|
dirty_before: int
|
||||||
|
dirty_after: int
|
||||||
|
|
||||||
|
def summary(self) -> str:
|
||||||
|
panel_input = self.action.panel_input
|
||||||
|
selector = "" if panel_input.selector is None else f" selector=0x{panel_input.selector:04X}"
|
||||||
|
return (
|
||||||
|
f"{self.action.label} source={h16(panel_input.source)} "
|
||||||
|
f"shadow={panel_input.spec} previous={h16(panel_input.previous)} "
|
||||||
|
f"dirty={panel_input.dirty_spec}{selector}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
PANEL_LANES: tuple[tuple[int, int, int, int, int], ...] = (
|
||||||
|
(0xF102, 0xF6D7, 0xF6E7, 0xF6F2, 7),
|
||||||
|
(0xF103, 0xF6D6, 0xF6E6, 0xF6F2, 6),
|
||||||
|
(0xF104, 0xF6D5, 0xF6E5, 0xF6F2, 5),
|
||||||
|
(0xF105, 0xF6D4, 0xF6E4, 0xF6F2, 4),
|
||||||
|
(0xF106, 0xF6D3, 0xF6E3, 0xF6F2, 3),
|
||||||
|
(0xF107, 0xF6D2, 0xF6E2, 0xF6F2, 2),
|
||||||
|
(0xF108, 0xF6D1, 0xF6E1, 0xF6F2, 1),
|
||||||
|
(0xF109, 0xF6D0, 0xF6E0, 0xF6F2, 0),
|
||||||
|
(0xF005, 0xF6DC, 0xF6EC, 0xF6F3, 4),
|
||||||
|
(0xF006, 0xF6DB, 0xF6EB, 0xF6F3, 3),
|
||||||
|
)
|
||||||
|
|
||||||
|
KNOWN_PANEL_INPUTS: dict[str, PanelInput] = {
|
||||||
|
"cam-power": PanelInput(
|
||||||
|
name="cam-power",
|
||||||
|
source=0xF105,
|
||||||
|
shadow=0xF6D4,
|
||||||
|
previous=0xF6E4,
|
||||||
|
bit=3,
|
||||||
|
dirty=0xF6F2,
|
||||||
|
dirty_bit=4,
|
||||||
|
selector=0x0007,
|
||||||
|
note="CAM POWER button; queues selector 0x0007 when gates allow",
|
||||||
|
),
|
||||||
|
"call": PanelInput(
|
||||||
|
name="call",
|
||||||
|
source=0xF006,
|
||||||
|
shadow=0xF6DB,
|
||||||
|
previous=0xF6EB,
|
||||||
|
bit=5,
|
||||||
|
dirty=0xF6F3,
|
||||||
|
dirty_bit=3,
|
||||||
|
selector=0x0015,
|
||||||
|
note="CALL button; queues selector 0x0015 active/inactive reports",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
PANEL_ALIASES: dict[str, str] = {
|
||||||
|
"cam": "cam-power",
|
||||||
|
"camera-power": "cam-power",
|
||||||
|
"camera_power": "cam-power",
|
||||||
|
"cam_power": "cam-power",
|
||||||
|
"campower": "cam-power",
|
||||||
|
"power": "cam-power",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_panel_input(text: str) -> PanelInput:
|
||||||
|
token = _normalize_token(text)
|
||||||
|
token = PANEL_ALIASES.get(token, token)
|
||||||
|
if token in KNOWN_PANEL_INPUTS:
|
||||||
|
return KNOWN_PANEL_INPUTS[token]
|
||||||
|
if "." not in token:
|
||||||
|
raise ValueError(f"unknown panel input {text!r}; use cam-power, call, or an address bit like F6D4.3")
|
||||||
|
address_text, bit_text = token.split(".", 1)
|
||||||
|
address = _parse_address(address_text)
|
||||||
|
try:
|
||||||
|
bit = int(bit_text, 0)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(f"invalid panel bit in {text!r}") from exc
|
||||||
|
if not 0 <= bit <= 7:
|
||||||
|
raise ValueError(f"panel bit out of range in {text!r}")
|
||||||
|
for source, shadow, previous, dirty, dirty_bit in PANEL_LANES:
|
||||||
|
if address in {source, shadow}:
|
||||||
|
return PanelInput(
|
||||||
|
name=f"{h16(shadow)}.{bit}",
|
||||||
|
source=source,
|
||||||
|
shadow=shadow,
|
||||||
|
previous=previous,
|
||||||
|
bit=bit,
|
||||||
|
dirty=dirty,
|
||||||
|
dirty_bit=dirty_bit,
|
||||||
|
note="raw panel matrix input inferred from ROM shadow/dirty lane",
|
||||||
|
)
|
||||||
|
raise ValueError(f"{h16(address)} is not a known A8 panel byte shadow/source")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_panel_action(text: str, *, default_pressed: bool = True) -> PanelAction:
|
||||||
|
raw = text.strip()
|
||||||
|
spec = raw
|
||||||
|
pressed = default_pressed
|
||||||
|
for separator in ("=", ":"):
|
||||||
|
if separator not in raw:
|
||||||
|
continue
|
||||||
|
left, right = raw.rsplit(separator, 1)
|
||||||
|
state = _parse_state(right)
|
||||||
|
if state is None:
|
||||||
|
continue
|
||||||
|
spec = left
|
||||||
|
pressed = state
|
||||||
|
break
|
||||||
|
return PanelAction(resolve_panel_input(spec), pressed=pressed, raw=raw)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_state(text: str) -> bool | None:
|
||||||
|
token = _normalize_token(text)
|
||||||
|
if token in {"press", "pressed", "on", "down", "1", "true", "active"}:
|
||||||
|
return True
|
||||||
|
if token in {"release", "released", "off", "up", "0", "false", "inactive"}:
|
||||||
|
return False
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_token(text: str) -> str:
|
||||||
|
return text.strip().lower().replace("_", "-")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_address(text: str) -> int:
|
||||||
|
token = text.strip().upper()
|
||||||
|
if token.startswith("H'"):
|
||||||
|
token = token[2:]
|
||||||
|
elif token.startswith("$"):
|
||||||
|
token = token[1:]
|
||||||
|
elif token.startswith("0X"):
|
||||||
|
token = token[2:]
|
||||||
|
try:
|
||||||
|
value = int(token, 16)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(f"invalid panel address {text!r}") from exc
|
||||||
|
if not 0 <= value <= 0xFFFF:
|
||||||
|
raise ValueError(f"panel address out of range {text!r}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def set_bit(value: int, bit: int, enabled: bool) -> int:
|
||||||
|
mask = 1 << bit
|
||||||
|
return (value | mask) & 0xFF if enabled else (value & ~mask) & 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"KNOWN_PANEL_INPUTS",
|
||||||
|
"PANEL_ALIASES",
|
||||||
|
"PANEL_LANES",
|
||||||
|
"PanelAction",
|
||||||
|
"PanelInjection",
|
||||||
|
"PanelInput",
|
||||||
|
"parse_panel_action",
|
||||||
|
"resolve_panel_input",
|
||||||
|
"set_bit",
|
||||||
|
]
|
||||||
336
h8536/emulator/report_queue_probe.py
Normal file
336
h8536/emulator/report_queue_probe.py
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from ..formatting import h16, parse_int
|
||||||
|
from .cli import load_rom
|
||||||
|
from .eeprom_image import write_eeprom_snapshot
|
||||||
|
from .runner import H8536Emulator
|
||||||
|
from .rx_probe import RunContext, _run_until, _rx_ready, format_frame
|
||||||
|
from .uart import UartTiming
|
||||||
|
|
||||||
|
|
||||||
|
CHECKSUM_SEED = 0x5A
|
||||||
|
REPORT_QUEUE_START = 0xF870
|
||||||
|
REPORT_QUEUE_HEAD = 0xF9B0
|
||||||
|
REPORT_QUEUE_TAIL = 0xF9B5
|
||||||
|
TX_STAGING_START = 0xF850
|
||||||
|
TX_FRAME_START = 0xF858
|
||||||
|
TX_FRAME_LENGTH = 6
|
||||||
|
CURRENT_TABLE_START = 0xE800
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ReportQueueProbeResult:
|
||||||
|
rom_path: Path
|
||||||
|
report_word: int
|
||||||
|
payload_selector: int
|
||||||
|
payload: int
|
||||||
|
expected_frame: bytes
|
||||||
|
boot_summary: str
|
||||||
|
steps: int
|
||||||
|
stopped_reason: str
|
||||||
|
tx_frames: tuple[bytes, ...]
|
||||||
|
staging_bytes: bytes
|
||||||
|
finalized_bytes: bytes
|
||||||
|
state: dict[str, int]
|
||||||
|
eeprom_writes: tuple[str, ...]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def emitted_expected_frame(self) -> bool:
|
||||||
|
return self.expected_frame in self.tx_frames
|
||||||
|
|
||||||
|
def as_dict(self) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"kind": "h8536_report_queue_probe",
|
||||||
|
"rom_path": str(self.rom_path),
|
||||||
|
"report_word": f"0x{self.report_word:04X}",
|
||||||
|
"payload_selector": f"0x{self.payload_selector:04X}",
|
||||||
|
"payload": f"0x{self.payload:04X}",
|
||||||
|
"expected_frame": format_frame(self.expected_frame),
|
||||||
|
"emitted_expected_frame": self.emitted_expected_frame,
|
||||||
|
"boot_summary": self.boot_summary,
|
||||||
|
"steps": self.steps,
|
||||||
|
"stopped_reason": self.stopped_reason,
|
||||||
|
"tx_frames": [format_frame(frame) for frame in self.tx_frames],
|
||||||
|
"staging_bytes_f850_f855": format_frame(self.staging_bytes),
|
||||||
|
"finalized_bytes_f858_f85d": format_frame(self.finalized_bytes),
|
||||||
|
"state": {name: _format_state_value(name, value) for name, value in self.state.items()},
|
||||||
|
"eeprom_writes": list(self.eeprom_writes),
|
||||||
|
}
|
||||||
|
|
||||||
|
def lines(self) -> list[str]:
|
||||||
|
lines = [
|
||||||
|
f"rom={self.rom_path}",
|
||||||
|
self.boot_summary,
|
||||||
|
f"report_word={h16(self.report_word)} payload_selector={h16(self.payload_selector)} payload={h16(self.payload)}",
|
||||||
|
f"expected_frame={format_frame(self.expected_frame)}",
|
||||||
|
f"emitted_expected_frame={int(self.emitted_expected_frame)}",
|
||||||
|
f"stopped={self.stopped_reason} steps={self.steps}",
|
||||||
|
"tx_frames=" + (" | ".join(format_frame(frame) for frame in self.tx_frames) or "none"),
|
||||||
|
f"staging_F850_F855={format_frame(self.staging_bytes)}",
|
||||||
|
f"finalized_F858_F85D={format_frame(self.finalized_bytes)}",
|
||||||
|
"state:",
|
||||||
|
]
|
||||||
|
for name, value in self.state.items():
|
||||||
|
width = 4 if name in {"queue_word", "current_table_word"} else 2
|
||||||
|
lines.append(f" {name}=0x{value:0{width}X}")
|
||||||
|
if self.eeprom_writes:
|
||||||
|
lines.append("eeprom_writes:")
|
||||||
|
lines.extend(f" {line}" for line in self.eeprom_writes)
|
||||||
|
else:
|
||||||
|
lines.append("eeprom_writes=none")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def build_expected_report_frame(report_word: int, payload: int) -> bytes:
|
||||||
|
first, second, third = encode_report_header(report_word)
|
||||||
|
frame = bytes(
|
||||||
|
[
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
third,
|
||||||
|
(payload >> 8) & 0xFF,
|
||||||
|
payload & 0xFF,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return frame + bytes([frame_checksum(frame)])
|
||||||
|
|
||||||
|
|
||||||
|
def encode_report_header(report_word: int) -> tuple[int, int, int]:
|
||||||
|
raw = report_word & 0xFFFF
|
||||||
|
encoded_selector = _loc_6206_selector_encode(raw)
|
||||||
|
|
||||||
|
r1 = _swap_bytes(raw)
|
||||||
|
r1 = (r1 & 0xFF00) | ((r1 & 0xFF) >> 1)
|
||||||
|
r2 = r1 & 0xFF
|
||||||
|
|
||||||
|
first = r1 & 0x07
|
||||||
|
third = encoded_selector & 0xFF
|
||||||
|
swapped_encoded = _swap_bytes(encoded_selector)
|
||||||
|
second = (swapped_encoded & 0xFF) | (r2 & 0x78)
|
||||||
|
return first & 0xFF, second & 0xFF, third & 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
def report_payload_selector(report_word: int) -> int:
|
||||||
|
return report_word & 0x01FF
|
||||||
|
|
||||||
|
|
||||||
|
def frame_checksum(frame_without_checksum: bytes) -> int:
|
||||||
|
checksum = CHECKSUM_SEED
|
||||||
|
for value in frame_without_checksum[: TX_FRAME_LENGTH - 1]:
|
||||||
|
checksum ^= value
|
||||||
|
return checksum & 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
def run_report_queue_probe(
|
||||||
|
*,
|
||||||
|
report_word: int = 0x0204,
|
||||||
|
payload: int = 0x0000,
|
||||||
|
queue_index: int = 0,
|
||||||
|
rom_path: Path | None = None,
|
||||||
|
boot_steps: int = 250_000,
|
||||||
|
max_steps: int = 200_000,
|
||||||
|
eeprom_seed: str = "blank",
|
||||||
|
eeprom_image: bytes | None = None,
|
||||||
|
tx_wire_timing: bool = False,
|
||||||
|
uart_baud: int = 38_400,
|
||||||
|
uart_format: str = "8E1",
|
||||||
|
p9_fast_path: bool = True,
|
||||||
|
p9_fast_input: int = 0xFF,
|
||||||
|
p7_input: int = 0xFF,
|
||||||
|
) -> tuple[H8536Emulator, ReportQueueProbeResult]:
|
||||||
|
rom_bytes, discovered_rom_path = load_rom(rom_path)
|
||||||
|
emulator = H8536Emulator(
|
||||||
|
rom_bytes,
|
||||||
|
p9_fast_path_enabled=p9_fast_path,
|
||||||
|
p9_fast_default_input_byte=p9_fast_input,
|
||||||
|
p7_input=p7_input,
|
||||||
|
eeprom_seed=eeprom_seed,
|
||||||
|
sci1_tx_timing=UartTiming.from_format(uart_format, baud=uart_baud) if tx_wire_timing else None,
|
||||||
|
)
|
||||||
|
if eeprom_image is not None:
|
||||||
|
emulator.memory.load_eeprom_image(eeprom_image)
|
||||||
|
|
||||||
|
boot_context = RunContext()
|
||||||
|
boot_used, boot_reason = _run_until(emulator, boot_steps, _rx_ready, boot_context)
|
||||||
|
boot_summary = (
|
||||||
|
f"boot={boot_reason} steps={boot_used} pc={h16(emulator.cpu.pc)} "
|
||||||
|
f"rx_serviceable={int(_rx_ready(emulator))} lcd_display={emulator.memory.lcd.display_text(lines=4)!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
payload_selector = report_payload_selector(report_word)
|
||||||
|
expected_frame = build_expected_report_frame(report_word, payload)
|
||||||
|
_seed_report_queue(emulator, report_word=report_word, payload=payload, queue_index=queue_index)
|
||||||
|
emulator.memory.clear_eeprom_write_log()
|
||||||
|
|
||||||
|
start_frame_count = len(emulator.sci1.tx_frames)
|
||||||
|
|
||||||
|
def emitted_expected(inner: H8536Emulator) -> bool:
|
||||||
|
return expected_frame in inner.sci1.tx_frames[start_frame_count:]
|
||||||
|
|
||||||
|
run_context = RunContext()
|
||||||
|
steps, reason = _run_until(emulator, max_steps, emitted_expected, run_context)
|
||||||
|
stopped_reason = "expected_frame" if reason == "predicate" else reason
|
||||||
|
|
||||||
|
state = _state_snapshot(emulator, queue_index=queue_index, payload_selector=payload_selector)
|
||||||
|
staging = bytes(emulator.memory.read8(address) for address in range(TX_STAGING_START, TX_STAGING_START + TX_FRAME_LENGTH))
|
||||||
|
finalized = bytes(emulator.memory.read8(address) for address in range(TX_FRAME_START, TX_FRAME_START + TX_FRAME_LENGTH))
|
||||||
|
result = ReportQueueProbeResult(
|
||||||
|
rom_path=discovered_rom_path,
|
||||||
|
report_word=report_word & 0xFFFF,
|
||||||
|
payload_selector=payload_selector,
|
||||||
|
payload=payload & 0xFFFF,
|
||||||
|
expected_frame=expected_frame,
|
||||||
|
boot_summary=boot_summary,
|
||||||
|
steps=steps,
|
||||||
|
stopped_reason=stopped_reason,
|
||||||
|
tx_frames=tuple(emulator.sci1.tx_frames[start_frame_count:]),
|
||||||
|
staging_bytes=staging,
|
||||||
|
finalized_bytes=finalized,
|
||||||
|
state=state,
|
||||||
|
eeprom_writes=tuple(emulator.memory.p9_bus.x24164_bus.write_log_lines(limit=80)),
|
||||||
|
)
|
||||||
|
return emulator, result
|
||||||
|
|
||||||
|
|
||||||
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Seed the ROM report queue and let the H8/536 ROM build the matching SCI1 TX frame."
|
||||||
|
)
|
||||||
|
parser.add_argument("--rom", type=Path, help="ROM image path; defaults to ROM/M27C512@DIP28_1.BIN")
|
||||||
|
parser.add_argument("--report-word", type=parse_int, default=0x0204, help="word to place in F870 report queue")
|
||||||
|
parser.add_argument("--payload", type=parse_int, default=0x0000, help="word to place in E800[current selector]")
|
||||||
|
parser.add_argument("--queue-index", type=parse_int, default=0, help="F870 queue slot to seed")
|
||||||
|
parser.add_argument("--boot-steps", type=int, default=250_000, help="maximum boot steps before queue seeding")
|
||||||
|
parser.add_argument("--max-steps", type=int, default=200_000, help="maximum steps after queue seeding")
|
||||||
|
parser.add_argument("--tx-wire-timing", action="store_true", help="model UART TX character time between TXI bytes")
|
||||||
|
parser.add_argument("--uart-baud", type=parse_int, default=38_400, help="baud rate used with --tx-wire-timing")
|
||||||
|
parser.add_argument("--uart-format", default="8E1", help="UART format used with --tx-wire-timing")
|
||||||
|
parser.add_argument("--no-p9-fast-path", action="store_true", help="disable shortcut handling for known P9 routines")
|
||||||
|
parser.add_argument("--p9-fast-input", type=parse_int, default=0xFF, help="default byte returned by P9 fast-path reads")
|
||||||
|
parser.add_argument("--p7-input", type=parse_int, default=0xFF, help="external P7 pin state; DIP-off default is 0xFF")
|
||||||
|
parser.add_argument("--eeprom-seed", choices=("blank", "factory"), default="blank", help="initial X24164/shadow state")
|
||||||
|
parser.add_argument("--eeprom-load", type=Path, help="load a 0x1000-byte logical EEPROM image before booting")
|
||||||
|
parser.add_argument("--eeprom-save", type=Path, help="save the final EEPROM image")
|
||||||
|
parser.add_argument("--eeprom-report", type=Path, help="write a readable EEPROM snapshot")
|
||||||
|
parser.add_argument("--eeprom-report-json", type=Path, help="write a structured EEPROM snapshot")
|
||||||
|
parser.add_argument("--eeprom-report-include-image", action="store_true", help="include full EEPROM image in JSON")
|
||||||
|
parser.add_argument("--json", action="store_true", help="print JSON instead of text")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
args = build_arg_parser().parse_args(argv)
|
||||||
|
emulator, result = run_report_queue_probe(
|
||||||
|
report_word=args.report_word,
|
||||||
|
payload=args.payload,
|
||||||
|
queue_index=args.queue_index,
|
||||||
|
rom_path=args.rom,
|
||||||
|
boot_steps=args.boot_steps,
|
||||||
|
max_steps=args.max_steps,
|
||||||
|
eeprom_seed=args.eeprom_seed,
|
||||||
|
eeprom_image=args.eeprom_load.read_bytes() if args.eeprom_load else None,
|
||||||
|
tx_wire_timing=args.tx_wire_timing,
|
||||||
|
uart_baud=args.uart_baud,
|
||||||
|
uart_format=args.uart_format,
|
||||||
|
p9_fast_path=not args.no_p9_fast_path,
|
||||||
|
p9_fast_input=args.p9_fast_input,
|
||||||
|
p7_input=args.p7_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(result.as_dict(), indent=2, sort_keys=True))
|
||||||
|
else:
|
||||||
|
for line in result.lines():
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
if args.eeprom_save:
|
||||||
|
args.eeprom_save.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.eeprom_save.write_bytes(emulator.memory.dump_eeprom_image())
|
||||||
|
print(f"eeprom_saved={args.eeprom_save}")
|
||||||
|
if args.eeprom_report:
|
||||||
|
write_eeprom_snapshot(emulator.memory, args.eeprom_report, rom_bytes=emulator.memory.rom.data)
|
||||||
|
print(f"eeprom_report={args.eeprom_report}")
|
||||||
|
if args.eeprom_report_json:
|
||||||
|
write_eeprom_snapshot(
|
||||||
|
emulator.memory,
|
||||||
|
args.eeprom_report_json,
|
||||||
|
rom_bytes=emulator.memory.rom.data,
|
||||||
|
as_json=True,
|
||||||
|
include_image_hex=args.eeprom_report_include_image,
|
||||||
|
)
|
||||||
|
print(f"eeprom_report_json={args.eeprom_report_json}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_report_queue(
|
||||||
|
emulator: H8536Emulator,
|
||||||
|
*,
|
||||||
|
report_word: int,
|
||||||
|
payload: int,
|
||||||
|
queue_index: int,
|
||||||
|
) -> None:
|
||||||
|
queue_index &= 0x7F
|
||||||
|
queue_address = REPORT_QUEUE_START + (queue_index * 2)
|
||||||
|
payload_address = CURRENT_TABLE_START + (report_payload_selector(report_word) * 2)
|
||||||
|
memory = emulator.memory
|
||||||
|
memory.write16(queue_address, report_word)
|
||||||
|
memory.write16(payload_address, payload)
|
||||||
|
memory.write8(REPORT_QUEUE_TAIL, queue_index)
|
||||||
|
memory.write8(REPORT_QUEUE_HEAD, (queue_index + 1) & 0x7F)
|
||||||
|
memory.write8(0xF9C0, 0x00)
|
||||||
|
memory.write8(0xF9C3, 0x00)
|
||||||
|
memory.write8(0xFAA2, 0x00)
|
||||||
|
memory.write8(0xFAA3, 0x00)
|
||||||
|
|
||||||
|
|
||||||
|
def _state_snapshot(emulator: H8536Emulator, *, queue_index: int, payload_selector: int) -> dict[str, int]:
|
||||||
|
memory = emulator.memory
|
||||||
|
queue_address = REPORT_QUEUE_START + ((queue_index & 0x7F) * 2)
|
||||||
|
payload_address = CURRENT_TABLE_START + ((payload_selector & 0x01FF) * 2)
|
||||||
|
return {
|
||||||
|
"queue_head_F9B0": memory.read8(REPORT_QUEUE_HEAD),
|
||||||
|
"queue_tail_F9B5": memory.read8(REPORT_QUEUE_TAIL),
|
||||||
|
"queue_word": memory.read16(queue_address),
|
||||||
|
"current_table_word": memory.read16(payload_address),
|
||||||
|
"tx_gate_F9C0": memory.read8(0xF9C0),
|
||||||
|
"rx_index_F9C3": memory.read8(0xF9C3),
|
||||||
|
"heartbeat_gate_F9C4": memory.read8(0xF9C4),
|
||||||
|
"session_flags_FAA2": memory.read8(0xFAA2),
|
||||||
|
"pending_mask_FAA3": memory.read8(0xFAA3),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _loc_6206_selector_encode(value: int) -> int:
|
||||||
|
value &= 0x01FF
|
||||||
|
if value <= 0x007F:
|
||||||
|
return value
|
||||||
|
if value <= 0x017F:
|
||||||
|
return ((value - 0x0080) + 0x0100) & 0xFFFF
|
||||||
|
return ((value - 0x0180) + 0x0200) & 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def _swap_bytes(value: int) -> int:
|
||||||
|
value &= 0xFFFF
|
||||||
|
return ((value & 0x00FF) << 8) | ((value >> 8) & 0x00FF)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_state_value(name: str, value: int) -> str:
|
||||||
|
width = 4 if name in {"queue_word", "current_table_word"} else 2
|
||||||
|
return f"0x{value:0{width}X}"
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ReportQueueProbeResult",
|
||||||
|
"build_expected_report_frame",
|
||||||
|
"encode_report_header",
|
||||||
|
"frame_checksum",
|
||||||
|
"main",
|
||||||
|
"report_payload_selector",
|
||||||
|
"run_report_queue_probe",
|
||||||
|
]
|
||||||
@@ -39,6 +39,7 @@ from .cpu import CPUState, mask, s8, s16, sign_bit
|
|||||||
from .errors import EmulatorError, UnsupportedInstruction
|
from .errors import EmulatorError, UnsupportedInstruction
|
||||||
from .fast_paths import P9FastPath, P9FastPathConfig
|
from .fast_paths import P9FastPath, P9FastPathConfig
|
||||||
from .memory import MemoryMap
|
from .memory import MemoryMap
|
||||||
|
from .panel import PanelAction, PanelInjection, PanelInput, resolve_panel_input, set_bit
|
||||||
from .sci import SCI1
|
from .sci import SCI1
|
||||||
from .timers import FrtOciaScheduler, FrtRegisters
|
from .timers import FrtOciaScheduler, FrtRegisters
|
||||||
from .uart import UartTiming
|
from .uart import UartTiming
|
||||||
@@ -134,6 +135,38 @@ class H8536Emulator:
|
|||||||
def inject_sci1_rx_byte(self, value: int) -> None:
|
def inject_sci1_rx_byte(self, value: int) -> None:
|
||||||
self.memory.inject_sci1_rx_byte(value)
|
self.memory.inject_sci1_rx_byte(value)
|
||||||
|
|
||||||
|
def inject_panel_input(self, panel_input: PanelInput | str, *, pressed: bool = True) -> PanelInjection:
|
||||||
|
resolved = resolve_panel_input(panel_input) if isinstance(panel_input, str) else panel_input
|
||||||
|
action = PanelAction(resolved, pressed=pressed)
|
||||||
|
shadow_before = self.memory.read8(resolved.shadow)
|
||||||
|
source_before = self.memory.external.get(resolved.source, shadow_before)
|
||||||
|
previous_before = self.memory.read8(resolved.previous)
|
||||||
|
dirty_before = self.memory.read8(resolved.dirty)
|
||||||
|
|
||||||
|
source_after = set_bit(source_before, resolved.bit, pressed)
|
||||||
|
shadow_after = set_bit(shadow_before, resolved.bit, pressed)
|
||||||
|
previous_after = set_bit(previous_before, resolved.bit, not pressed)
|
||||||
|
dirty_after = dirty_before | (1 << resolved.dirty_bit)
|
||||||
|
|
||||||
|
self.memory.write8(resolved.source, source_after)
|
||||||
|
self.memory.write8(resolved.shadow, shadow_after)
|
||||||
|
# The ROM's loc_1Bxx dispatchers act on shadow XOR previous-shadow.
|
||||||
|
# Force the previous sample opposite at this bit so the main loop sees one clean edge.
|
||||||
|
self.memory.write8(resolved.previous, previous_after)
|
||||||
|
self.memory.write8(resolved.dirty, dirty_after)
|
||||||
|
|
||||||
|
return PanelInjection(
|
||||||
|
action=action,
|
||||||
|
source_before=source_before,
|
||||||
|
source_after=source_after,
|
||||||
|
shadow_before=shadow_before,
|
||||||
|
shadow_after=shadow_after,
|
||||||
|
previous_before=previous_before,
|
||||||
|
previous_after=previous_after,
|
||||||
|
dirty_before=dirty_before,
|
||||||
|
dirty_after=dirty_after,
|
||||||
|
)
|
||||||
|
|
||||||
def step(self) -> str:
|
def step(self) -> str:
|
||||||
pc = self.cpu.pc
|
pc = self.cpu.pc
|
||||||
cycles_before = self.cpu.cycles
|
cycles_before = self.cpu.cycles
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from .constants import (
|
|||||||
from .eeprom_image import write_eeprom_snapshot
|
from .eeprom_image import write_eeprom_snapshot
|
||||||
from .errors import UnsupportedInstruction
|
from .errors import UnsupportedInstruction
|
||||||
from .memory import MemoryAccess
|
from .memory import MemoryAccess
|
||||||
|
from .panel import PanelAction, parse_panel_action, resolve_panel_input
|
||||||
from .runner import H8536Emulator
|
from .runner import H8536Emulator
|
||||||
from .uart import UartTiming
|
from .uart import UartTiming
|
||||||
|
|
||||||
@@ -33,6 +34,13 @@ CONNECT_LCD_FRAMES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
WATCH_PCS = {
|
WATCH_PCS = {
|
||||||
|
0x15E0: "main_panel_scanner",
|
||||||
|
0x1BA0: "panel_f6d4_edge_dispatch",
|
||||||
|
0x1BF8: "panel_f6db_edge_dispatch",
|
||||||
|
0x1C0E: "panel_bit_dispatch",
|
||||||
|
0x1F40: "cam_power_handler",
|
||||||
|
0x20A1: "call_handler",
|
||||||
|
0x3E54: "report_queue_enqueue",
|
||||||
0xBB57: "sci1_eri_entry",
|
0xBB57: "sci1_eri_entry",
|
||||||
0xBB67: "sci1_rxi_entry",
|
0xBB67: "sci1_rxi_entry",
|
||||||
0xBBD6: "rx_checksum_seed",
|
0xBBD6: "rx_checksum_seed",
|
||||||
@@ -56,6 +64,10 @@ WATCH_RANGES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
ACCESS_RANGES = (
|
ACCESS_RANGES = (
|
||||||
|
(0xF000, 0xF10F, "panel_external_bytes"),
|
||||||
|
(0xF6D0, 0xF6DF, "panel_shadow_bytes"),
|
||||||
|
(0xF6E0, 0xF6EF, "panel_previous_shadow_bytes"),
|
||||||
|
(0xF6F0, 0xF6F3, "panel_dirty_bytes"),
|
||||||
(0xF850, 0xF85D, "tx_staging_or_frame"),
|
(0xF850, 0xF85D, "tx_staging_or_frame"),
|
||||||
(0xF860, 0xF86D, "rx_validation_or_capture"),
|
(0xF860, 0xF86D, "rx_validation_or_capture"),
|
||||||
(0xF870, 0xF96F, "report_queue"),
|
(0xF870, 0xF96F, "report_queue"),
|
||||||
@@ -64,8 +76,12 @@ ACCESS_RANGES = (
|
|||||||
(0xFAA2, 0xFAA6, "serial_latches"),
|
(0xFAA2, 0xFAA6, "serial_latches"),
|
||||||
(0xFAF0, 0xFAFF, "lcd_line_buffer"),
|
(0xFAF0, 0xFAFF, "lcd_line_buffer"),
|
||||||
(0xE000, 0xE001, "primary_table_index_0000"),
|
(0xE000, 0xE001, "primary_table_index_0000"),
|
||||||
|
(0xE00E, 0xE00F, "primary_cam_power_index_0007"),
|
||||||
|
(0xE02A, 0xE02B, "primary_call_index_0015"),
|
||||||
(0xE400, 0xE401, "secondary_table_index_0000"),
|
(0xE400, 0xE401, "secondary_table_index_0000"),
|
||||||
(0xE800, 0xE801, "current_table_index_0000"),
|
(0xE800, 0xE801, "current_table_index_0000"),
|
||||||
|
(0xE80E, 0xE80F, "current_cam_power_index_0007"),
|
||||||
|
(0xE82A, 0xE82B, "current_call_index_0015"),
|
||||||
(0xEC00, 0xEC01, "flag_table_index_0000"),
|
(0xEC00, 0xEC01, "flag_table_index_0000"),
|
||||||
(0xE000, 0xE3FF, "primary_table_E000"),
|
(0xE000, 0xE3FF, "primary_table_E000"),
|
||||||
(0xE400, 0xE7FF, "secondary_table_E400"),
|
(0xE400, 0xE7FF, "secondary_table_E400"),
|
||||||
@@ -77,6 +93,12 @@ ACCESS_RANGES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
STATE_BYTES = {
|
STATE_BYTES = {
|
||||||
|
0xF6D4: "panel_shadow_F6D4_cam_lane",
|
||||||
|
0xF6DB: "panel_shadow_F6DB_call_lane",
|
||||||
|
0xF6E4: "panel_previous_F6E4_cam_lane",
|
||||||
|
0xF6EB: "panel_previous_F6EB_call_lane",
|
||||||
|
0xF6F2: "panel_dirty_F6F2",
|
||||||
|
0xF6F3: "panel_dirty_F6F3",
|
||||||
0xF9B0: "queue_head",
|
0xF9B0: "queue_head",
|
||||||
0xF9B5: "queue_tail",
|
0xF9B5: "queue_tail",
|
||||||
0xF9C0: "tx_gate",
|
0xF9C0: "tx_gate",
|
||||||
@@ -104,12 +126,14 @@ STATE_WORDS = {
|
|||||||
0xE000: "E000_index_0000_primary",
|
0xE000: "E000_index_0000_primary",
|
||||||
0xE004: "E000_index_0002_primary",
|
0xE004: "E000_index_0002_primary",
|
||||||
0xE008: "E000_index_0004_primary",
|
0xE008: "E000_index_0004_primary",
|
||||||
|
0xE00E: "E000_index_0007_cam_power_primary",
|
||||||
0xE024: "E000_index_0012_primary",
|
0xE024: "E000_index_0012_primary",
|
||||||
0xE026: "E000_index_0013_primary",
|
0xE026: "E000_index_0013_primary",
|
||||||
0xE02A: "E000_index_0015_primary",
|
0xE02A: "E000_index_0015_primary",
|
||||||
0xE104: "E000_index_0082_primary",
|
0xE104: "E000_index_0082_primary",
|
||||||
0xE400: "E400_index_0000_secondary",
|
0xE400: "E400_index_0000_secondary",
|
||||||
0xE800: "E800_index_0000_current",
|
0xE800: "E800_index_0000_current",
|
||||||
|
0xE80E: "E800_index_0007_cam_power_current",
|
||||||
0xE804: "E800_index_0002_current",
|
0xE804: "E800_index_0002_current",
|
||||||
0xE808: "E800_index_0004_current",
|
0xE808: "E800_index_0004_current",
|
||||||
0xE824: "E800_index_0012_current",
|
0xE824: "E800_index_0012_current",
|
||||||
@@ -189,6 +213,50 @@ class FrameResult:
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PanelActionResult:
|
||||||
|
action: PanelAction
|
||||||
|
injection_summary: str
|
||||||
|
steps: int
|
||||||
|
stopped_reason: str
|
||||||
|
new_tx_bytes: bytes
|
||||||
|
new_tx_frames: list[bytes]
|
||||||
|
state_before: dict[str, int | str]
|
||||||
|
state_after: dict[str, int | str]
|
||||||
|
accesses: list[MemoryAccess]
|
||||||
|
context: RunContext
|
||||||
|
|
||||||
|
def lines(self, index: int) -> list[str]:
|
||||||
|
lines = [
|
||||||
|
f"panel_action[{index}]={self.action.label} input={self.action.panel_input.spec}",
|
||||||
|
f" injection={self.injection_summary}",
|
||||||
|
f" stopped={self.stopped_reason} steps={self.steps}",
|
||||||
|
f" new_tx_bytes={format_frame(self.new_tx_bytes) if self.new_tx_bytes else 'none'}",
|
||||||
|
]
|
||||||
|
if self.new_tx_frames:
|
||||||
|
lines.append(" new_tx_frames=" + " | ".join(format_frame(frame) for frame in self.new_tx_frames))
|
||||||
|
else:
|
||||||
|
lines.append(" new_tx_frames=none")
|
||||||
|
lcd_display = self.state_after.get("lcd_display_ascii")
|
||||||
|
if isinstance(lcd_display, str):
|
||||||
|
lines.append(f" lcd_display={lcd_display!r}")
|
||||||
|
state_changes = _state_change_lines(self.state_before, self.state_after)
|
||||||
|
if state_changes:
|
||||||
|
lines.append(" state_changes:")
|
||||||
|
lines.extend(f" {line}" for line in state_changes)
|
||||||
|
pc_lines = _pc_hit_lines(self.context)
|
||||||
|
if pc_lines:
|
||||||
|
lines.append(" pc_hits:")
|
||||||
|
lines.extend(f" {line}" for line in pc_lines)
|
||||||
|
access_lines = _access_lines(self.accesses)
|
||||||
|
if access_lines:
|
||||||
|
lines.append(" interesting_accesses:")
|
||||||
|
lines.extend(f" {line}" for line in access_lines)
|
||||||
|
if self.context.unsupported:
|
||||||
|
lines.append(f" unsupported={self.context.unsupported}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def parse_frame(text: str) -> bytes:
|
def parse_frame(text: str) -> bytes:
|
||||||
normalized = text.strip().replace(",", " ").replace(":", " ").replace("-", " ").replace("_", " ")
|
normalized = text.strip().replace(",", " ").replace(":", " ").replace("-", " ").replace("_", " ")
|
||||||
parts = normalized.split()
|
parts = normalized.split()
|
||||||
@@ -209,6 +277,27 @@ def parse_frame(text: str) -> bytes:
|
|||||||
return bytes(values)
|
return bytes(values)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_panel_action_arg(text: str) -> PanelAction:
|
||||||
|
try:
|
||||||
|
return parse_panel_action(text)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise argparse.ArgumentTypeError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
|
def parse_panel_press_arg(text: str) -> PanelAction:
|
||||||
|
try:
|
||||||
|
return PanelAction(resolve_panel_input(text), pressed=True, raw=text)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise argparse.ArgumentTypeError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
|
def parse_panel_release_arg(text: str) -> PanelAction:
|
||||||
|
try:
|
||||||
|
return PanelAction(resolve_panel_input(text), pressed=False, raw=text)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise argparse.ArgumentTypeError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
def frame_checksum(data: bytes) -> int:
|
def frame_checksum(data: bytes) -> int:
|
||||||
checksum = CHECKSUM_SEED
|
checksum = CHECKSUM_SEED
|
||||||
for value in data[: FRAME_LENGTH - 1]:
|
for value in data[: FRAME_LENGTH - 1]:
|
||||||
@@ -318,6 +407,11 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--per-byte-steps", type=int, default=5_000, help="polite mode byte-consume limit, or UART mode step limit between byte arrivals")
|
parser.add_argument("--per-byte-steps", type=int, default=5_000, help="polite mode byte-consume limit, or UART mode step limit between byte arrivals")
|
||||||
parser.add_argument("--post-frame-steps", type=int, default=80_000, help="maximum steps after a full injected frame")
|
parser.add_argument("--post-frame-steps", type=int, default=80_000, help="maximum steps after a full injected frame")
|
||||||
parser.add_argument("--post-frame-ms", type=int, help="run this many emulated milliseconds after each injected frame")
|
parser.add_argument("--post-frame-ms", type=int, help="run this many emulated milliseconds after each injected frame")
|
||||||
|
parser.add_argument("--panel", action="append", type=parse_panel_action_arg, default=[], help="synthetic panel action, e.g. cam-power, call=release, or F6D4.3=press; preserves order across repeated --panel uses")
|
||||||
|
parser.add_argument("--panel-press", action="append", type=parse_panel_press_arg, default=[], help="synthetic panel press alias/spec, e.g. cam-power, call, or F6D4.3")
|
||||||
|
parser.add_argument("--panel-release", action="append", type=parse_panel_release_arg, default=[], help="synthetic panel release alias/spec, e.g. call or F6DB.5")
|
||||||
|
parser.add_argument("--post-panel-steps", type=int, default=120_000, help="maximum steps after each synthetic panel action")
|
||||||
|
parser.add_argument("--post-panel-ms", type=int, help="run this many emulated milliseconds after each synthetic panel action")
|
||||||
parser.add_argument("--wait-heartbeats", type=int, default=0, help="wait for this many heartbeat frames before injecting the first host frame")
|
parser.add_argument("--wait-heartbeats", type=int, default=0, help="wait for this many heartbeat frames before injecting the first host frame")
|
||||||
parser.add_argument("--wait-heartbeat-steps", type=int, default=1_500_000, help="maximum steps while waiting for pre-injection heartbeat frames")
|
parser.add_argument("--wait-heartbeat-steps", type=int, default=1_500_000, help="maximum steps while waiting for pre-injection heartbeat frames")
|
||||||
parser.add_argument("--uart-timing", action="store_true", help="inject frame bytes at real UART inter-byte timing instead of waiting for RDRF consumption")
|
parser.add_argument("--uart-timing", action="store_true", help="inject frame bytes at real UART inter-byte timing instead of waiting for RDRF consumption")
|
||||||
@@ -347,8 +441,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
frames = list(args.frames)
|
frames = list(args.frames)
|
||||||
if args.preset == "connect-lcd":
|
if args.preset == "connect-lcd":
|
||||||
frames.extend(CONNECT_LCD_FRAMES)
|
frames.extend(CONNECT_LCD_FRAMES)
|
||||||
if not frames:
|
panel_actions = [*args.panel, *args.panel_press, *args.panel_release]
|
||||||
raise SystemExit("pass at least one frame or use --preset connect-lcd")
|
if not frames and not panel_actions:
|
||||||
|
raise SystemExit("pass at least one frame, use --preset connect-lcd, or add --panel/--panel-press")
|
||||||
|
|
||||||
rom_path, emulator, boot_summary, results = run_rx_probe(
|
rom_path, emulator, boot_summary, results = run_rx_probe(
|
||||||
frames,
|
frames,
|
||||||
@@ -384,6 +479,19 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
for index, result in enumerate(results):
|
for index, result in enumerate(results):
|
||||||
for line in result.lines(index):
|
for line in result.lines(index):
|
||||||
print(line)
|
print(line)
|
||||||
|
panel_results = [
|
||||||
|
_run_panel_action(
|
||||||
|
emulator,
|
||||||
|
action,
|
||||||
|
post_panel_steps=args.post_panel_steps,
|
||||||
|
post_panel_ms=args.post_panel_ms,
|
||||||
|
stop_after_tx_frame=not args.keep_listening,
|
||||||
|
)
|
||||||
|
for action in panel_actions
|
||||||
|
]
|
||||||
|
for index, result in enumerate(panel_results):
|
||||||
|
for line in result.lines(index):
|
||||||
|
print(line)
|
||||||
print("total_tx_frames=" + " | ".join(format_frame(frame) for frame in emulator.sci1.tx_frames))
|
print("total_tx_frames=" + " | ".join(format_frame(frame) for frame in emulator.sci1.tx_frames))
|
||||||
eeprom_writes = emulator.memory.p9_bus.x24164_bus.write_log_lines(limit=80)
|
eeprom_writes = emulator.memory.p9_bus.x24164_bus.write_log_lines(limit=80)
|
||||||
if eeprom_writes:
|
if eeprom_writes:
|
||||||
@@ -484,6 +592,49 @@ def _run_frame(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_panel_action(
|
||||||
|
emulator: H8536Emulator,
|
||||||
|
action: PanelAction,
|
||||||
|
*,
|
||||||
|
post_panel_steps: int,
|
||||||
|
post_panel_ms: int | None,
|
||||||
|
stop_after_tx_frame: bool,
|
||||||
|
) -> PanelActionResult:
|
||||||
|
state_before = _state_snapshot(emulator)
|
||||||
|
log_start = len(emulator.memory.access_log)
|
||||||
|
tx_byte_start = len(emulator.sci1.tx_bytes)
|
||||||
|
tx_frame_start = len(emulator.sci1.tx_frames)
|
||||||
|
injection = emulator.inject_panel_input(action.panel_input, pressed=action.pressed)
|
||||||
|
context = RunContext()
|
||||||
|
|
||||||
|
def post_predicate(inner: H8536Emulator) -> bool:
|
||||||
|
if not stop_after_tx_frame:
|
||||||
|
return False
|
||||||
|
return any(frame != HEARTBEAT_FRAME for frame in inner.sci1.tx_frames[tx_frame_start:])
|
||||||
|
|
||||||
|
if post_panel_ms is not None:
|
||||||
|
steps, reason = _run_cycles_for_ms(emulator, post_panel_ms, context)
|
||||||
|
stopped_reason = reason
|
||||||
|
else:
|
||||||
|
steps, reason = _run_until(emulator, post_panel_steps, post_predicate, context)
|
||||||
|
stopped_reason = "non_heartbeat_tx_frame" if reason == "predicate" and stop_after_tx_frame else reason
|
||||||
|
|
||||||
|
log_end = len(emulator.memory.access_log)
|
||||||
|
state_after = _state_snapshot(emulator)
|
||||||
|
return PanelActionResult(
|
||||||
|
action=action,
|
||||||
|
injection_summary=injection.summary(),
|
||||||
|
steps=steps,
|
||||||
|
stopped_reason=stopped_reason,
|
||||||
|
new_tx_bytes=bytes(emulator.sci1.tx_bytes[tx_byte_start:]),
|
||||||
|
new_tx_frames=list(emulator.sci1.tx_frames[tx_frame_start:]),
|
||||||
|
state_before=state_before,
|
||||||
|
state_after=state_after,
|
||||||
|
accesses=emulator.memory.access_log[log_start:log_end],
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _inject_frame_uart_timed(
|
def _inject_frame_uart_timed(
|
||||||
emulator: H8536Emulator,
|
emulator: H8536Emulator,
|
||||||
frame: bytes,
|
frame: bytes,
|
||||||
@@ -672,11 +823,15 @@ def _parse_byte(text: str) -> int:
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CONNECT_LCD_FRAMES",
|
"CONNECT_LCD_FRAMES",
|
||||||
|
"PanelActionResult",
|
||||||
"format_frame",
|
"format_frame",
|
||||||
"frame_checksum",
|
"frame_checksum",
|
||||||
"frame_checksum_ok",
|
"frame_checksum_ok",
|
||||||
"main",
|
"main",
|
||||||
"parse_frame",
|
"parse_frame",
|
||||||
|
"parse_panel_action_arg",
|
||||||
|
"parse_panel_press_arg",
|
||||||
|
"parse_panel_release_arg",
|
||||||
"run_rx_probe",
|
"run_rx_probe",
|
||||||
"UartTiming",
|
"UartTiming",
|
||||||
]
|
]
|
||||||
|
|||||||
307
h8536/panel_button_trace.py
Normal file
307
h8536/panel_button_trace.py
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .decoder import H8536Decoder
|
||||||
|
from .formatting import h16
|
||||||
|
from .rom import Rom
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ROM = Path("ROM/M27C512@DIP28_1.BIN")
|
||||||
|
DEFAULT_TEXT_OUTPUT = Path("build/panel_button_trace.md")
|
||||||
|
DEFAULT_JSON_OUTPUT = Path("build/panel_button_trace.json")
|
||||||
|
|
||||||
|
BUTTON_TABLE_BASE = 0x2706
|
||||||
|
NOOP_HANDLER = 0x1C25
|
||||||
|
QUEUE_FUNCTION = 0x3E54
|
||||||
|
|
||||||
|
INPUT_BYTES = [
|
||||||
|
{
|
||||||
|
"shadow": "F6D7",
|
||||||
|
"source": "F102",
|
||||||
|
"dirty": "F6F2.7",
|
||||||
|
"dispatcher": "1B2D",
|
||||||
|
"initial_r5": 0x7E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6D6",
|
||||||
|
"source": "F103",
|
||||||
|
"dirty": "F6F2.6",
|
||||||
|
"dispatcher": "1B44",
|
||||||
|
"initial_r5": 0x6E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6D5",
|
||||||
|
"source": "F104",
|
||||||
|
"dirty": "F6F2.5",
|
||||||
|
"dispatcher": "1B5B",
|
||||||
|
"initial_r5": 0x5E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6D4",
|
||||||
|
"source": "F105",
|
||||||
|
"dirty": "F6F2.4",
|
||||||
|
"dispatcher": "1BA0",
|
||||||
|
"initial_r5": 0x4E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6D3",
|
||||||
|
"source": "F106",
|
||||||
|
"dirty": "F6F2.3",
|
||||||
|
"dispatcher": "1BB6",
|
||||||
|
"initial_r5": 0x3E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6D2",
|
||||||
|
"source": "F107",
|
||||||
|
"dirty": "F6F2.2",
|
||||||
|
"dispatcher": "1BCC",
|
||||||
|
"initial_r5": 0x2E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6D1",
|
||||||
|
"source": "F108",
|
||||||
|
"dirty": "F6F2.1",
|
||||||
|
"dispatcher": "1B72",
|
||||||
|
"initial_r5": 0x1E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6D0",
|
||||||
|
"source": "F109",
|
||||||
|
"dirty": "F6F2.0",
|
||||||
|
"dispatcher": "1B89",
|
||||||
|
"initial_r5": 0x0E,
|
||||||
|
"bank": "IRQ4 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6DC",
|
||||||
|
"source": "F005",
|
||||||
|
"dirty": "F6F3.4",
|
||||||
|
"dispatcher": "1BE2",
|
||||||
|
"initial_r5": 0xCE,
|
||||||
|
"bank": "IRQ3 A8 panel byte path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shadow": "F6DB",
|
||||||
|
"source": "F006",
|
||||||
|
"dirty": "F6F3.3",
|
||||||
|
"dispatcher": "1BF8",
|
||||||
|
"initial_r5": 0xBE,
|
||||||
|
"bank": "IRQ3 A8 panel byte path",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
KNOWN_REPORTS = {
|
||||||
|
0x0007: "CAM POWER",
|
||||||
|
0x0013: "IRIS/M.BLACK LINK",
|
||||||
|
0x0015: "CALL",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_panel_buttons(rom_bytes: bytes) -> dict[str, Any]:
|
||||||
|
rom = Rom(rom_bytes)
|
||||||
|
entries = _table_entries(rom)
|
||||||
|
handler_summaries = {
|
||||||
|
target: _summarize_handler(rom, target)
|
||||||
|
for target in sorted({entry["handler"] for entry in entries if entry["handler"] != NOOP_HANDLER})
|
||||||
|
}
|
||||||
|
for entry in entries:
|
||||||
|
entry["handler_summary"] = handler_summaries.get(entry["handler"], {})
|
||||||
|
selectors = entry["handler_summary"].get("report_selectors", [])
|
||||||
|
entry["known_report"] = ", ".join(
|
||||||
|
KNOWN_REPORTS.get(int(selector), f"0x{int(selector):04X}") for selector in selectors
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"kind": "panel_button_trace",
|
||||||
|
"button_table_base": BUTTON_TABLE_BASE,
|
||||||
|
"button_table_base_hex": h16(BUTTON_TABLE_BASE),
|
||||||
|
"noop_handler": NOOP_HANDLER,
|
||||||
|
"noop_handler_hex": h16(NOOP_HANDLER),
|
||||||
|
"entries": entries,
|
||||||
|
"known_paths": _known_paths(entries),
|
||||||
|
"handler_summaries": [
|
||||||
|
{"handler": handler, "handler_hex": h16(handler), **summary}
|
||||||
|
for handler, summary in sorted(handler_summaries.items())
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def format_markdown(analysis: dict[str, Any]) -> str:
|
||||||
|
lines = [
|
||||||
|
"# PT2 Known Button ROM Trace",
|
||||||
|
"",
|
||||||
|
"This report follows the panel button edge path from the serial-visible reports back into the ROM input scanner.",
|
||||||
|
"The key table is the indirect handler table at `H'2706`, used by `loc_1C0E` after byte-level panel input changes are detected.",
|
||||||
|
"",
|
||||||
|
"## Known Anchors",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
for path in analysis["known_paths"]:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f"### {path['name']}",
|
||||||
|
"",
|
||||||
|
f"- Emitted selector: `0x{path['selector']:04X}`",
|
||||||
|
f"- Handler: `H'{path['handler']:04X}`",
|
||||||
|
f"- Edge source: `{path['source']} -> {path['shadow']}` via `{path['dirty']}`",
|
||||||
|
f"- Trigger bit: `{path['shadow']}.{path['bit']}`",
|
||||||
|
f"- Table slot: `H'{path['table_address']:04X}` -> `H'{path['handler']:04X}`",
|
||||||
|
f"- Current-level tests: {', '.join(path['handler_summary'].get('level_tests', [])) or 'none found'}",
|
||||||
|
f"- State writes: {', '.join(path['handler_summary'].get('state_writes', [])) or 'none found'}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
"## Button Matrix Entries With Serial Reports",
|
||||||
|
"",
|
||||||
|
"| Source | Shadow bit | Dirty | Handler | Selector(s) | State writes |",
|
||||||
|
"| --- | --- | --- | --- | --- | --- |",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for entry in analysis["entries"]:
|
||||||
|
selectors = entry["handler_summary"].get("report_selectors", [])
|
||||||
|
if not selectors:
|
||||||
|
continue
|
||||||
|
selector_text = ", ".join(f"`0x{int(selector):04X}`" for selector in selectors)
|
||||||
|
writes = ", ".join(f"`{write}`" for write in entry["handler_summary"].get("state_writes", [])[:4])
|
||||||
|
lines.append(
|
||||||
|
f"| `{entry['source']}` | `{entry['shadow']}.{entry['bit']}` | `{entry['dirty']}` | "
|
||||||
|
f"`H'{entry['handler']:04X}` | {selector_text} | {writes} |"
|
||||||
|
)
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"## Practical Read",
|
||||||
|
"",
|
||||||
|
"- CALL and CAM POWER do share the general panel edge path with many other buttons.",
|
||||||
|
"- The shared path is: panel byte snapshot -> shadow byte -> dirty bit -> `loc_1C0E` jump table -> handler -> `loc_3E54` report.",
|
||||||
|
"- Other buttons diverge in their handlers: many require `F731/F730/F791` session/menu gates, mutate page state, or emit different selectors.",
|
||||||
|
"- Some table entries are `H'1C25`, an immediate `RTS`, so those physical matrix positions are intentionally ignored in this firmware context.",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Trace panel button matrix handlers and known CALL/CAM report sources.")
|
||||||
|
parser.add_argument("rom", nargs="?", type=Path, default=DEFAULT_ROM)
|
||||||
|
parser.add_argument("--out", type=Path, default=DEFAULT_TEXT_OUTPUT)
|
||||||
|
parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON_OUTPUT)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
analysis = analyze_panel_buttons(args.rom.read_bytes())
|
||||||
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.out.write_text(format_markdown(analysis), encoding="utf-8")
|
||||||
|
args.json_out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.json_out.write_text(json.dumps(analysis, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
print(f"wrote {args.out}")
|
||||||
|
print(f"wrote {args.json_out}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _table_entries(rom: Rom) -> list[dict[str, Any]]:
|
||||||
|
rows: list[dict[str, Any]] = []
|
||||||
|
for lane in INPUT_BYTES:
|
||||||
|
initial_r5 = int(lane["initial_r5"])
|
||||||
|
for bit in range(7, -1, -1):
|
||||||
|
table_offset = initial_r5 - (2 * (7 - bit))
|
||||||
|
table_address = BUTTON_TABLE_BASE + table_offset
|
||||||
|
handler = rom.u16(table_address)
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"source": lane["source"],
|
||||||
|
"shadow": lane["shadow"],
|
||||||
|
"dirty": lane["dirty"],
|
||||||
|
"dispatcher": lane["dispatcher"],
|
||||||
|
"bank": lane["bank"],
|
||||||
|
"bit": bit,
|
||||||
|
"table_offset": table_offset,
|
||||||
|
"table_address": table_address,
|
||||||
|
"table_address_hex": h16(table_address),
|
||||||
|
"handler": handler,
|
||||||
|
"handler_hex": h16(handler),
|
||||||
|
"is_noop": handler == NOOP_HANDLER,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def _known_paths(entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
paths: list[dict[str, Any]] = []
|
||||||
|
for entry in entries:
|
||||||
|
selectors = entry["handler_summary"].get("report_selectors", [])
|
||||||
|
for selector in selectors:
|
||||||
|
name = KNOWN_REPORTS.get(int(selector))
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
paths.append({"name": name, "selector": int(selector), **entry})
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_handler(rom: Rom, address: int) -> dict[str, Any]:
|
||||||
|
instructions = _decode_linear_handler(rom, address)
|
||||||
|
report_selectors: list[int] = []
|
||||||
|
last_r3: int | None = None
|
||||||
|
level_tests: list[str] = []
|
||||||
|
state_writes: list[str] = []
|
||||||
|
for ins in instructions:
|
||||||
|
text = ins.text
|
||||||
|
if match := re.search(r"MOV:[^#]+#H'([0-9A-F]{4}), R3", text):
|
||||||
|
last_r3 = int(match.group(1), 16)
|
||||||
|
if QUEUE_FUNCTION in ins.targets and last_r3 is not None:
|
||||||
|
if last_r3 not in report_selectors:
|
||||||
|
report_selectors.append(last_r3)
|
||||||
|
if match := re.search(r"BTST\.B #([0-7]), @H'(F6[0-9A-F]{2})", text):
|
||||||
|
test = f"{match.group(2)}.{match.group(1)}"
|
||||||
|
if test not in level_tests:
|
||||||
|
level_tests.append(test)
|
||||||
|
if match := re.search(r"@H'(E[89][0-9A-F]{2})", text):
|
||||||
|
write = match.group(1)
|
||||||
|
if _looks_like_write(text) and write not in state_writes:
|
||||||
|
state_writes.append(write)
|
||||||
|
return {
|
||||||
|
"report_selectors": report_selectors,
|
||||||
|
"report_selectors_hex": [f"0x{selector:04X}" for selector in report_selectors],
|
||||||
|
"level_tests": level_tests,
|
||||||
|
"state_writes": state_writes,
|
||||||
|
"instruction_count_scanned": len(instructions),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_linear_handler(rom: Rom, address: int, *, max_bytes: int = 0x90) -> list[Any]:
|
||||||
|
decoder = H8536Decoder(rom, labels={QUEUE_FUNCTION: "loc_3E54"})
|
||||||
|
instructions: list[Any] = []
|
||||||
|
pc = address
|
||||||
|
end = min(len(rom.data), address + max_bytes)
|
||||||
|
while pc < end:
|
||||||
|
ins = decoder.decode(pc)
|
||||||
|
instructions.append(ins)
|
||||||
|
pc += max(1, ins.size)
|
||||||
|
if ins.mnemonic == "RTS":
|
||||||
|
break
|
||||||
|
return instructions
|
||||||
|
|
||||||
|
|
||||||
|
def _looks_like_write(text: str) -> bool:
|
||||||
|
return text.startswith("MOV") or text.startswith("BSET") or text.startswith("BCLR") or text.startswith("CLR")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["analyze_panel_buttons", "format_markdown", "main"]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
349
h8536/panel_selectors.py
Normal file
349
h8536/panel_selectors.py
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
JsonObject = dict[str, Any]
|
||||||
|
|
||||||
|
CURRENT_TABLE_BASE = 0xE800
|
||||||
|
PRIMARY_TABLE_BASE = 0xE000
|
||||||
|
SECONDARY_TABLE_BASE = 0xE400
|
||||||
|
|
||||||
|
|
||||||
|
PANEL_SELECTOR_SEMANTICS: tuple[JsonObject, ...] = (
|
||||||
|
{
|
||||||
|
"selector": 0x0013,
|
||||||
|
"selector_hex": "0x0013",
|
||||||
|
"name": "slave_and_iris_mblack_link_lamps",
|
||||||
|
"summary": (
|
||||||
|
"Selector 0x0013 is a two-bit lamp/status word. ROM dispatch H'2E06 "
|
||||||
|
"reads current table word H'E826 and fans bit 15 and bit 14 into panel latch RAM."
|
||||||
|
),
|
||||||
|
"state_machine": {
|
||||||
|
"name_candidate": "iris_mblack_link_closed_loop_state_candidate",
|
||||||
|
"summary": (
|
||||||
|
"Bench-proven closed loop: the RCP reports local IRIS/M.BLACK LINK intent, "
|
||||||
|
"the CCU ACKs selector 0x0013, then the CCU mirrors the accepted selector "
|
||||||
|
"state back with command 0. The mirrored state controls the next toggle direction."
|
||||||
|
),
|
||||||
|
"active_report_frame": "00 00 13 40 00 09",
|
||||||
|
"clear_report_frame": "00 00 13 00 00 49",
|
||||||
|
"ack_frame": "05 00 13 00 00 4C",
|
||||||
|
"active_mirror_frame": "00 00 13 40 00 09",
|
||||||
|
"clear_mirror_frame": "00 00 13 00 00 49",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
},
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0013 * 2,
|
||||||
|
"primary_word_address_hex": "H'E026",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0013 * 2,
|
||||||
|
"current_word_address_hex": "H'E826",
|
||||||
|
"dispatch_handler": "H'2E06",
|
||||||
|
"confidence": "high",
|
||||||
|
"evidence": [
|
||||||
|
"bench: 00 00 13 80 00 C9 lights far-right SLAVE lamp",
|
||||||
|
"bench: 00 00 13 40 00 09 lights IRIS/M.BLACK LINK lamp",
|
||||||
|
"ROM: H'2E06-H'2E32 tests H'E826 bits 15/14 and sets/clears F791/F713/F716 latch bits",
|
||||||
|
],
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"bit": 15,
|
||||||
|
"mask": 0x8000,
|
||||||
|
"mask_hex": "0x8000",
|
||||||
|
"name": "SLAVE lamp",
|
||||||
|
"when_set": "sets F791.6 and F713.4",
|
||||||
|
"when_clear": "clears F791.6 and F713.4",
|
||||||
|
"ram_bits": ["F791.6", "F713.4"],
|
||||||
|
"handler_range": "H'2E06-H'2E1A",
|
||||||
|
"confidence": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bit": 14,
|
||||||
|
"mask": 0x4000,
|
||||||
|
"mask_hex": "0x4000",
|
||||||
|
"name": "IRIS/M.BLACK LINK lamp",
|
||||||
|
"when_set": "sets F791.5 and F716.7",
|
||||||
|
"when_clear": "clears F791.5 and F716.7",
|
||||||
|
"ram_bits": ["F791.5", "F716.7"],
|
||||||
|
"handler_range": "H'2E1E-H'2E32",
|
||||||
|
"confidence": "high",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"local_triggers": [
|
||||||
|
{
|
||||||
|
"kind": "panel_input_toggle",
|
||||||
|
"source": "F006.7 / F6DB.7",
|
||||||
|
"handler": "H'200E",
|
||||||
|
"name_candidate": "provisional_iris_mblack_link_button_toggle_report",
|
||||||
|
"summary": (
|
||||||
|
"When F6DB.7 is asserted and F731 <= 3, the ROM toggles current-table "
|
||||||
|
"bit 14 at H'E826 based on F791.5, then queues selector 0x0013 through loc_3E54."
|
||||||
|
),
|
||||||
|
"gate": "F731 <= 3",
|
||||||
|
"current_state_bit": "F791.5",
|
||||||
|
"active_value": 0x4000,
|
||||||
|
"active_value_hex": "0x4000",
|
||||||
|
"clear_value": 0x0000,
|
||||||
|
"clear_value_hex": "0x0000",
|
||||||
|
"writes": ["H'E826 bit14"],
|
||||||
|
"queue_call": "loc_3E54",
|
||||||
|
"confidence": "medium-high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "local_helper_set_clear",
|
||||||
|
"source": "H'1FE8/H'1FFB",
|
||||||
|
"handler": "H'1FE8-H'200D",
|
||||||
|
"summary": "Adjacent local helpers set or clear current-table bit 15 at H'E826 and queue selector 0x0013.",
|
||||||
|
"writes": ["H'E826 bit15"],
|
||||||
|
"queue_call": "loc_3E54",
|
||||||
|
"confidence": "medium",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "SLAVE lamp on", "confidence": "high"},
|
||||||
|
{"value": 0x4000, "value_hex": "0x4000", "meaning": "IRIS/M.BLACK LINK lamp on", "confidence": "high"},
|
||||||
|
{"value": 0x0000, "value_hex": "0x0000", "meaning": "SLAVE and IRIS/M.BLACK LINK latch bits clear through H'2E06", "confidence": "high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x0015,
|
||||||
|
"selector_hex": "0x0015",
|
||||||
|
"name": "call_and_red_tally_lamp_lane",
|
||||||
|
"summary": "Bench-visible CALL lamp and red tally lane; local CALL handler mirrors F6DB.5 into E800[0x0015].15.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0015 * 2,
|
||||||
|
"primary_word_address_hex": "H'E02A",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0015 * 2,
|
||||||
|
"current_word_address_hex": "H'E82A",
|
||||||
|
"dispatch_handler": "handler unknown",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"evidence": [
|
||||||
|
"bench: 00 00 15 80 00 CF lights CALL and red tally",
|
||||||
|
"ROM: H'20A1-H'20BA reads F6DB.5, writes H'E82A, and queues selector 0x0015",
|
||||||
|
],
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "CALL lamp and red tally on", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0000, "value_hex": "0x0000", "meaning": "CALL inactive/clear report", "confidence": "bench-high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x0017,
|
||||||
|
"selector_hex": "0x0017",
|
||||||
|
"name": "bars_lamp_lane",
|
||||||
|
"summary": "Bench-visible BARS lamp/latch lane; low writes do not reliably clear the visible latch.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0017 * 2,
|
||||||
|
"primary_word_address_hex": "H'E02E",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0017 * 2,
|
||||||
|
"current_word_address_hex": "H'E82E",
|
||||||
|
"dispatch_handler": "handler unknown",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"evidence": [
|
||||||
|
"bench: 00 00 17 80 00 CD lights BARS",
|
||||||
|
"bench: 00 00 17 40 00 0D also lights the BARS latch in neighbor sweep",
|
||||||
|
"ROM: H'1EDE can queue selector 0x0017 from F6D4.2",
|
||||||
|
],
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "BARS lamp on", "confidence": "bench-high"},
|
||||||
|
{"value": 0x4000, "value_hex": "0x4000", "meaning": "BARS lamp/latch on", "confidence": "bench-medium-high"},
|
||||||
|
{"value": 0x0000, "value_hex": "0x0000", "meaning": "BARS low write; visible latch may remain", "confidence": "bench-medium"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x001A,
|
||||||
|
"selector_hex": "0x001A",
|
||||||
|
"name": "monitor_selector_lamps",
|
||||||
|
"summary": "Bench-visible MONITOR selector cluster found from ROM-derived button-output sweep.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x001A * 2,
|
||||||
|
"primary_word_address_hex": "H'E034",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x001A * 2,
|
||||||
|
"current_word_address_hex": "H'E834",
|
||||||
|
"dispatch_handler": "handler unknown",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"evidence": [
|
||||||
|
"bench: 00 00 1A 08 08 40 lights MONITOR ENC",
|
||||||
|
"bench: 00 00 1A 20 20 40 lights MONITOR B",
|
||||||
|
"bench: 00 00 1A 40 40 40 lights MONITOR G",
|
||||||
|
"bench: 00 00 1A 80 80 40 lights MONITOR R",
|
||||||
|
"ROM: H'1CB2-H'1D56 writes packed values to H'E834 and queues selector 0x001A",
|
||||||
|
],
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x0808, "value_hex": "0x0808", "meaning": "MONITOR ENC lamp", "confidence": "bench-high"},
|
||||||
|
{"value": 0x2020, "value_hex": "0x2020", "meaning": "MONITOR B lamp", "confidence": "bench-high"},
|
||||||
|
{"value": 0x4040, "value_hex": "0x4040", "meaning": "MONITOR G lamp", "confidence": "bench-high"},
|
||||||
|
{"value": 0x8080, "value_hex": "0x8080", "meaning": "MONITOR R lamp", "confidence": "bench-high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x0024,
|
||||||
|
"selector_hex": "0x0024",
|
||||||
|
"name": "lcd_selector_button_lamp",
|
||||||
|
"summary": "Bench-visible LCD selector-button lamp lane.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0024 * 2,
|
||||||
|
"primary_word_address_hex": "H'E048",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0024 * 2,
|
||||||
|
"current_word_address_hex": "H'E848",
|
||||||
|
"confidence": "bench-medium",
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "LCD selector-button lamp visible", "confidence": "bench-medium"},
|
||||||
|
{"value": 0x0000, "value_hex": "0x0000", "meaning": "lamp remained visible at 0.5 s in isolation run", "confidence": "bench-low"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x006B,
|
||||||
|
"selector_hex": "0x006B",
|
||||||
|
"name": "standard_lamp_lane",
|
||||||
|
"summary": "Bench-visible STANDARD lamp lane found from ROM-derived F6D4.6 handler candidate.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x006B * 2,
|
||||||
|
"primary_word_address_hex": "H'E0D6",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x006B * 2,
|
||||||
|
"current_word_address_hex": "H'E8D6",
|
||||||
|
"dispatch_handler": "handler unknown",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"evidence": [
|
||||||
|
"bench: 00 00 6B 80 00 B1 lights STANDARD",
|
||||||
|
"ROM: H'2048 can write H'E8D6=0x8000 and queue selector 0x006B from F6D4.6",
|
||||||
|
],
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "STANDARD lamp on", "confidence": "bench-high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x0082,
|
||||||
|
"selector_hex": "0x0082",
|
||||||
|
"name": "iris_readout_lane",
|
||||||
|
"summary": "Bench-visible IRIS seven-segment/display lane.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0082 * 2,
|
||||||
|
"primary_word_address_hex": "H'E104",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0082 * 2,
|
||||||
|
"current_word_address_hex": "H'E904",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "IRIS display OP", "confidence": "bench-high"},
|
||||||
|
{"value": 0x4000, "value_hex": "0x4000", "meaning": "IRIS display 1.4", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0000, "value_hex": "0x0000", "meaning": "IRIS display blank", "confidence": "bench-high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x0083,
|
||||||
|
"selector_hex": "0x0083",
|
||||||
|
"name": "combined_iris_shutter_master_gain_status_lane",
|
||||||
|
"summary": "Bench-visible combined status/readout lane; clear behavior appears latched or copied elsewhere.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0083 * 2,
|
||||||
|
"primary_word_address_hex": "H'E106",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0083 * 2,
|
||||||
|
"current_word_address_hex": "H'E906",
|
||||||
|
"confidence": "bench-medium-high",
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "IRIS AUTO, SHUTTER OFF, MASTER GAIN -3", "confidence": "bench-medium-high"},
|
||||||
|
{"value": 0x4000, "value_hex": "0x4000", "meaning": "IRIS AUTO, SHUTTER OFF, MASTER GAIN 0", "confidence": "bench-high"},
|
||||||
|
{"value": 0x2000, "value_hex": "0x2000", "meaning": "IRIS AUTO, SHUTTER OFF, MASTER GAIN 3", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0004, "value_hex": "0x0004", "meaning": "IRIS AUTO, SHUTTER OFF, MASTER GAIN HP", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0000, "value_hex": "0x0000", "meaning": "same visible state remained at 0.5 s", "confidence": "bench-low"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x008F,
|
||||||
|
"selector_hex": "0x008F",
|
||||||
|
"name": "shutter_display_status_lane",
|
||||||
|
"summary": "Bench-visible shutter/status display lane; local F6D0.6/F6D0.7 handlers also queue this selector.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x008F * 2,
|
||||||
|
"primary_word_address_hex": "H'E11E",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x008F * 2,
|
||||||
|
"current_word_address_hex": "H'E91E",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"evidence": [
|
||||||
|
"bench: 00 01 0F 80 00 D4 shows IRIS AUTO and shutter value beginning with 1",
|
||||||
|
"bench: 00 01 0F 20 00 74 shows IRIS AUTO and shutter 00.0",
|
||||||
|
"bench: 00 01 0F 08 00 5C shows IRIS AUTO and shutter EVS",
|
||||||
|
"bench: 00 01 0F 10 00 44 shows IRIS AUTO and shutter OFF",
|
||||||
|
"ROM: H'24E8/H'252E write H'E91E and queue selector 0x008F from F6D0.7/F6D0.6",
|
||||||
|
],
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "IRIS AUTO plus shutter value beginning with 1", "confidence": "bench-medium-high"},
|
||||||
|
{"value": 0x2000, "value_hex": "0x2000", "meaning": "IRIS AUTO plus shutter 00.0", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0800, "value_hex": "0x0800", "meaning": "IRIS AUTO plus shutter EVS", "confidence": "bench-high"},
|
||||||
|
{"value": 0x1000, "value_hex": "0x1000", "meaning": "IRIS AUTO plus shutter OFF", "confidence": "bench-high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x0093,
|
||||||
|
"selector_hex": "0x0093",
|
||||||
|
"name": "white_balance_black_flare_mode_lane",
|
||||||
|
"summary": "Bench-visible white-balance and black/flare lamp lane.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0093 * 2,
|
||||||
|
"primary_word_address_hex": "H'E126",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0093 * 2,
|
||||||
|
"current_word_address_hex": "H'E926",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "BLACK/FLARE MANUAL plus white-balance PRESET", "confidence": "bench-high"},
|
||||||
|
{"value": 0x4000, "value_hex": "0x4000", "meaning": "BLACK/FLARE MANUAL plus white-balance AUTO", "confidence": "bench-high"},
|
||||||
|
{"value": 0x2000, "value_hex": "0x2000", "meaning": "BLACK/FLARE MANUAL plus white-balance MANUAL", "confidence": "bench-high"},
|
||||||
|
{"value": 0x1020, "value_hex": "0x1020", "meaning": "BLACK/FLARE MANUAL plus white-balance MANUAL", "confidence": "bench-high"},
|
||||||
|
{"value": 0x4040, "value_hex": "0x4040", "meaning": "BLACK/FLARE AUTO plus white-balance AUTO", "confidence": "bench-high"},
|
||||||
|
{"value": 0x8040, "value_hex": "0x8040", "meaning": "BLACK/FLARE AUTO plus white-balance PRESET", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0020, "value_hex": "0x0020", "meaning": "BLACK/FLARE MANUAL plus white-balance MANUAL", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0040, "value_hex": "0x0040", "meaning": "BLACK/FLARE AUTO plus white-balance MANUAL", "confidence": "bench-high"},
|
||||||
|
{"value": 0x0000, "value_hex": "0x0000", "meaning": "BLACK/FLARE MANUAL plus white-balance MANUAL", "confidence": "bench-high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": 0x0110,
|
||||||
|
"selector_hex": "0x0110",
|
||||||
|
"name": "knee_auto_lamp_or_page_status_lane",
|
||||||
|
"summary": "Bench-visible KNEE AUTO source; ROM notes indicate timed KNEE/detail page interaction.",
|
||||||
|
"primary_word_address": PRIMARY_TABLE_BASE + 0x0110 * 2,
|
||||||
|
"primary_word_address_hex": "H'E220",
|
||||||
|
"current_word_address": CURRENT_TABLE_BASE + 0x0110 * 2,
|
||||||
|
"current_word_address_hex": "H'EA20",
|
||||||
|
"confidence": "bench-high",
|
||||||
|
"evidence": [
|
||||||
|
"bench: 00 01 90 80 00 4B lights KNEE AUTO",
|
||||||
|
"ROM: KNEE notes identify E000[0x0110].15 as a strong KNEE AUTO/timed display source",
|
||||||
|
],
|
||||||
|
"value_meanings": [
|
||||||
|
{"value": 0x8000, "value_hex": "0x8000", "meaning": "KNEE AUTO lamp/status on", "confidence": "bench-high"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def panel_selector_semantics_payload() -> list[JsonObject]:
|
||||||
|
return deepcopy(list(PANEL_SELECTOR_SEMANTICS))
|
||||||
|
|
||||||
|
|
||||||
|
def known_panel_selector(selector: int) -> JsonObject | None:
|
||||||
|
normalized = selector & 0x01FF
|
||||||
|
for item in PANEL_SELECTOR_SEMANTICS:
|
||||||
|
if int(item["selector"]) == normalized:
|
||||||
|
return deepcopy(item)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def selector_word_address(table_base: int, selector: int) -> int:
|
||||||
|
return (table_base + ((selector & 0x01FF) * 2)) & 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def describe_selector_value(selector: int, value: int) -> list[str]:
|
||||||
|
item = known_panel_selector(selector)
|
||||||
|
if item is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
normalized_value = value & 0xFFFF
|
||||||
|
lines: list[str] = []
|
||||||
|
for meaning in item.get("value_meanings", []):
|
||||||
|
if not isinstance(meaning, dict) or meaning.get("value") != normalized_value:
|
||||||
|
continue
|
||||||
|
lines.append(str(meaning.get("meaning") or "known panel selector value"))
|
||||||
|
|
||||||
|
for effect in item.get("effects", []):
|
||||||
|
if not isinstance(effect, dict) or not isinstance(effect.get("mask"), int):
|
||||||
|
continue
|
||||||
|
mask = int(effect["mask"]) & 0xFFFF
|
||||||
|
state = "set" if normalized_value & mask else "clear"
|
||||||
|
action = effect.get("when_set") if state == "set" else effect.get("when_clear")
|
||||||
|
name = str(effect.get("name") or f"bit {effect.get('bit', '?')}")
|
||||||
|
if action:
|
||||||
|
lines.append(f"{name}: bit {effect.get('bit', '?')} {state}; {action}")
|
||||||
|
else:
|
||||||
|
lines.append(f"{name}: bit {effect.get('bit', '?')} {state}")
|
||||||
|
return lines
|
||||||
@@ -278,6 +278,7 @@ def _declarations(tx_candidate: JsonObject | None, rx_candidate: JsonObject | No
|
|||||||
"typedef uint8_t u8;",
|
"typedef uint8_t u8;",
|
||||||
"typedef uint16_t u16;",
|
"typedef uint16_t u16;",
|
||||||
"",
|
"",
|
||||||
|
"#define BIT(n) (1u << (n))",
|
||||||
"extern volatile u8 MEM8[0x10000];",
|
"extern volatile u8 MEM8[0x10000];",
|
||||||
"",
|
"",
|
||||||
f"#define {channel}_SCR MEM8[{_c_hex(scr)}]",
|
f"#define {channel}_SCR MEM8[{_c_hex(scr)}]",
|
||||||
@@ -424,6 +425,7 @@ def _semantics_lines(
|
|||||||
lines.extend(_command_effect_comment_lines(protocol.get("command_effects"), opts, prefix=" * "))
|
lines.extend(_command_effect_comment_lines(protocol.get("command_effects"), opts, prefix=" * "))
|
||||||
lines.extend(_response_schema_comment_lines(_schema_list(protocol), opts, prefix=" * "))
|
lines.extend(_response_schema_comment_lines(_schema_list(protocol), opts, prefix=" * "))
|
||||||
lines.extend(_table_map_comment_lines(_table_map_list(protocol), opts, prefix=" * "))
|
lines.extend(_table_map_comment_lines(_table_map_list(protocol), opts, prefix=" * "))
|
||||||
|
lines.extend(_panel_selector_comment_lines(protocol.get("panel_selector_semantics"), opts, prefix=" * "))
|
||||||
lines.extend(_state_variable_comment_lines(protocol.get("state_variable_candidates"), opts, prefix=" * "))
|
lines.extend(_state_variable_comment_lines(protocol.get("state_variable_candidates"), opts, prefix=" * "))
|
||||||
lines.extend(_retry_error_comment_lines(protocol.get("retry_error_model"), opts, prefix=" * "))
|
lines.extend(_retry_error_comment_lines(protocol.get("retry_error_model"), opts, prefix=" * "))
|
||||||
lines.extend(_gate_queue_comment_lines(protocol.get("gate_queue_model"), opts, prefix=" * "))
|
lines.extend(_gate_queue_comment_lines(protocol.get("gate_queue_model"), opts, prefix=" * "))
|
||||||
@@ -466,6 +468,8 @@ def _semantics_lines(
|
|||||||
)
|
)
|
||||||
lines.extend(_gate_queue_predicate_function_lines(protocol.get("gate_queue_model")))
|
lines.extend(_gate_queue_predicate_function_lines(protocol.get("gate_queue_model")))
|
||||||
lines.extend(_timer_architecture_function_lines(protocol))
|
lines.extend(_timer_architecture_function_lines(protocol))
|
||||||
|
lines.extend(_panel_selector_function_lines(protocol.get("panel_selector_semantics")))
|
||||||
|
lines.extend(_panel_selector_provisional_function_lines(protocol.get("panel_selector_semantics")))
|
||||||
lines.extend(
|
lines.extend(
|
||||||
[
|
[
|
||||||
"void sci1_process_candidate_protocol_command(void)",
|
"void sci1_process_candidate_protocol_command(void)",
|
||||||
@@ -474,6 +478,8 @@ def _semantics_lines(
|
|||||||
" u16 logical_index = sci1_rx_candidate_logical_index();",
|
" u16 logical_index = sci1_rx_candidate_logical_index();",
|
||||||
" u16 value = sci1_rx_candidate_value();",
|
" u16 value = sci1_rx_candidate_value();",
|
||||||
"",
|
"",
|
||||||
|
" sci1_candidate_panel_selector_annotation(logical_index, value);",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
lines.extend(_command_dispatch_switch_lines(commands, opts))
|
lines.extend(_command_dispatch_switch_lines(commands, opts))
|
||||||
@@ -644,6 +650,70 @@ def _table_map_comment_lines(
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_selector_comment_lines(
|
||||||
|
value: object,
|
||||||
|
opts: SerialPseudocodeOptions,
|
||||||
|
*,
|
||||||
|
prefix: str,
|
||||||
|
) -> list[str]:
|
||||||
|
selectors = _object_list(value)
|
||||||
|
if not selectors:
|
||||||
|
return []
|
||||||
|
lines = [f"{prefix}panel selector semantics:"]
|
||||||
|
for selector in selectors[:6]:
|
||||||
|
selector_hex = selector.get("selector_hex") or _selector_hex(selector.get("selector"))
|
||||||
|
name = selector.get("name") or "panel_selector"
|
||||||
|
current = selector.get("current_word_address_hex") or "current table"
|
||||||
|
dispatch = selector.get("dispatch_handler") or "dispatch unknown"
|
||||||
|
summary = _comment_text(str(selector.get("summary") or "bench/ROM selector annotation"))
|
||||||
|
lines.append(f"{prefix}- {selector_hex} {name}: {summary}")
|
||||||
|
lines.append(f"{prefix} current word: {current}; dispatch: {dispatch}")
|
||||||
|
for effect in _object_list(selector.get("effects"))[:4]:
|
||||||
|
mask = effect.get("mask_hex") or _selector_hex(effect.get("mask"))
|
||||||
|
effect_name = effect.get("name") or "effect"
|
||||||
|
when_set = _comment_text(str(effect.get("when_set") or "set"))
|
||||||
|
bits = ", ".join(str(item) for item in effect.get("ram_bits", []))
|
||||||
|
suffix = f"; RAM {bits}" if bits else ""
|
||||||
|
lines.append(f"{prefix} {mask} -> {effect_name}: {when_set}{suffix}")
|
||||||
|
meanings = []
|
||||||
|
for meaning in _object_list(selector.get("value_meanings"))[:4]:
|
||||||
|
value_hex = meaning.get("value_hex") or _selector_hex(meaning.get("value"))
|
||||||
|
label = _comment_text(str(meaning.get("meaning") or "known panel value"))
|
||||||
|
meanings.append(f"{value_hex} {label}")
|
||||||
|
if meanings:
|
||||||
|
lines.append(f"{prefix} observed values: {'; '.join(meanings)}")
|
||||||
|
state_machine = selector.get("state_machine")
|
||||||
|
if isinstance(state_machine, dict):
|
||||||
|
name_candidate = state_machine.get("name_candidate") or "selector_state_machine_candidate"
|
||||||
|
summary = _comment_text(str(state_machine.get("summary") or "bench-proven selector state-machine candidate"))
|
||||||
|
lines.append(f"{prefix} state machine: {name_candidate}: {summary}")
|
||||||
|
active = state_machine.get("active_report_frame")
|
||||||
|
clear = state_machine.get("clear_report_frame")
|
||||||
|
ack = state_machine.get("ack_frame")
|
||||||
|
mirror_active = state_machine.get("active_mirror_frame")
|
||||||
|
mirror_clear = state_machine.get("clear_mirror_frame")
|
||||||
|
if active or clear or ack:
|
||||||
|
lines.append(
|
||||||
|
f"{prefix} frames: active report {active or '?'}; clear report {clear or '?'}; "
|
||||||
|
f"ACK {ack or '?'}; mirror active {mirror_active or '?'}; mirror clear {mirror_clear or '?'}"
|
||||||
|
)
|
||||||
|
triggers = []
|
||||||
|
for trigger in _object_list(selector.get("local_triggers"))[:3]:
|
||||||
|
source = trigger.get("source") or trigger.get("handler") or "local path"
|
||||||
|
summary = _comment_text(str(trigger.get("summary") or "local trigger candidate"))
|
||||||
|
name_candidate = trigger.get("name_candidate")
|
||||||
|
prefix_text = f"{name_candidate} " if name_candidate else ""
|
||||||
|
triggers.append(f"{prefix_text}{source}: {summary}")
|
||||||
|
if triggers:
|
||||||
|
lines.append(f"{prefix} local trigger candidates: {'; '.join(triggers)}")
|
||||||
|
evidence = ", ".join(str(item) for item in selector.get("evidence", []) if item)
|
||||||
|
if opts.include_evidence and evidence:
|
||||||
|
lines.append(f"{prefix} evidence: {_comment_text(evidence)}")
|
||||||
|
if len(selectors) > 6:
|
||||||
|
lines.append(f"{prefix}- ... {len(selectors) - 6} more panel selector annotations")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def _state_variable_comment_lines(
|
def _state_variable_comment_lines(
|
||||||
value: object,
|
value: object,
|
||||||
opts: SerialPseudocodeOptions,
|
opts: SerialPseudocodeOptions,
|
||||||
@@ -955,6 +1025,121 @@ def _timer_architecture_function_lines(protocol: JsonObject) -> list[str]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_selector_function_lines(value: object) -> list[str]:
|
||||||
|
selectors = _object_list(value)
|
||||||
|
if not selectors:
|
||||||
|
return [
|
||||||
|
"static void sci1_candidate_panel_selector_annotation(u16 logical_index, u16 value)",
|
||||||
|
"{",
|
||||||
|
" (void)logical_index;",
|
||||||
|
" (void)value;",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"static void sci1_candidate_panel_selector_annotation(u16 logical_index, u16 value)",
|
||||||
|
"{",
|
||||||
|
" /* Known bench/ROM selector labels. This helper is commentary for the decompile. */",
|
||||||
|
" switch (logical_index) {",
|
||||||
|
]
|
||||||
|
for selector in selectors:
|
||||||
|
selector_value = selector.get("selector")
|
||||||
|
if not isinstance(selector_value, int):
|
||||||
|
continue
|
||||||
|
selector_hex = selector.get("selector_hex") or f"0x{selector_value:04X}"
|
||||||
|
name = _comment_text(str(selector.get("name") or "panel selector"))
|
||||||
|
current = selector.get("current_word_address_hex") or "current table"
|
||||||
|
dispatch = selector.get("dispatch_handler") or "dispatch unknown"
|
||||||
|
lines.append(f" case 0x{selector_value & 0x01FF:04X}u:")
|
||||||
|
lines.append(f" /* {selector_hex} {name}; current word {current}; {dispatch}. */")
|
||||||
|
for effect in _object_list(selector.get("effects")):
|
||||||
|
mask = effect.get("mask")
|
||||||
|
if not isinstance(mask, int):
|
||||||
|
continue
|
||||||
|
effect_name = _comment_text(str(effect.get("name") or "panel effect"))
|
||||||
|
when_set = _comment_text(str(effect.get("when_set") or "set"))
|
||||||
|
when_clear = _comment_text(str(effect.get("when_clear") or "clear"))
|
||||||
|
lines.append(f" if ((value & 0x{mask & 0xFFFF:04X}u) != 0u) {{")
|
||||||
|
lines.append(f" /* {effect_name}: {when_set}. */")
|
||||||
|
lines.append(" } else {")
|
||||||
|
lines.append(f" /* {effect_name}: {when_clear}. */")
|
||||||
|
lines.append(" }")
|
||||||
|
for meaning in _object_list(selector.get("value_meanings")):
|
||||||
|
known_value = meaning.get("value")
|
||||||
|
if not isinstance(known_value, int):
|
||||||
|
continue
|
||||||
|
label = _comment_text(str(meaning.get("meaning") or "known panel value"))
|
||||||
|
lines.append(f" if (value == 0x{known_value & 0xFFFF:04X}u) {{")
|
||||||
|
lines.append(f" /* {label}. */")
|
||||||
|
lines.append(" }")
|
||||||
|
lines.append(" break;")
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
" default:",
|
||||||
|
" break;",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_selector_provisional_function_lines(value: object) -> list[str]:
|
||||||
|
selectors = _object_list(value)
|
||||||
|
lines: list[str] = []
|
||||||
|
for selector in selectors:
|
||||||
|
selector_value = selector.get("selector")
|
||||||
|
if not isinstance(selector_value, int):
|
||||||
|
continue
|
||||||
|
state_machine = selector.get("state_machine")
|
||||||
|
if not isinstance(state_machine, dict):
|
||||||
|
continue
|
||||||
|
for trigger in _object_list(selector.get("local_triggers")):
|
||||||
|
name = str(trigger.get("name_candidate") or "").strip()
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
handler = _comment_text(str(trigger.get("handler") or "handler unknown"))
|
||||||
|
source = _comment_text(str(trigger.get("source") or "source unknown"))
|
||||||
|
gate = _comment_text(str(trigger.get("gate") or "gate unknown"))
|
||||||
|
current_bit = _comment_text(str(trigger.get("current_state_bit") or "current state bit unknown"))
|
||||||
|
summary = _comment_text(str(trigger.get("summary") or "local trigger candidate"))
|
||||||
|
active_value = _int_from_object(trigger.get("active_value"), 0x4000)
|
||||||
|
clear_value = _int_from_object(trigger.get("clear_value"), 0x0000)
|
||||||
|
active_report = _comment_text(str(state_machine.get("active_report_frame") or "active report unknown"))
|
||||||
|
clear_report = _comment_text(str(state_machine.get("clear_report_frame") or "clear report unknown"))
|
||||||
|
ack_frame = _comment_text(str(state_machine.get("ack_frame") or "ACK unknown"))
|
||||||
|
active_mirror = _comment_text(str(state_machine.get("active_mirror_frame") or "active mirror unknown"))
|
||||||
|
clear_mirror = _comment_text(str(state_machine.get("clear_mirror_frame") or "clear mirror unknown"))
|
||||||
|
safe_name = _safe_identifier(name)
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f"void {safe_name}(void)",
|
||||||
|
"{",
|
||||||
|
f" /* Provisional name for ROM {handler}: {summary} */",
|
||||||
|
f" /* Source {source}; gate {gate}; current state {current_bit}. */",
|
||||||
|
" if ((MEM8[0xF6DBu] & BIT(7)) == 0u) {",
|
||||||
|
" return;",
|
||||||
|
" }",
|
||||||
|
" if (MEM8[0xF731u] > 3u) {",
|
||||||
|
" return;",
|
||||||
|
" }",
|
||||||
|
"",
|
||||||
|
" if ((MEM8[0xF791u] & BIT(5)) == 0u) {",
|
||||||
|
f" /* Requests selector 0x{selector_value & 0x01FF:04X}=0x{active_value & 0xFFFF:04X}: {active_report}. */",
|
||||||
|
f" /* CCU should ACK {ack_frame}, then mirror {active_mirror}. */",
|
||||||
|
" } else {",
|
||||||
|
f" /* Requests selector 0x{selector_value & 0x01FF:04X}=0x{clear_value & 0xFFFF:04X}: {clear_report}. */",
|
||||||
|
f" /* CCU should ACK {ack_frame}, then mirror {clear_mirror}. */",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def _timer_tick_function_lines(function_name: str, counters: list[JsonObject], summary: str) -> list[str]:
|
def _timer_tick_function_lines(function_name: str, counters: list[JsonObject], summary: str) -> list[str]:
|
||||||
lines = [
|
lines = [
|
||||||
f"void {function_name}(void)",
|
f"void {function_name}(void)",
|
||||||
@@ -1130,6 +1315,16 @@ def _command_hex(value: object) -> str:
|
|||||||
return "?"
|
return "?"
|
||||||
|
|
||||||
|
|
||||||
|
def _selector_hex(value: object) -> str:
|
||||||
|
if isinstance(value, int):
|
||||||
|
return f"0x{value & 0xFFFF:04X}"
|
||||||
|
return "?"
|
||||||
|
|
||||||
|
|
||||||
|
def _int_from_object(value: object, default: int) -> int:
|
||||||
|
return value if isinstance(value, int) else default
|
||||||
|
|
||||||
|
|
||||||
def _tx_functions(candidate: JsonObject, opts: SerialPseudocodeOptions) -> list[str]:
|
def _tx_functions(candidate: JsonObject, opts: SerialPseudocodeOptions) -> list[str]:
|
||||||
length = _int_field(candidate, "frame_length", 6)
|
length = _int_field(candidate, "frame_length", 6)
|
||||||
seed = _int_field(candidate, "checksum_seed", 0x5A)
|
seed = _int_field(candidate, "checksum_seed", 0x5A)
|
||||||
@@ -1386,3 +1581,7 @@ def _safe_identifier(value: str) -> str:
|
|||||||
|
|
||||||
def _comment_text(text: str) -> str:
|
def _comment_text(text: str) -> str:
|
||||||
return text.replace("*/", "* /").replace("\r", " ").replace("\n", " ")
|
return text.replace("*/", "* /").replace("\r", " ").replace("\n", " ")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import time
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, TextIO
|
from typing import Any, Callable, TextIO
|
||||||
|
|
||||||
from .bench_connect_lcd import (
|
from .bench_connect_lcd import (
|
||||||
BenchLogger,
|
BenchLogger,
|
||||||
@@ -21,15 +21,18 @@ from .bench_connect_lcd import (
|
|||||||
_send_frame,
|
_send_frame,
|
||||||
_wait_for_ready,
|
_wait_for_ready,
|
||||||
format_frame,
|
format_frame,
|
||||||
|
frame_checksum,
|
||||||
frame_checksum_ok,
|
frame_checksum_ok,
|
||||||
parse_frame,
|
parse_frame,
|
||||||
serial_format_label,
|
serial_format_label,
|
||||||
)
|
)
|
||||||
|
from .camera_snapshots import CameraSnapshots
|
||||||
from .serial_table_dump import build_read_frame, decode_table_read_response
|
from .serial_table_dump import build_read_frame, decode_table_read_response
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_ACK_TARGET = bytes.fromhex("07804020902D")
|
DEFAULT_ACK_TARGET = bytes.fromhex("07804020902D")
|
||||||
DEFAULT_ACK_FRAME = bytes.fromhex("05004000001F")
|
DEFAULT_ACK_FRAME = bytes.fromhex("05004000001F")
|
||||||
|
HEARTBEAT_FRAME = bytes.fromhex("0000000080DA")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -42,6 +45,9 @@ class ScenarioContext:
|
|||||||
table_rows: list[dict[str, Any]] = field(default_factory=list)
|
table_rows: list[dict[str, Any]] = field(default_factory=list)
|
||||||
target_counts: dict[str, int] = field(default_factory=dict)
|
target_counts: dict[str, int] = field(default_factory=dict)
|
||||||
tx_records: list[dict[str, Any]] = field(default_factory=list)
|
tx_records: list[dict[str, Any]] = field(default_factory=list)
|
||||||
|
snapshot_records: list[dict[str, Any]] = field(default_factory=list)
|
||||||
|
snapshotter: CameraSnapshots | None = None
|
||||||
|
current_step_index: int | None = None
|
||||||
ack_sent: int = 0
|
ack_sent: int = 0
|
||||||
abort_requested: bool = False
|
abort_requested: bool = False
|
||||||
|
|
||||||
@@ -52,6 +58,37 @@ def default_log_path(scenario: dict[str, Any]) -> Path:
|
|||||||
return Path("captures") / f"{safe_name}-{datetime.now().strftime('%Y%m%d-%H%M%S')}.txt"
|
return Path("captures") / f"{safe_name}-{datetime.now().strftime('%Y%m%d-%H%M%S')}.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def _snapshot_output_dir(args: argparse.Namespace, log_path: Path) -> Path | None:
|
||||||
|
if args.snapshot_dir is not None:
|
||||||
|
return args.snapshot_dir
|
||||||
|
if args.camera_index is not None:
|
||||||
|
return log_path.parent / f"{log_path.stem}-snapshots"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _snapshot_camera_index(args: argparse.Namespace) -> int:
|
||||||
|
return 0 if args.camera_index is None else args.camera_index
|
||||||
|
|
||||||
|
|
||||||
|
def _snapshot_delays(args: argparse.Namespace) -> list[float]:
|
||||||
|
text = str(args.snapshot_delays or "").strip()
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
delays: list[float] = []
|
||||||
|
for part in text.split(","):
|
||||||
|
item = part.strip()
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
delay = float(item)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise SystemExit(f"invalid snapshot delay {item!r}") from exc
|
||||||
|
if delay < 0:
|
||||||
|
raise SystemExit("snapshot delays must be zero or positive")
|
||||||
|
delays.append(delay)
|
||||||
|
return delays
|
||||||
|
|
||||||
|
|
||||||
def build_arg_parser() -> argparse.ArgumentParser:
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Run JSON-described serial bench scenarios against the real RCP."
|
description="Run JSON-described serial bench scenarios against the real RCP."
|
||||||
@@ -69,6 +106,39 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
parser.add_argument("--sync", choices=("checksum", "fixed"), default="checksum", help="RX frame sync strategy")
|
parser.add_argument("--sync", choices=("checksum", "fixed"), default="checksum", help="RX frame sync strategy")
|
||||||
parser.add_argument("--log", type=Path, help="capture log path")
|
parser.add_argument("--log", type=Path, help="capture log path")
|
||||||
parser.add_argument("--result-json", type=Path, help="write machine-readable scenario summary")
|
parser.add_argument("--result-json", type=Path, help="write machine-readable scenario summary")
|
||||||
|
parser.add_argument(
|
||||||
|
"--quiet-console",
|
||||||
|
action="store_true",
|
||||||
|
help="keep full logs in --log but suppress RX/DETECT chatter on the console",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--camera-index",
|
||||||
|
type=int,
|
||||||
|
help="enable webcam snapshots with this OpenCV camera index; use 0 for the default camera",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--snapshot-dir",
|
||||||
|
type=Path,
|
||||||
|
help="directory for webcam snapshots; enables camera index 0 if --camera-index is omitted",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--snapshot-delays",
|
||||||
|
default="0",
|
||||||
|
help="comma-separated seconds after each send to capture, e.g. 0,0.25,1.0",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--snapshot-before-send",
|
||||||
|
action="store_true",
|
||||||
|
help="also capture immediately before each send",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--snapshot-acks",
|
||||||
|
action="store_true",
|
||||||
|
help="capture snapshots for generated ACK frames as well as scenario send steps",
|
||||||
|
)
|
||||||
|
parser.add_argument("--snapshot-warmup", type=float, default=0.5, help="seconds to warm up the webcam")
|
||||||
|
parser.add_argument("--snapshot-width", type=int, help="requested webcam capture width")
|
||||||
|
parser.add_argument("--snapshot-height", type=int, help="requested webcam capture height")
|
||||||
parser.add_argument("--dry-run", action="store_true", help="print the plan without opening serial ports")
|
parser.add_argument("--dry-run", action="store_true", help="print the plan without opening serial ports")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@@ -83,8 +153,11 @@ def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
serial = _import_serial()
|
serial = _import_serial()
|
||||||
logger = BenchLogger(log_path, stdout=stdout)
|
console = _FilteredStdout(stdout, _quiet_console_line) if args.quiet_console else stdout
|
||||||
|
logger = BenchLogger(log_path, stdout=console)
|
||||||
detector = FrameDetector(sync_mode=args.sync)
|
detector = FrameDetector(sync_mode=args.sync)
|
||||||
|
snapshotter: CameraSnapshots | None = None
|
||||||
|
ctx: ScenarioContext | None = None
|
||||||
try:
|
try:
|
||||||
logger.emit("Serial bench scenario")
|
logger.emit("Serial bench scenario")
|
||||||
logger.emit(f"name={scenario.get('name', args.scenario.stem)}")
|
logger.emit(f"name={scenario.get('name', args.scenario.stem)}")
|
||||||
@@ -93,24 +166,54 @@ def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
|||||||
f"relay={args.relay_port} {args.relay_baud} sync={args.sync}"
|
f"relay={args.relay_port} {args.relay_baud} sync={args.sync}"
|
||||||
)
|
)
|
||||||
logger.emit(f"log={log_path}")
|
logger.emit(f"log={log_path}")
|
||||||
|
snapshot_dir = _snapshot_output_dir(args, log_path)
|
||||||
|
if snapshot_dir is not None:
|
||||||
|
snapshotter = CameraSnapshots(
|
||||||
|
camera_index=_snapshot_camera_index(args),
|
||||||
|
output_dir=snapshot_dir,
|
||||||
|
warmup_seconds=args.snapshot_warmup,
|
||||||
|
width=args.snapshot_width,
|
||||||
|
height=args.snapshot_height,
|
||||||
|
)
|
||||||
|
snapshotter.open()
|
||||||
|
logger.emit(
|
||||||
|
f"snapshots={snapshot_dir} camera_index={_snapshot_camera_index(args)} "
|
||||||
|
f"delays={','.join(f'{delay:g}' for delay in _snapshot_delays(args))}"
|
||||||
|
)
|
||||||
with open_device_serial(serial, args) as device:
|
with open_device_serial(serial, args) as device:
|
||||||
ctx = ScenarioContext(args=args, logger=logger, detector=detector, device=device)
|
ctx = ScenarioContext(
|
||||||
|
args=args,
|
||||||
|
logger=logger,
|
||||||
|
detector=detector,
|
||||||
|
device=device,
|
||||||
|
snapshotter=snapshotter,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
for index, step in enumerate(_scenario_steps(scenario), start=1):
|
for index, step in enumerate(_scenario_steps(scenario), start=1):
|
||||||
if ctx.abort_requested:
|
if ctx.abort_requested:
|
||||||
logger.event("SCENARIO_ABORT requested by prior step")
|
logger.event("SCENARIO_ABORT requested by prior step")
|
||||||
break
|
break
|
||||||
action, spec = _normalize_step(step)
|
action, spec = _normalize_step(step)
|
||||||
|
ctx.current_step_index = index
|
||||||
logger.event(f"STEP {index} {action}")
|
logger.event(f"STEP {index} {action}")
|
||||||
_run_step(ctx, action, spec)
|
_run_step(ctx, action, spec)
|
||||||
finally:
|
finally:
|
||||||
|
ctx.current_step_index = None
|
||||||
if ctx.relay is not None:
|
if ctx.relay is not None:
|
||||||
ctx.relay.close()
|
ctx.relay.close()
|
||||||
|
if snapshotter is not None and ctx is not None:
|
||||||
|
snapshotter.close()
|
||||||
|
ctx.snapshot_records = snapshotter.records()
|
||||||
|
snapshotter = None
|
||||||
|
if ctx is None:
|
||||||
|
raise RuntimeError("scenario did not create a context")
|
||||||
_emit_summary(ctx, logger)
|
_emit_summary(ctx, logger)
|
||||||
if args.result_json:
|
if args.result_json:
|
||||||
_write_result_json(args.result_json, scenario, log_path, ctx)
|
_write_result_json(args.result_json, scenario, log_path, ctx)
|
||||||
return 0
|
return 0
|
||||||
finally:
|
finally:
|
||||||
|
if snapshotter is not None:
|
||||||
|
snapshotter.close()
|
||||||
logger.close()
|
logger.close()
|
||||||
|
|
||||||
|
|
||||||
@@ -165,14 +268,23 @@ def _run_step(ctx: ScenarioContext, action: str, spec: dict[str, Any]) -> None:
|
|||||||
raise SystemExit(2)
|
raise SystemExit(2)
|
||||||
elif action in {"drain", "listen", "wait"}:
|
elif action in {"drain", "listen", "wait"}:
|
||||||
_listen(ctx, float(spec.get("seconds", spec.get("value", 0.0))))
|
_listen(ctx, float(spec.get("seconds", spec.get("value", 0.0))))
|
||||||
|
elif action == "listen_ack":
|
||||||
|
_step_listen_ack(ctx, spec)
|
||||||
|
elif action == "listen_ack_until_quiet":
|
||||||
|
_step_listen_ack_until_quiet(ctx, spec)
|
||||||
elif action == "send":
|
elif action == "send":
|
||||||
frame = _parse_required_frame(spec.get("frame"))
|
frame = _parse_required_frame(spec.get("frame"))
|
||||||
label = str(spec.get("label", "send"))
|
label = str(spec.get("label", "send"))
|
||||||
_send_and_record(ctx, frame, label)
|
capture = bool(spec.get("snapshot", True))
|
||||||
|
_send_and_record(ctx, frame, label, capture=capture)
|
||||||
if float(spec.get("listen", 0.0)) > 0:
|
if float(spec.get("listen", 0.0)) > 0:
|
||||||
_listen(ctx, float(spec.get("listen", 0.0)))
|
_listen(ctx, float(spec.get("listen", 0.0)))
|
||||||
elif action == "wait_for":
|
elif action == "wait_for":
|
||||||
_step_wait_for(ctx, spec)
|
_step_wait_for(ctx, spec)
|
||||||
|
elif action == "prompt":
|
||||||
|
_step_prompt(ctx, spec)
|
||||||
|
elif action == "note":
|
||||||
|
_step_note(ctx, spec)
|
||||||
elif action == "table_sweep":
|
elif action == "table_sweep":
|
||||||
_step_table_sweep(ctx, spec)
|
_step_table_sweep(ctx, spec)
|
||||||
elif action == "repeat":
|
elif action == "repeat":
|
||||||
@@ -214,6 +326,24 @@ def _step_wait_for(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
|||||||
raise SystemExit(3)
|
raise SystemExit(3)
|
||||||
|
|
||||||
|
|
||||||
|
def _step_prompt(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
||||||
|
message = str(spec.get("message", spec.get("value", "Press Enter to continue.")))
|
||||||
|
ctx.logger.event(f"PROMPT {message}")
|
||||||
|
input(message + " ")
|
||||||
|
|
||||||
|
|
||||||
|
def _step_note(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
||||||
|
message = str(spec.get("message", spec.get("value", "")))
|
||||||
|
if bool(spec.get("banner", False)):
|
||||||
|
ctx.logger.emit("")
|
||||||
|
ctx.logger.emit("NOTE " + "=" * 68)
|
||||||
|
ctx.logger.event(f"NOTE {message}")
|
||||||
|
ctx.logger.emit("NOTE " + "=" * 68)
|
||||||
|
ctx.logger.emit("")
|
||||||
|
else:
|
||||||
|
ctx.logger.event(f"NOTE {message}")
|
||||||
|
|
||||||
|
|
||||||
def _step_repeat(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
def _step_repeat(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
||||||
count = max(0, int(spec.get("count", 1)))
|
count = max(0, int(spec.get("count", 1)))
|
||||||
steps = spec.get("steps", [])
|
steps = spec.get("steps", [])
|
||||||
@@ -230,9 +360,13 @@ def _step_table_sweep(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
|||||||
selectors = _selector_list(spec)
|
selectors = _selector_list(spec)
|
||||||
gap = float(spec.get("gap", 0.080))
|
gap = float(spec.get("gap", 0.080))
|
||||||
ack = _ack_config(spec.get("ack_on", {}))
|
ack = _ack_config(spec.get("ack_on", {}))
|
||||||
|
ack_note = (
|
||||||
|
"ack=disabled"
|
||||||
|
if not ack["enabled"]
|
||||||
|
else f"ack_targets={len(ack['targets'])} ack_frame={format_frame(ack['frame'])}"
|
||||||
|
)
|
||||||
ctx.logger.event(
|
ctx.logger.event(
|
||||||
f"TABLE_SWEEP selectors={len(selectors)} gap={gap:.3f}s "
|
f"TABLE_SWEEP selectors={len(selectors)} gap={gap:.3f}s {ack_note}"
|
||||||
f"ack_targets={len(ack['targets'])} ack_frame={format_frame(ack['frame'])}"
|
|
||||||
)
|
)
|
||||||
for selector in selectors:
|
for selector in selectors:
|
||||||
if ctx.abort_requested:
|
if ctx.abort_requested:
|
||||||
@@ -240,13 +374,80 @@ def _step_table_sweep(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
|||||||
break
|
break
|
||||||
frame = build_read_frame(selector)
|
frame = build_read_frame(selector)
|
||||||
ctx.logger.event(f"READ selector=0x{selector:03X} frame={format_frame(frame)}")
|
ctx.logger.event(f"READ selector=0x{selector:03X} frame={format_frame(frame)}")
|
||||||
_send_and_record(ctx, frame, f"read_0x{selector:03X}")
|
_send_and_record(ctx, frame, f"read_0x{selector:03X}", capture=bool(spec.get("snapshot", False)))
|
||||||
_listen_with_ack(ctx, gap, selector, ack)
|
_listen_with_ack(ctx, gap, selector, ack)
|
||||||
|
|
||||||
|
|
||||||
|
def _step_listen_ack(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
||||||
|
seconds = float(spec.get("seconds", spec.get("value", 1.0)))
|
||||||
|
ack = _ack_config_from_step(spec)
|
||||||
|
ack_text = (
|
||||||
|
f"ack_frame={format_frame(ack['frame'])}"
|
||||||
|
if ack["ack_mode"] == "fixed"
|
||||||
|
else f"ack_mode={ack['ack_mode']}"
|
||||||
|
)
|
||||||
|
ctx.logger.event(
|
||||||
|
f"LISTEN_ACK seconds={seconds:.3f} target_mode={ack['target_mode']} targets={len(ack['targets'])} "
|
||||||
|
f"{ack_text} limit_scope={ack['limit_scope']} max_acks={ack['max_acks']}"
|
||||||
|
)
|
||||||
|
_listen_with_ack(ctx, seconds, None, ack)
|
||||||
|
|
||||||
|
|
||||||
|
def _step_listen_ack_until_quiet(ctx: ScenarioContext, spec: dict[str, Any]) -> None:
|
||||||
|
seconds = float(spec.get("seconds", spec.get("value", 10.0)))
|
||||||
|
quiet_seconds = float(spec.get("quiet_seconds", spec.get("quiet", 0.750)))
|
||||||
|
ack = _ack_config_from_step(spec)
|
||||||
|
ack_text = (
|
||||||
|
f"ack_frame={format_frame(ack['frame'])}"
|
||||||
|
if ack["ack_mode"] == "fixed"
|
||||||
|
else f"ack_mode={ack['ack_mode']}"
|
||||||
|
)
|
||||||
|
ctx.logger.event(
|
||||||
|
f"LISTEN_ACK_UNTIL_QUIET seconds={seconds:.3f} quiet={quiet_seconds:.3f} "
|
||||||
|
f"target_mode={ack['target_mode']} targets={len(ack['targets'])} "
|
||||||
|
f"{ack_text} limit_scope={ack['limit_scope']} max_acks={ack['max_acks']}"
|
||||||
|
)
|
||||||
|
_listen_with_ack(ctx, seconds, None, ack, quiet_seconds=quiet_seconds)
|
||||||
|
|
||||||
|
|
||||||
|
def _ack_config_from_step(spec: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
return _ack_config(
|
||||||
|
{
|
||||||
|
"enabled": spec.get("enabled", True),
|
||||||
|
"frames": spec.get("frames", spec.get("frame")),
|
||||||
|
"ack_frame": spec.get("ack_frame"),
|
||||||
|
"ack_guard": spec.get("ack_guard", 0.020),
|
||||||
|
"poll_interval": spec.get("poll_interval", 0.005),
|
||||||
|
"post_ack_read": spec.get("post_ack_read", 0.250),
|
||||||
|
"once_per_selector": spec.get("once_per_frame", False),
|
||||||
|
"max_acks": spec.get("max_acks"),
|
||||||
|
"max_target_hits": spec.get("max_target_hits"),
|
||||||
|
"abort_on_limit": spec.get("abort_on_limit", False),
|
||||||
|
"ack_mode": spec.get("ack_mode", spec.get("mode", "fixed")),
|
||||||
|
"target_mode": spec.get("target_mode", spec.get("match", "explicit")),
|
||||||
|
"limit_scope": spec.get("limit_scope", spec.get("scope", "local")),
|
||||||
|
"respond_on": spec.get("respond_on", spec.get("send_on", [])),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _ack_config(raw: Any) -> dict[str, Any]:
|
def _ack_config(raw: Any) -> dict[str, Any]:
|
||||||
spec = raw if isinstance(raw, dict) else {}
|
spec = raw if isinstance(raw, dict) else {}
|
||||||
targets = _parse_frame_list(spec.get("frames", spec.get("frame", DEFAULT_ACK_TARGET)))
|
enabled = bool(spec.get("enabled", True))
|
||||||
|
ack_mode = str(spec.get("ack_mode", spec.get("mode", "fixed"))).strip().lower().replace("-", "_")
|
||||||
|
if ack_mode not in {"fixed", "cmd5_selector"}:
|
||||||
|
raise SystemExit(f"unknown ack_mode {ack_mode!r}")
|
||||||
|
target_mode = str(spec.get("target_mode", spec.get("match", "explicit"))).strip().lower().replace("-", "_")
|
||||||
|
if target_mode == "queued_report":
|
||||||
|
target_mode = "queued_reports"
|
||||||
|
if target_mode not in {"explicit", "queued_reports"}:
|
||||||
|
raise SystemExit(f"unknown target_mode {target_mode!r}")
|
||||||
|
limit_scope = str(spec.get("limit_scope", spec.get("scope", "global"))).strip().lower().replace("-", "_")
|
||||||
|
if limit_scope not in {"global", "local"}:
|
||||||
|
raise SystemExit(f"unknown limit_scope {limit_scope!r}")
|
||||||
|
targets = set() if target_mode == "queued_reports" else _parse_frame_list(spec.get("frames", spec.get("frame", DEFAULT_ACK_TARGET)))
|
||||||
|
if not enabled:
|
||||||
|
targets = set()
|
||||||
return {
|
return {
|
||||||
"targets": set(targets),
|
"targets": set(targets),
|
||||||
"frame": _parse_optional_frame(spec.get("ack_frame"), DEFAULT_ACK_FRAME),
|
"frame": _parse_optional_frame(spec.get("ack_frame"), DEFAULT_ACK_FRAME),
|
||||||
@@ -254,37 +455,103 @@ def _ack_config(raw: Any) -> dict[str, Any]:
|
|||||||
"poll_interval": float(spec.get("poll_interval", 0.005)),
|
"poll_interval": float(spec.get("poll_interval", 0.005)),
|
||||||
"post_read": float(spec.get("post_ack_read", 0.250)),
|
"post_read": float(spec.get("post_ack_read", 0.250)),
|
||||||
"once_per_selector": bool(spec.get("once_per_selector", True)),
|
"once_per_selector": bool(spec.get("once_per_selector", True)),
|
||||||
"enabled": bool(spec.get("enabled", True)),
|
"enabled": enabled,
|
||||||
"max_acks": _optional_int(spec.get("max_acks")),
|
"max_acks": _optional_int(spec.get("max_acks")),
|
||||||
"max_target_hits": _optional_int(spec.get("max_target_hits")),
|
"max_target_hits": _optional_int(spec.get("max_target_hits")),
|
||||||
"abort_on_limit": bool(spec.get("abort_on_limit", True)),
|
"abort_on_limit": bool(spec.get("abort_on_limit", True)),
|
||||||
|
"ack_mode": ack_mode,
|
||||||
|
"target_mode": target_mode,
|
||||||
|
"limit_scope": limit_scope,
|
||||||
|
"respond_on": _response_rules(spec.get("respond_on", [])),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _ack_frame_for_target(target: bytes, ack: dict[str, Any]) -> bytes:
|
||||||
|
if ack["ack_mode"] == "fixed":
|
||||||
|
return ack["frame"]
|
||||||
|
if len(target) != 6:
|
||||||
|
raise SystemExit(f"cannot build selector ACK for malformed target {target!r}")
|
||||||
|
body = bytes([0x05, target[1] & 0x7F, target[2], 0x00, 0x00])
|
||||||
|
return body + bytes([frame_checksum(body)])
|
||||||
|
|
||||||
|
|
||||||
|
def _ack_matches(frame: bytes, ack: dict[str, Any]) -> bool:
|
||||||
|
if ack["target_mode"] == "explicit":
|
||||||
|
return frame in ack["targets"]
|
||||||
|
if frame == HEARTBEAT_FRAME or not frame_checksum_ok(frame):
|
||||||
|
return False
|
||||||
|
return frame[0] in {0x00, 0x01, 0x02} and not (frame[1] & 0x80)
|
||||||
|
|
||||||
|
|
||||||
|
def _response_rules(raw: Any) -> list[dict[str, Any]]:
|
||||||
|
values = raw if isinstance(raw, list) else ([raw] if raw else [])
|
||||||
|
rules: list[dict[str, Any]] = []
|
||||||
|
for index, value in enumerate(values):
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise SystemExit("respond_on entries must be objects")
|
||||||
|
targets = _parse_frame_list(value.get("frames", value.get("frame")))
|
||||||
|
response_frame = _parse_required_frame(value.get("send", value.get("response")))
|
||||||
|
label = str(value.get("label", f"respond_on_{index + 1}"))
|
||||||
|
rules.append(
|
||||||
|
{
|
||||||
|
"targets": targets,
|
||||||
|
"frame": response_frame,
|
||||||
|
"label": label,
|
||||||
|
"delay": float(value.get("delay", 0.0)),
|
||||||
|
"listen": float(value.get("listen", 0.0)),
|
||||||
|
"once": bool(value.get("once", True)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return rules
|
||||||
|
|
||||||
|
|
||||||
def _listen_with_ack(
|
def _listen_with_ack(
|
||||||
ctx: ScenarioContext,
|
ctx: ScenarioContext,
|
||||||
seconds: float,
|
seconds: float,
|
||||||
selector: int,
|
selector: int | None,
|
||||||
ack: dict[str, Any],
|
ack: dict[str, Any],
|
||||||
|
*,
|
||||||
|
quiet_seconds: float | None = None,
|
||||||
) -> list[bytes]:
|
) -> list[bytes]:
|
||||||
deadline = time.monotonic() + max(0.0, seconds)
|
deadline = time.monotonic() + max(0.0, seconds)
|
||||||
observed: list[bytes] = []
|
observed: list[bytes] = []
|
||||||
|
pending: list[bytes] = []
|
||||||
|
pending_index = 0
|
||||||
|
last_activity = time.monotonic()
|
||||||
acked_targets: set[bytes] = set()
|
acked_targets: set[bytes] = set()
|
||||||
|
fired_responses: set[int] = set()
|
||||||
|
ack_start = ctx.ack_sent
|
||||||
|
target_start = sum(ctx.target_counts.values())
|
||||||
|
|
||||||
|
def enqueue(frames: list[bytes]) -> None:
|
||||||
|
nonlocal last_activity
|
||||||
|
if not frames:
|
||||||
|
return
|
||||||
|
observed.extend(frames)
|
||||||
|
pending.extend(frames)
|
||||||
|
last_activity = time.monotonic()
|
||||||
|
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
frames = _read_available(ctx, selector=selector)
|
frames = _read_available(ctx, selector=selector)
|
||||||
observed.extend(frames)
|
enqueue(frames)
|
||||||
if not frames:
|
if not frames and pending_index >= len(pending):
|
||||||
|
if quiet_seconds is not None and time.monotonic() - last_activity >= quiet_seconds:
|
||||||
|
ctx.logger.event(f"LISTEN_ACK_QUIET quiet={quiet_seconds:.3f}s")
|
||||||
|
break
|
||||||
sleep_for = min(max(0.001, ack["poll_interval"]), max(0.0, deadline - time.monotonic()))
|
sleep_for = min(max(0.001, ack["poll_interval"]), max(0.0, deadline - time.monotonic()))
|
||||||
if sleep_for > 0:
|
if sleep_for > 0:
|
||||||
time.sleep(sleep_for)
|
time.sleep(sleep_for)
|
||||||
continue
|
continue
|
||||||
if not ack["enabled"]:
|
if not ack["enabled"]:
|
||||||
|
pending_index = len(pending)
|
||||||
continue
|
continue
|
||||||
for frame in frames:
|
while pending_index < len(pending):
|
||||||
if frame not in ack["targets"]:
|
frame = pending[pending_index]
|
||||||
|
pending_index += 1
|
||||||
|
if not _ack_matches(frame, ack):
|
||||||
continue
|
continue
|
||||||
_count_target(ctx, frame)
|
_count_target(ctx, frame)
|
||||||
if _ack_limit_reached(ctx, ack):
|
if _ack_limit_reached(ctx, ack, ack_start=ack_start, target_start=target_start):
|
||||||
ctx.logger.event("ACK_LIMIT reached before ACK send")
|
ctx.logger.event("ACK_LIMIT reached before ACK send")
|
||||||
if ack["abort_on_limit"]:
|
if ack["abort_on_limit"]:
|
||||||
ctx.abort_requested = True
|
ctx.abort_requested = True
|
||||||
@@ -294,15 +561,30 @@ def _listen_with_ack(
|
|||||||
continue
|
continue
|
||||||
acked_targets.add(frame)
|
acked_targets.add(frame)
|
||||||
if ack["guard"] > 0:
|
if ack["guard"] > 0:
|
||||||
observed.extend(_listen(ctx, ack["guard"], selector=selector))
|
enqueue(_listen(ctx, ack["guard"], selector=selector))
|
||||||
_send_and_record(ctx, ack["frame"], "ack")
|
_send_and_record(ctx, _ack_frame_for_target(frame, ack), "ack", capture=ctx.args.snapshot_acks)
|
||||||
ctx.ack_sent += 1
|
ctx.ack_sent += 1
|
||||||
if _ack_limit_reached(ctx, ack):
|
if _ack_limit_reached(ctx, ack, ack_start=ack_start, target_start=target_start):
|
||||||
ctx.logger.event("ACK_LIMIT reached after ACK send")
|
ctx.logger.event("ACK_LIMIT reached after ACK send")
|
||||||
if ack["abort_on_limit"]:
|
if ack["abort_on_limit"]:
|
||||||
ctx.abort_requested = True
|
ctx.abort_requested = True
|
||||||
if ack["post_read"] > 0:
|
if ack["post_read"] > 0:
|
||||||
observed.extend(_listen(ctx, ack["post_read"], selector=selector))
|
enqueue(_listen(ctx, ack["post_read"], selector=selector))
|
||||||
|
for rule_index, rule in enumerate(ack["respond_on"]):
|
||||||
|
if frame not in rule["targets"]:
|
||||||
|
continue
|
||||||
|
if rule["once"] and rule_index in fired_responses:
|
||||||
|
continue
|
||||||
|
fired_responses.add(rule_index)
|
||||||
|
if rule["delay"] > 0:
|
||||||
|
time.sleep(rule["delay"])
|
||||||
|
ctx.logger.event(
|
||||||
|
f"RESPOND_ON target={format_frame(frame)} "
|
||||||
|
f"send={format_frame(rule['frame'])} label={rule['label']}"
|
||||||
|
)
|
||||||
|
_send_and_record(ctx, rule["frame"], rule["label"], capture=ctx.args.snapshot_acks)
|
||||||
|
if rule["listen"] > 0:
|
||||||
|
enqueue(_listen(ctx, rule["listen"], selector=selector))
|
||||||
if ctx.abort_requested:
|
if ctx.abort_requested:
|
||||||
return observed
|
return observed
|
||||||
return observed
|
return observed
|
||||||
@@ -368,8 +650,12 @@ def _record_table_rows(ctx: ScenarioContext, frames: list[bytes], selector: int
|
|||||||
ctx.logger.event(f"TABLE selector=0x{selector:03X} echo={echo:02X} value={value:04X}")
|
ctx.logger.event(f"TABLE selector=0x{selector:03X} echo={echo:02X} value={value:04X}")
|
||||||
|
|
||||||
|
|
||||||
def _send_and_record(ctx: ScenarioContext, frame: bytes, label: str) -> None:
|
def _send_and_record(ctx: ScenarioContext, frame: bytes, label: str, *, capture: bool = True) -> None:
|
||||||
|
if capture:
|
||||||
|
_capture_snapshots(ctx, frame, label, phase_prefix="pre", delays=[])
|
||||||
_send_frame(ctx.device, frame, ctx.logger, label)
|
_send_frame(ctx.device, frame, ctx.logger, label)
|
||||||
|
if capture:
|
||||||
|
_capture_snapshots(ctx, frame, label, phase_prefix="tx", delays=_snapshot_delays(ctx.args))
|
||||||
ctx.tx_records.append(
|
ctx.tx_records.append(
|
||||||
{
|
{
|
||||||
"label": label,
|
"label": label,
|
||||||
@@ -379,6 +665,45 @@ def _send_and_record(ctx: ScenarioContext, frame: bytes, label: str) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _capture_snapshots(
|
||||||
|
ctx: ScenarioContext,
|
||||||
|
frame: bytes,
|
||||||
|
label: str,
|
||||||
|
*,
|
||||||
|
phase_prefix: str,
|
||||||
|
delays: list[float],
|
||||||
|
) -> None:
|
||||||
|
if ctx.snapshotter is None:
|
||||||
|
return
|
||||||
|
frame_text = format_frame(frame)
|
||||||
|
if phase_prefix == "pre":
|
||||||
|
if not ctx.args.snapshot_before_send:
|
||||||
|
return
|
||||||
|
phases = [("pre", 0.0)]
|
||||||
|
else:
|
||||||
|
phases = [(_delay_phase(delay), delay) for delay in delays]
|
||||||
|
|
||||||
|
for phase, delay in phases:
|
||||||
|
try:
|
||||||
|
record = ctx.snapshotter.schedule(
|
||||||
|
label=label,
|
||||||
|
frame_text=frame_text,
|
||||||
|
phase=phase,
|
||||||
|
delay_seconds=delay,
|
||||||
|
step_index=ctx.current_step_index,
|
||||||
|
)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
ctx.logger.event(f"SNAPSHOT_ERROR {label} {phase} {exc}")
|
||||||
|
continue
|
||||||
|
ctx.snapshot_records.append(record)
|
||||||
|
ctx.logger.event(f"SNAPSHOT_SCHEDULE {phase} {label} {record['path']}")
|
||||||
|
|
||||||
|
|
||||||
|
def _delay_phase(delay: float) -> str:
|
||||||
|
millis = int(round(max(0.0, delay) * 1000.0))
|
||||||
|
return f"tx+{millis:04d}ms"
|
||||||
|
|
||||||
|
|
||||||
def _count_target(ctx: ScenarioContext, frame: bytes) -> None:
|
def _count_target(ctx: ScenarioContext, frame: bytes) -> None:
|
||||||
text = format_frame(frame)
|
text = format_frame(frame)
|
||||||
ctx.target_counts[text] = ctx.target_counts.get(text, 0) + 1
|
ctx.target_counts[text] = ctx.target_counts.get(text, 0) + 1
|
||||||
@@ -433,12 +758,24 @@ def _optional_int(raw: Any) -> int | None:
|
|||||||
return _int_value(raw)
|
return _int_value(raw)
|
||||||
|
|
||||||
|
|
||||||
def _ack_limit_reached(ctx: ScenarioContext, ack: dict[str, Any]) -> bool:
|
def _ack_limit_reached(
|
||||||
|
ctx: ScenarioContext,
|
||||||
|
ack: dict[str, Any],
|
||||||
|
*,
|
||||||
|
ack_start: int = 0,
|
||||||
|
target_start: int = 0,
|
||||||
|
) -> bool:
|
||||||
|
if ack.get("limit_scope") == "local":
|
||||||
|
ack_count = ctx.ack_sent - ack_start
|
||||||
|
target_count = sum(ctx.target_counts.values()) - target_start
|
||||||
|
else:
|
||||||
|
ack_count = ctx.ack_sent
|
||||||
|
target_count = sum(ctx.target_counts.values())
|
||||||
max_acks = ack.get("max_acks")
|
max_acks = ack.get("max_acks")
|
||||||
if max_acks is not None and ctx.ack_sent >= max_acks:
|
if max_acks is not None and ack_count >= max_acks:
|
||||||
return True
|
return True
|
||||||
max_target_hits = ack.get("max_target_hits")
|
max_target_hits = ack.get("max_target_hits")
|
||||||
if max_target_hits is not None and sum(ctx.target_counts.values()) >= max_target_hits:
|
if max_target_hits is not None and target_count >= max_target_hits:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -449,26 +786,149 @@ def _print_dry_run(args: argparse.Namespace, scenario: dict[str, Any], log_path:
|
|||||||
print(f"relay={args.relay_port} {args.relay_baud}", file=stdout)
|
print(f"relay={args.relay_port} {args.relay_baud}", file=stdout)
|
||||||
print(f"sync={args.sync}", file=stdout)
|
print(f"sync={args.sync}", file=stdout)
|
||||||
print(f"log={log_path}", file=stdout)
|
print(f"log={log_path}", file=stdout)
|
||||||
|
snapshot_dir = _snapshot_output_dir(args, log_path)
|
||||||
|
if snapshot_dir is not None:
|
||||||
|
print(
|
||||||
|
f"snapshots={snapshot_dir} camera_index={_snapshot_camera_index(args)} "
|
||||||
|
f"before_send={int(args.snapshot_before_send)} "
|
||||||
|
f"delays={','.join(f'{delay:g}' for delay in _snapshot_delays(args))}",
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
for index, step in enumerate(_scenario_steps(scenario), start=1):
|
for index, step in enumerate(_scenario_steps(scenario), start=1):
|
||||||
action, spec = _normalize_step(step)
|
action, spec = _normalize_step(step)
|
||||||
print(f"step[{index}]={action}", file=stdout)
|
print(f"step[{index}]={action}", file=stdout)
|
||||||
if action == "send":
|
_print_step_dry_run(action, spec, stdout)
|
||||||
frame = _parse_required_frame(spec.get("frame"))
|
|
||||||
print(f" frame={format_frame(frame)} checksum_ok={int(frame_checksum_ok(frame))}", file=stdout)
|
|
||||||
elif action == "table_sweep":
|
class _FilteredStdout:
|
||||||
selectors = _selector_list(spec)
|
def __init__(self, stdout: TextIO, predicate: Callable[[str], bool]) -> None:
|
||||||
ack = _ack_config(spec.get("ack_on", {}))
|
self.stdout = stdout
|
||||||
if selectors:
|
self.predicate = predicate
|
||||||
first = selectors[0]
|
self.buffer = ""
|
||||||
last = selectors[-1]
|
|
||||||
print(f" selectors={len(selectors)} first=0x{first:03X} last=0x{last:03X}", file=stdout)
|
def write(self, text: str) -> int:
|
||||||
else:
|
self.buffer += text
|
||||||
print(" selectors=0", file=stdout)
|
while "\n" in self.buffer:
|
||||||
print(f" gap={float(spec.get('gap', 0.080)):.3f}", file=stdout)
|
line, self.buffer = self.buffer.split("\n", 1)
|
||||||
|
if self.predicate(line):
|
||||||
|
self.stdout.write(line + "\n")
|
||||||
|
self.stdout.flush()
|
||||||
|
return len(text)
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
if self.buffer:
|
||||||
|
if self.predicate(self.buffer):
|
||||||
|
self.stdout.write(self.buffer)
|
||||||
|
self.buffer = ""
|
||||||
|
self.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def _quiet_console_line(line: str) -> bool:
|
||||||
|
if not line.strip():
|
||||||
|
return True
|
||||||
|
keep_fragments = (
|
||||||
|
"Serial bench scenario",
|
||||||
|
"name=",
|
||||||
|
"device=",
|
||||||
|
"log=",
|
||||||
|
"STEP ",
|
||||||
|
"PROMPT ",
|
||||||
|
"NOTE ",
|
||||||
|
"SNAPSHOT_SCHEDULE ",
|
||||||
|
"SNAPSHOT_ERROR ",
|
||||||
|
"RESPOND_ON ",
|
||||||
|
"Summary",
|
||||||
|
"rx_frames=",
|
||||||
|
"resync_events=",
|
||||||
|
"tx_frames=",
|
||||||
|
"abort_requested=",
|
||||||
|
"known_shutter",
|
||||||
|
"queued_shutter",
|
||||||
|
"iris_mblack",
|
||||||
|
"selector_0013",
|
||||||
|
)
|
||||||
|
return any(fragment in line for fragment in keep_fragments)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_step_dry_run(action: str, spec: dict[str, Any], stdout: TextIO, *, indent: str = " ") -> None:
|
||||||
|
if action == "send":
|
||||||
|
frame = _parse_required_frame(spec.get("frame"))
|
||||||
|
print(f"{indent}frame={format_frame(frame)} checksum_ok={int(frame_checksum_ok(frame))}", file=stdout)
|
||||||
|
if float(spec.get("listen", 0.0)) > 0:
|
||||||
|
print(f"{indent}listen={float(spec.get('listen', 0.0)):.3f}s", file=stdout)
|
||||||
|
elif action in {"drain", "listen", "wait"}:
|
||||||
|
print(f"{indent}seconds={float(spec.get('seconds', spec.get('value', 0.0))):.3f}", file=stdout)
|
||||||
|
elif action in {"listen_ack", "listen_ack_until_quiet"}:
|
||||||
|
ack = _ack_config_from_step(spec)
|
||||||
|
print(f"{indent}seconds={float(spec.get('seconds', spec.get('value', 1.0))):.3f}", file=stdout)
|
||||||
|
if action == "listen_ack_until_quiet":
|
||||||
|
print(
|
||||||
|
f"{indent}quiet={float(spec.get('quiet_seconds', spec.get('quiet', 0.750))):.3f}s",
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
|
if not ack["enabled"]:
|
||||||
|
print(f"{indent}ack=disabled", file=stdout)
|
||||||
|
else:
|
||||||
|
print(f"{indent}target_mode={ack['target_mode']}", file=stdout)
|
||||||
for target in sorted(ack["targets"]):
|
for target in sorted(ack["targets"]):
|
||||||
print(f" ack_target={format_frame(target)}", file=stdout)
|
print(f"{indent}ack_target={format_frame(target)}", file=stdout)
|
||||||
print(f" ack_frame={format_frame(ack['frame'])}", file=stdout)
|
print(f"{indent}ack_mode={ack['ack_mode']}", file=stdout)
|
||||||
print(f" max_acks={ack['max_acks']} max_target_hits={ack['max_target_hits']}", file=stdout)
|
if ack["ack_mode"] == "fixed":
|
||||||
|
print(f"{indent}ack_frame={format_frame(ack['frame'])}", file=stdout)
|
||||||
|
print(
|
||||||
|
f"{indent}limit_scope={ack['limit_scope']} max_acks={ack['max_acks']} "
|
||||||
|
f"max_target_hits={ack['max_target_hits']}",
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
|
for rule in ack["respond_on"]:
|
||||||
|
print(
|
||||||
|
f"{indent}respond_on={len(rule['targets'])} "
|
||||||
|
f"send={format_frame(rule['frame'])} label={rule['label']} "
|
||||||
|
f"once={int(rule['once'])}",
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
|
elif action in {"prompt", "note"}:
|
||||||
|
message = str(spec.get("message", spec.get("value", "Press Enter to continue.")))
|
||||||
|
print(f"{indent}message={message}", file=stdout)
|
||||||
|
elif action == "wait_ready":
|
||||||
|
print(
|
||||||
|
f"{indent}heartbeats={int(spec.get('heartbeats', 2))} "
|
||||||
|
f"timeout={float(spec.get('timeout', 10.0)):.3f}s "
|
||||||
|
f"require={int(bool(spec.get('require', False)))}",
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
|
elif action == "table_sweep":
|
||||||
|
selectors = _selector_list(spec)
|
||||||
|
ack = _ack_config(spec.get("ack_on", {}))
|
||||||
|
if selectors:
|
||||||
|
first = selectors[0]
|
||||||
|
last = selectors[-1]
|
||||||
|
print(f"{indent}selectors={len(selectors)} first=0x{first:03X} last=0x{last:03X}", file=stdout)
|
||||||
|
else:
|
||||||
|
print(f"{indent}selectors=0", file=stdout)
|
||||||
|
print(f"{indent}gap={float(spec.get('gap', 0.080)):.3f}", file=stdout)
|
||||||
|
if not ack["enabled"]:
|
||||||
|
print(f"{indent}ack=disabled", file=stdout)
|
||||||
|
else:
|
||||||
|
for target in sorted(ack["targets"]):
|
||||||
|
print(f"{indent}ack_target={format_frame(target)}", file=stdout)
|
||||||
|
print(f"{indent}ack_frame={format_frame(ack['frame'])}", file=stdout)
|
||||||
|
print(
|
||||||
|
f"{indent}limit_scope={ack['limit_scope']} max_acks={ack['max_acks']} "
|
||||||
|
f"max_target_hits={ack['max_target_hits']}",
|
||||||
|
file=stdout,
|
||||||
|
)
|
||||||
|
elif action == "repeat":
|
||||||
|
count = max(0, int(spec.get("count", 1)))
|
||||||
|
steps = spec.get("steps", [])
|
||||||
|
step_count = len(steps) if isinstance(steps, list) else 0
|
||||||
|
print(f"{indent}count={count} steps={step_count}", file=stdout)
|
||||||
|
if not isinstance(steps, list):
|
||||||
|
return
|
||||||
|
for child_index, child in enumerate(steps, start=1):
|
||||||
|
child_action, child_spec = _normalize_step(child)
|
||||||
|
print(f"{indent}child[{child_index}]={child_action}", file=stdout)
|
||||||
|
_print_step_dry_run(child_action, child_spec, stdout, indent=indent + " ")
|
||||||
|
|
||||||
|
|
||||||
def _emit_summary(ctx: ScenarioContext, logger: BenchLogger) -> None:
|
def _emit_summary(ctx: ScenarioContext, logger: BenchLogger) -> None:
|
||||||
@@ -477,6 +937,7 @@ def _emit_summary(ctx: ScenarioContext, logger: BenchLogger) -> None:
|
|||||||
logger.emit(f"rx_frames={len(ctx.detector.frames)} trailing_unframed_bytes={len(ctx.detector.buffer)}")
|
logger.emit(f"rx_frames={len(ctx.detector.frames)} trailing_unframed_bytes={len(ctx.detector.buffer)}")
|
||||||
logger.emit(f"resync_events={ctx.detector.resync_events} dropped_bytes={ctx.detector.dropped_bytes}")
|
logger.emit(f"resync_events={ctx.detector.resync_events} dropped_bytes={ctx.detector.dropped_bytes}")
|
||||||
logger.emit(f"tx_frames={len(ctx.tx_records)} ack_sent={ctx.ack_sent} table_response_rows={len(ctx.table_rows)}")
|
logger.emit(f"tx_frames={len(ctx.tx_records)} ack_sent={ctx.ack_sent} table_response_rows={len(ctx.table_rows)}")
|
||||||
|
logger.emit(f"snapshots={len(ctx.snapshot_records)}")
|
||||||
logger.emit(f"abort_requested={int(ctx.abort_requested)}")
|
logger.emit(f"abort_requested={int(ctx.abort_requested)}")
|
||||||
for target, count in sorted(ctx.target_counts.items()):
|
for target, count in sorted(ctx.target_counts.items()):
|
||||||
logger.emit(f"ack_target {target}={count}")
|
logger.emit(f"ack_target {target}={count}")
|
||||||
@@ -500,6 +961,7 @@ def _write_result_json(path: Path, scenario: dict[str, Any], log_path: Path, ctx
|
|||||||
"labels": dict(ctx.detector.labels),
|
"labels": dict(ctx.detector.labels),
|
||||||
"tx_frames": ctx.tx_records,
|
"tx_frames": ctx.tx_records,
|
||||||
"ack_sent": ctx.ack_sent,
|
"ack_sent": ctx.ack_sent,
|
||||||
|
"snapshots": ctx.snapshot_records,
|
||||||
"abort_requested": ctx.abort_requested,
|
"abort_requested": ctx.abort_requested,
|
||||||
"ack_targets": ctx.target_counts,
|
"ack_targets": ctx.target_counts,
|
||||||
"table_rows": ctx.table_rows,
|
"table_rows": ctx.table_rows,
|
||||||
|
|||||||
121
h8536/serial_scenario_compare.py
Normal file
121
h8536/serial_scenario_compare.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, TextIO
|
||||||
|
|
||||||
|
from .bench_connect_lcd import format_frame, label_frame, parse_frame
|
||||||
|
|
||||||
|
|
||||||
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Compare two serial_scenario result JSON files and highlight extra button/report traffic."
|
||||||
|
)
|
||||||
|
parser.add_argument("baseline", type=Path, help="baseline result JSON, usually a no-button run")
|
||||||
|
parser.add_argument("candidate", type=Path, help="candidate result JSON, usually a one-button run")
|
||||||
|
parser.add_argument("--show-labels", action="store_true", help="also show label count deltas")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
||||||
|
args = build_arg_parser().parse_args(argv)
|
||||||
|
baseline = _load_result(args.baseline)
|
||||||
|
candidate = _load_result(args.candidate)
|
||||||
|
print(format_comparison(baseline, candidate, show_labels=args.show_labels), file=stdout)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _load_result(path: Path) -> dict[str, Any]:
|
||||||
|
with path.open("r", encoding="utf-8") as handle:
|
||||||
|
result = json.load(handle)
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
raise SystemExit(f"{path} is not a result JSON object")
|
||||||
|
result["_path"] = str(path)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def format_comparison(
|
||||||
|
baseline: dict[str, Any],
|
||||||
|
candidate: dict[str, Any],
|
||||||
|
*,
|
||||||
|
show_labels: bool = False,
|
||||||
|
) -> str:
|
||||||
|
base_targets = Counter(_string_int_mapping(baseline.get("ack_targets", {})))
|
||||||
|
candidate_targets = Counter(_string_int_mapping(candidate.get("ack_targets", {})))
|
||||||
|
target_delta = candidate_targets - base_targets
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"Serial scenario comparison",
|
||||||
|
f"baseline={baseline.get('_path', '')}",
|
||||||
|
f"candidate={candidate.get('_path', '')}",
|
||||||
|
f"baseline_log={baseline.get('log', '')}",
|
||||||
|
f"candidate_log={candidate.get('log', '')}",
|
||||||
|
f"baseline_rx_frames={baseline.get('rx_frames', 0)} candidate_rx_frames={candidate.get('rx_frames', 0)}",
|
||||||
|
f"baseline_ack_sent={baseline.get('ack_sent', 0)} candidate_ack_sent={candidate.get('ack_sent', 0)}",
|
||||||
|
]
|
||||||
|
|
||||||
|
if target_delta:
|
||||||
|
lines.append("extra ACK-target frames in candidate:")
|
||||||
|
for frame_text, count in sorted(target_delta.items(), key=_frame_sort_key):
|
||||||
|
lines.append(f" +{count:3d} {frame_text} {_describe_frame(frame_text)}")
|
||||||
|
else:
|
||||||
|
lines.append("no extra ACK-target frames found in candidate")
|
||||||
|
|
||||||
|
missing = base_targets - candidate_targets
|
||||||
|
if missing:
|
||||||
|
lines.append("baseline ACK-target frames missing/decreased in candidate:")
|
||||||
|
for frame_text, count in sorted(missing.items(), key=_frame_sort_key):
|
||||||
|
lines.append(f" -{count:3d} {frame_text} {_describe_frame(frame_text)}")
|
||||||
|
|
||||||
|
if show_labels:
|
||||||
|
base_labels = Counter(_string_int_mapping(baseline.get("labels", {})))
|
||||||
|
candidate_labels = Counter(_string_int_mapping(candidate.get("labels", {})))
|
||||||
|
label_delta = candidate_labels - base_labels
|
||||||
|
lines.append("label count increases:")
|
||||||
|
if label_delta:
|
||||||
|
for label, count in sorted(label_delta.items()):
|
||||||
|
lines.append(f" +{count:3d} {label}")
|
||||||
|
else:
|
||||||
|
lines.append(" none")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def _string_int_mapping(raw: Any) -> dict[str, int]:
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
return {}
|
||||||
|
output: dict[str, int] = {}
|
||||||
|
for key, value in raw.items():
|
||||||
|
try:
|
||||||
|
output[str(key)] = int(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def _describe_frame(frame_text: str) -> str:
|
||||||
|
frame = parse_frame(frame_text)
|
||||||
|
selector = ((frame[1] & 0x7F) << 7) | frame[2]
|
||||||
|
value = (frame[3] << 8) | frame[4]
|
||||||
|
label = label_frame(frame) or "checksum_ok_unlabeled"
|
||||||
|
return f"cmd=0x{frame[0]:02X} selector=0x{selector:04X} value=0x{value:04X} label={label}"
|
||||||
|
|
||||||
|
|
||||||
|
def _frame_sort_key(item: tuple[str, int]) -> tuple[int, int, int, str]:
|
||||||
|
frame_text, _count = item
|
||||||
|
try:
|
||||||
|
frame = parse_frame(frame_text)
|
||||||
|
except argparse.ArgumentTypeError:
|
||||||
|
return (0xFFFF, 0xFFFF, 0xFFFF, frame_text)
|
||||||
|
selector = ((frame[1] & 0x7F) << 7) | frame[2]
|
||||||
|
value = (frame[3] << 8) | frame[4]
|
||||||
|
return (selector, frame[0], value, frame_text)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"build_arg_parser",
|
||||||
|
"format_comparison",
|
||||||
|
"main",
|
||||||
|
]
|
||||||
158
h8536/serial_scenario_unexpected.py
Normal file
158
h8536/serial_scenario_unexpected.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable, TextIO
|
||||||
|
|
||||||
|
|
||||||
|
DETECT_RE = re.compile(
|
||||||
|
r"^(?P<time>\d\d:\d\d:\d\d\.\d{3})\s+DETECT\s+"
|
||||||
|
r"(?:(?P<label>\S+)\s+)?(?P<frame>(?:[0-9A-Fa-f]{2}\s*){6})\s*$"
|
||||||
|
)
|
||||||
|
|
||||||
|
DEFAULT_IGNORED_LABELS = {
|
||||||
|
"heartbeat",
|
||||||
|
"connect_ok_path_response_candidate",
|
||||||
|
"connect_c0_path_response_candidate",
|
||||||
|
"table_readback_candidate",
|
||||||
|
"active_selector0_keepalive_report",
|
||||||
|
"gated_active_0004_response_candidate",
|
||||||
|
"gated_active_0004_transition_candidate",
|
||||||
|
}
|
||||||
|
|
||||||
|
KNOWN_FRAME_LABELS = {
|
||||||
|
"00 00 00 80 80 5A": "active_selector0_keepalive_report",
|
||||||
|
"00 00 6C 00 00 36": "copy_completion_exit_selector_006c_candidate",
|
||||||
|
"00 00 6D 00 00 37": "copy_in_progress_selector_006d_candidate",
|
||||||
|
"00 00 15 80 00 CF": "known_call_button_active_report",
|
||||||
|
"00 00 15 00 00 4F": "known_call_button_inactive_report",
|
||||||
|
"00 00 07 80 00 DD": "known_cam_power_button_report",
|
||||||
|
"00 00 13 00 00 49": "known_iris_mblack_link_clear_report_candidate",
|
||||||
|
"00 00 13 40 00 09": "known_iris_mblack_link_active_report_candidate",
|
||||||
|
"00 00 13 80 00 C9": "known_selector_0013_bit15_report_candidate",
|
||||||
|
"00 00 13 C0 00 89": "known_selector_0013_bit15_plus_iris_mblack_link_report_candidate",
|
||||||
|
"01 00 13 00 00 48": "queued_iris_mblack_link_clear_report_candidate",
|
||||||
|
"02 00 13 00 00 4B": "queued_iris_mblack_link_clear_report_candidate",
|
||||||
|
"01 00 13 40 00 08": "queued_iris_mblack_link_active_report_candidate",
|
||||||
|
"02 00 13 40 00 0B": "queued_iris_mblack_link_active_report_candidate",
|
||||||
|
"01 00 13 80 00 C8": "queued_selector_0013_bit15_report_candidate",
|
||||||
|
"02 00 13 80 00 CB": "queued_selector_0013_bit15_report_candidate",
|
||||||
|
"01 00 13 C0 00 88": "queued_selector_0013_bit15_plus_iris_mblack_link_report_candidate",
|
||||||
|
"02 00 13 C0 00 8B": "queued_selector_0013_bit15_plus_iris_mblack_link_report_candidate",
|
||||||
|
"01 00 17 80 00 CC": "queued_bars_button_selector_0017_active_candidate",
|
||||||
|
"02 00 17 80 00 CF": "queued_bars_button_selector_0017_active_candidate",
|
||||||
|
"01 00 18 80 00 C3": "queued_bars_button_selector_0018_active_candidate",
|
||||||
|
"02 00 18 80 00 C0": "queued_bars_button_selector_0018_active_candidate",
|
||||||
|
"01 01 1A 08 00 48": "queued_iris_auto_button_selector_009a_active_candidate",
|
||||||
|
"02 01 1A 08 00 4B": "queued_iris_auto_button_selector_009a_active_candidate",
|
||||||
|
"01 00 04 00 00 5F": "gated_active_0004_response_candidate",
|
||||||
|
"02 00 04 00 00 5C": "gated_active_0004_transition_candidate",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DetectedFrame:
|
||||||
|
timestamp: str
|
||||||
|
label: str
|
||||||
|
frame: str
|
||||||
|
line_number: int
|
||||||
|
|
||||||
|
|
||||||
|
def build_arg_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Show unexpected DETECT frames from a serial_scenario capture log."
|
||||||
|
)
|
||||||
|
parser.add_argument("log", type=Path, help="serial_scenario capture log")
|
||||||
|
parser.add_argument(
|
||||||
|
"--include-refresh",
|
||||||
|
action="store_true",
|
||||||
|
help="include heartbeat, table-readback, and CONNECT OK refresh frames",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--show-all",
|
||||||
|
action="store_true",
|
||||||
|
help="print every matching DETECT frame after the summary",
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
||||||
|
args = build_arg_parser().parse_args(argv)
|
||||||
|
text = args.log.read_text(encoding="utf-8")
|
||||||
|
frames = parse_detected_frames(text.splitlines())
|
||||||
|
interesting = frames if args.include_refresh else filter_expected_refresh(frames)
|
||||||
|
print(format_report(frames, interesting, include_refresh=args.include_refresh, show_all=args.show_all), file=stdout)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def parse_detected_frames(lines: Iterable[str]) -> list[DetectedFrame]:
|
||||||
|
frames: list[DetectedFrame] = []
|
||||||
|
for line_number, line in enumerate(lines, start=1):
|
||||||
|
match = DETECT_RE.match(line.strip())
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
frame = " ".join(match.group("frame").upper().split())
|
||||||
|
label = KNOWN_FRAME_LABELS.get(frame, match.group("label") or "checksum_ok_unlabeled")
|
||||||
|
frames.append(
|
||||||
|
DetectedFrame(
|
||||||
|
timestamp=match.group("time"),
|
||||||
|
label=label,
|
||||||
|
frame=frame,
|
||||||
|
line_number=line_number,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return frames
|
||||||
|
|
||||||
|
|
||||||
|
def filter_expected_refresh(frames: Iterable[DetectedFrame]) -> list[DetectedFrame]:
|
||||||
|
return [frame for frame in frames if frame.label not in DEFAULT_IGNORED_LABELS]
|
||||||
|
|
||||||
|
|
||||||
|
def format_report(
|
||||||
|
frames: list[DetectedFrame],
|
||||||
|
interesting: list[DetectedFrame],
|
||||||
|
*,
|
||||||
|
include_refresh: bool,
|
||||||
|
show_all: bool,
|
||||||
|
) -> str:
|
||||||
|
lines = [
|
||||||
|
"Serial scenario unexpected-frame summary",
|
||||||
|
f"detected_frames={len(frames)} mode={'all' if include_refresh else 'unexpected-only'}",
|
||||||
|
]
|
||||||
|
label_counts = Counter(frame.label for frame in frames)
|
||||||
|
if label_counts:
|
||||||
|
lines.append("labels:")
|
||||||
|
for label, count in sorted(label_counts.items()):
|
||||||
|
lines.append(f" {label}: {count}")
|
||||||
|
ignored = len(frames) - len(interesting)
|
||||||
|
if not include_refresh:
|
||||||
|
lines.append(f"ignored_expected_refresh={ignored}")
|
||||||
|
lines.append(f"interesting_frames={len(interesting)}")
|
||||||
|
|
||||||
|
frame_counts = Counter((frame.frame, frame.label) for frame in interesting)
|
||||||
|
if frame_counts:
|
||||||
|
lines.append("interesting frame counts:")
|
||||||
|
for (frame_text, label), count in sorted(frame_counts.items(), key=lambda item: (-item[1], item[0][0])):
|
||||||
|
first = next(frame for frame in interesting if frame.frame == frame_text and frame.label == label)
|
||||||
|
lines.append(f" {count:4d}x {frame_text} label={label} first={first.timestamp} line={first.line_number}")
|
||||||
|
else:
|
||||||
|
lines.append("no unexpected checksum-valid frames found")
|
||||||
|
|
||||||
|
if show_all and interesting:
|
||||||
|
lines.append("timeline:")
|
||||||
|
for frame in interesting:
|
||||||
|
lines.append(f" {frame.timestamp} line={frame.line_number} {frame.frame} label={frame.label}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DetectedFrame",
|
||||||
|
"filter_expected_refresh",
|
||||||
|
"format_report",
|
||||||
|
"main",
|
||||||
|
"parse_detected_frames",
|
||||||
|
]
|
||||||
@@ -4,6 +4,8 @@ import re
|
|||||||
from collections.abc import Iterable, Mapping
|
from collections.abc import Iterable, Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from .panel_selectors import panel_selector_semantics_payload
|
||||||
|
|
||||||
|
|
||||||
JsonObject = dict[str, Any]
|
JsonObject = dict[str, Any]
|
||||||
|
|
||||||
@@ -140,6 +142,7 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
|||||||
"tx_report_model": None,
|
"tx_report_model": None,
|
||||||
"periodic_resend_model": None,
|
"periodic_resend_model": None,
|
||||||
"timer_interrupt_model": None,
|
"timer_interrupt_model": None,
|
||||||
|
"panel_selector_semantics": [],
|
||||||
"confidence": "low",
|
"confidence": "low",
|
||||||
"confidence_score": 0.0,
|
"confidence_score": 0.0,
|
||||||
"caveat": "No protocol semantics are emitted without both RX and TX serial reconstruction candidates.",
|
"caveat": "No protocol semantics are emitted without both RX and TX serial reconstruction candidates.",
|
||||||
@@ -212,6 +215,7 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
|||||||
"tx_report_model": tx_report_model,
|
"tx_report_model": tx_report_model,
|
||||||
"periodic_resend_model": periodic_resend_model,
|
"periodic_resend_model": periodic_resend_model,
|
||||||
"timer_interrupt_model": timer_interrupt_model,
|
"timer_interrupt_model": timer_interrupt_model,
|
||||||
|
"panel_selector_semantics": panel_selector_semantics_payload(),
|
||||||
"evidence": evidence,
|
"evidence": evidence,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -233,6 +237,7 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
|||||||
"tx_report_model": protocol["tx_report_model"],
|
"tx_report_model": protocol["tx_report_model"],
|
||||||
"periodic_resend_model": protocol["periodic_resend_model"],
|
"periodic_resend_model": protocol["periodic_resend_model"],
|
||||||
"timer_interrupt_model": protocol["timer_interrupt_model"],
|
"timer_interrupt_model": protocol["timer_interrupt_model"],
|
||||||
|
"panel_selector_semantics": protocol["panel_selector_semantics"],
|
||||||
"confidence": protocol["confidence"],
|
"confidence": protocol["confidence"],
|
||||||
"confidence_score": protocol["confidence_score"],
|
"confidence_score": protocol["confidence_score"],
|
||||||
"caveat": protocol["caveat"],
|
"caveat": protocol["caveat"],
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ def _logical_operand_accesses(
|
|||||||
logical_address: int | None = None
|
logical_address: int | None = None
|
||||||
if isinstance(offset, int):
|
if isinstance(offset, int):
|
||||||
logical_address = (int(table["logical_base_address"]) + offset) & 0xFFFF
|
logical_address = (int(table["logical_base_address"]) + offset) & 0xFFFF
|
||||||
|
selector = _selector_for_table_offset(table, offset)
|
||||||
access = _base_access(ins, functions, semantic_accesses)
|
access = _base_access(ins, functions, semantic_accesses)
|
||||||
access.update(
|
access.update(
|
||||||
{
|
{
|
||||||
@@ -306,6 +307,9 @@ def _logical_operand_accesses(
|
|||||||
if logical_address is not None:
|
if logical_address is not None:
|
||||||
access["logical_address"] = logical_address
|
access["logical_address"] = logical_address
|
||||||
access["logical_address_hex"] = h16(logical_address)
|
access["logical_address_hex"] = h16(logical_address)
|
||||||
|
if selector is not None:
|
||||||
|
access["selector"] = selector
|
||||||
|
access["selector_hex"] = f"0x{selector:03X}"
|
||||||
accesses.append(access)
|
accesses.append(access)
|
||||||
return accesses
|
return accesses
|
||||||
|
|
||||||
@@ -342,6 +346,7 @@ def _direct_logical_address_access(
|
|||||||
) -> JsonObject:
|
) -> JsonObject:
|
||||||
base = int(table["logical_base_address"])
|
base = int(table["logical_base_address"])
|
||||||
offset = address - base
|
offset = address - base
|
||||||
|
selector = _selector_for_table_offset(table, offset)
|
||||||
access = _base_access(ins, functions, semantic_accesses)
|
access = _base_access(ins, functions, semantic_accesses)
|
||||||
access.update(
|
access.update(
|
||||||
{
|
{
|
||||||
@@ -359,6 +364,9 @@ def _direct_logical_address_access(
|
|||||||
"access": _access_direction(ins, address) or "read_write_candidate",
|
"access": _access_direction(ins, address) or "read_write_candidate",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if selector is not None:
|
||||||
|
access["selector"] = selector
|
||||||
|
access["selector_hex"] = f"0x{selector:03X}"
|
||||||
return access
|
return access
|
||||||
|
|
||||||
|
|
||||||
@@ -373,6 +381,7 @@ def _direct_candidate_address_access(
|
|||||||
offset = address - base
|
offset = address - base
|
||||||
access = _base_access(ins, functions, semantic_accesses)
|
access = _base_access(ins, functions, semantic_accesses)
|
||||||
logical_offset = DIRECT_TABLE_TO_LOGICAL_OFFSET.get(base)
|
logical_offset = DIRECT_TABLE_TO_LOGICAL_OFFSET.get(base)
|
||||||
|
selector = _selector_for_table_offset(table, offset)
|
||||||
access.update(
|
access.update(
|
||||||
{
|
{
|
||||||
"table": table["name"],
|
"table": table["name"],
|
||||||
@@ -392,6 +401,9 @@ def _direct_candidate_address_access(
|
|||||||
if logical_offset is not None:
|
if logical_offset is not None:
|
||||||
access["semantic_negative_offset"] = logical_offset
|
access["semantic_negative_offset"] = logical_offset
|
||||||
access["semantic_negative_offset_hex"] = h16(logical_offset)
|
access["semantic_negative_offset_hex"] = h16(logical_offset)
|
||||||
|
if selector is not None:
|
||||||
|
access["selector"] = selector
|
||||||
|
access["selector_hex"] = f"0x{selector:03X}"
|
||||||
return access
|
return access
|
||||||
|
|
||||||
|
|
||||||
@@ -564,6 +576,8 @@ def _format_access_line(access: Mapping[str, Any]) -> str:
|
|||||||
index_text = f"index dynamic via {access.get('index_register')} operand {operand}"
|
index_text = f"index dynamic via {access.get('index_register')} operand {operand}"
|
||||||
else:
|
else:
|
||||||
index_text = f"offset {h16(int(index or 0))}"
|
index_text = f"offset {h16(int(index or 0))}"
|
||||||
|
if access.get("selector_hex"):
|
||||||
|
index_text += f" selector {access['selector_hex']}"
|
||||||
if access.get("logical_address_hex"):
|
if access.get("logical_address_hex"):
|
||||||
index_text += f" -> {access['logical_address_hex']}"
|
index_text += f" -> {access['logical_address_hex']}"
|
||||||
elif access.get("direct_address_hex"):
|
elif access.get("direct_address_hex"):
|
||||||
@@ -599,6 +613,19 @@ def _summarize_functions(accesses: Iterable[Mapping[str, Any]]) -> list[JsonObje
|
|||||||
return sorted(summaries.values(), key=lambda item: (-int(item["access_count"]), str(item["label"])))
|
return sorted(summaries.values(), key=lambda item: (-int(item["access_count"]), str(item["label"])))
|
||||||
|
|
||||||
|
|
||||||
|
def _selector_for_table_offset(table: Mapping[str, Any], offset: int | str) -> int | None:
|
||||||
|
if not isinstance(offset, int):
|
||||||
|
return None
|
||||||
|
element = str(table.get("element_candidate") or "")
|
||||||
|
if element == "word_value":
|
||||||
|
if offset % 2:
|
||||||
|
return None
|
||||||
|
return (offset // 2) & 0x01FF
|
||||||
|
if element == "bit_flags":
|
||||||
|
return offset & 0x01FF
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _function_ranges(payload: Mapping[str, Any]) -> list[JsonObject]:
|
def _function_ranges(payload: Mapping[str, Any]) -> list[JsonObject]:
|
||||||
call_graph = payload.get("call_graph")
|
call_graph = payload.get("call_graph")
|
||||||
if not isinstance(call_graph, Mapping):
|
if not isinstance(call_graph, Mapping):
|
||||||
|
|||||||
8
h8536_emulator_report_queue_probe.py
Normal file
8
h8536_emulator_report_queue_probe.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Compatibility wrapper for the H8/536 report-queue emulator probe."""
|
||||||
|
|
||||||
|
from h8536.emulator.report_queue_probe import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
8
h8536_panel_button_trace.py
Normal file
8
h8536_panel_button_trace.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Compatibility wrapper for the H8/536 panel button trace CLI."""
|
||||||
|
|
||||||
|
from h8536.panel_button_trace import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
52
scenarios/active-control-report-watch-broad.json
Normal file
52
scenarios/active-control-report-watch-broad.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "active-control-report-watch-broad",
|
||||||
|
"notes": [
|
||||||
|
"Hold CONNECT OK while streaming broader camera/status words that may enable more local controls.",
|
||||||
|
"Press or turn one physical control at a time while this runs. Note the rough time and control name.",
|
||||||
|
"This is noisier than the quiet watch: each cycle writes 0x008F and 0x0093, so use the unexpected-frame summarizer after the run."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_ok_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "repeat",
|
||||||
|
"count": 80,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "broad_refresh_008f_1800",
|
||||||
|
"frame": "00 01 0F 18 00 4C",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "broad_refresh_0093_ffff",
|
||||||
|
"frame": "00 01 13 FF FF 48",
|
||||||
|
"listen": 0.30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
70
scenarios/active-control-report-watch-gated.json
Normal file
70
scenarios/active-control-report-watch-gated.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"name": "active-control-report-watch-gated",
|
||||||
|
"notes": [
|
||||||
|
"Hold CONNECT OK and seed candidate secondary-table E400 gates before watching for local-control TX reports.",
|
||||||
|
"This follows the ROM clue that some local report paths require E400 feature bits, not only E000 display/status words.",
|
||||||
|
"Press or turn one physical control at a time while this runs. Note the rough time and control name."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_ok_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0015_other_copy_candidate",
|
||||||
|
"frame": "06 00 15 80 00 C9",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_008f_shutter_bits_11_12",
|
||||||
|
"frame": "06 01 0F 18 00 4A",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0093_all_bits",
|
||||||
|
"frame": "06 01 13 FF FF 4E",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "repeat",
|
||||||
|
"count": 80,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "gated_refresh_008f_1800",
|
||||||
|
"frame": "00 01 0F 18 00 4C",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "gated_refresh_0093_90ff",
|
||||||
|
"frame": "00 01 13 90 FF 27",
|
||||||
|
"listen": 0.30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
46
scenarios/active-control-report-watch-quiet.json
Normal file
46
scenarios/active-control-report-watch-quiet.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "active-control-report-watch-quiet",
|
||||||
|
"notes": [
|
||||||
|
"Hold the panel in CONNECT OK with the least noisy proven refresh stream, then watch for local-control TX reports.",
|
||||||
|
"Press or turn one physical control at a time while this runs. Note the rough time and control name.",
|
||||||
|
"Expected refresh traffic is table readback for 0x0093 plus 02 00 02 00 00 5A OK-path responses; anything else is interesting."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_ok_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "repeat",
|
||||||
|
"count": 100,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "quiet_active_refresh_0093_9020",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.60
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
208
scenarios/button-report-broad-gates-press.json
Normal file
208
scenarios/button-report-broad-gates-press.json
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
{
|
||||||
|
"name": "button-report-broad-gates-press",
|
||||||
|
"notes": [
|
||||||
|
"One-button candidate test with broad secondary E400 gates enabled for ROM-observed report selectors.",
|
||||||
|
"Run this only after the common-gate press test fails to produce a clear extra selector.",
|
||||||
|
"If a button only emits here, it probably needs a selector-specific feature/report gate rather than only the common queue-service gate."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "prompt",
|
||||||
|
"message": "Broad-gate run: pick one physical button. When the NOTE banner appears, press it once. Press Enter to start."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0007",
|
||||||
|
"frame": "06 00 07 FF FF 5B",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0013",
|
||||||
|
"frame": "06 00 13 FF FF 4F",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0015",
|
||||||
|
"frame": "06 00 15 FF FF 49",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0017",
|
||||||
|
"frame": "06 00 17 FF FF 4B",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0018",
|
||||||
|
"frame": "06 00 18 FF FF 44",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_001a",
|
||||||
|
"frame": "06 00 1A FF FF 46",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_006b",
|
||||||
|
"frame": "06 01 6B FF FF 36",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0083",
|
||||||
|
"frame": "06 01 03 FF FF 5E",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_008f",
|
||||||
|
"frame": "06 01 0F FF FF 52",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0093",
|
||||||
|
"frame": "06 01 13 FF FF 4E",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0096",
|
||||||
|
"frame": "06 01 16 FF FF 4B",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_0097",
|
||||||
|
"frame": "06 01 17 FF FF 4A",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_009a",
|
||||||
|
"frame": "06 01 1A FF FF 47",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_00b7",
|
||||||
|
"frame": "06 01 37 FF FF 6A",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_00b9",
|
||||||
|
"frame": "06 01 39 FF FF 64",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_00c4",
|
||||||
|
"frame": "06 02 44 FF FF 1A",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_00c6",
|
||||||
|
"frame": "06 02 46 FF FF 18",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "secondary_gate_00f8",
|
||||||
|
"frame": "06 02 78 FF FF 26",
|
||||||
|
"listen": 0.04
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "cmd5_latch_clear_0096",
|
||||||
|
"frame": "05 01 16 00 00 48",
|
||||||
|
"listen": 0.05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 3.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 48,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "recover_connect_ok_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 1.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 32,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS THE ONE TEST BUTTON NOW; broad secondary gates are enabled.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 16.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 96,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "repeat_last_for_hidden_report_check",
|
||||||
|
"frame": "07 00 00 00 00 5D",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
100
scenarios/button-report-common-gate-baseline.json
Normal file
100
scenarios/button-report-common-gate-baseline.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"name": "button-report-common-gate-baseline",
|
||||||
|
"notes": [
|
||||||
|
"No-button baseline for queued local-control report tests.",
|
||||||
|
"Do not press or turn controls during the NOTE window. Compare this result JSON against one-button candidate runs.",
|
||||||
|
"This uses the same common queue-service gate that exposed SHUTTER ON/OFF as selector 0x010F."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "prompt",
|
||||||
|
"message": "Baseline run: do not touch the panel during the NOTE window. Press Enter to start."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "cmd5_latch_clear_0096",
|
||||||
|
"frame": "05 01 16 00 00 48",
|
||||||
|
"listen": 0.05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 3.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 48,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "recover_connect_ok_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 1.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 32,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "NO BUTTONS NOW; baseline queued-report ACK window is running.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 16.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 96,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "repeat_last_for_hidden_report_check",
|
||||||
|
"frame": "07 00 00 00 00 5D",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
100
scenarios/button-report-common-gate-press.json
Normal file
100
scenarios/button-report-common-gate-press.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"name": "button-report-common-gate-press",
|
||||||
|
"notes": [
|
||||||
|
"One-button candidate test for queued local-control reports.",
|
||||||
|
"Run the common-gate baseline first, then run this once per physical button candidate and compare result JSON files.",
|
||||||
|
"If a button produces an extra selector here, it probably shares the same common queue-service gate as SHUTTER ON/OFF."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "prompt",
|
||||||
|
"message": "Pick one physical button for this run. When the NOTE banner appears, press it once. Press Enter to start."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "cmd5_latch_clear_0096",
|
||||||
|
"frame": "05 01 16 00 00 48",
|
||||||
|
"listen": 0.05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 3.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 48,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "recover_connect_ok_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 1.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 32,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS THE ONE TEST BUTTON NOW; live queued-report ACK window is running.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 16.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 96,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "repeat_last_for_hidden_report_check",
|
||||||
|
"frame": "07 00 00 00 00 5D",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
scenarios/copy-family00-006d-006c-1000ms.json
Normal file
47
scenarios/copy-family00-006d-006c-1000ms.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-family00-006d-006c-1000ms",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send family-00 selector 0x006D, then family-00 selector 0x006C 1 second later.",
|
||||||
|
"Gives the LCD time to visibly enter COPY IN PROGRESS if family-00 0x006D drives the same queue path as command 5."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "family00_copy_start_006d",
|
||||||
|
"frame": "00 00 6D 00 00 37",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "family00_copy_complete_candidate_006c",
|
||||||
|
"frame": "00 00 6C 00 00 36",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
scenarios/copy-family00-006d-006c-250ms.json
Normal file
47
scenarios/copy-family00-006d-006c-250ms.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-family00-006d-006c-250ms",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send family-00 selector 0x006D, then family-00 selector 0x006C 250 ms later.",
|
||||||
|
"Compares family-00 set/queue behavior against the known command-5 COPY start/complete pair."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "family00_copy_start_006d",
|
||||||
|
"frame": "00 00 6D 00 00 37",
|
||||||
|
"listen": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "family00_copy_complete_candidate_006c",
|
||||||
|
"frame": "00 00 6C 00 00 36",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
53
scenarios/copy-hold-006d-5x-006c.json
Normal file
53
scenarios/copy-hold-006d-5x-006c.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-hold-006d-5x-006c",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D every 1 second for five starts/progress ticks, then send 006C.",
|
||||||
|
"Tests whether repeated 006D can hold the copy-in-progress state for a longer transfer before completion."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "repeat",
|
||||||
|
"count": 5,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_progress_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
51
scenarios/copy-hold-006d-5x-no-complete.json
Normal file
51
scenarios/copy-hold-006d-5x-no-complete.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-hold-006d-5x-no-complete",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D every 1 second five times, then do not send 006C.",
|
||||||
|
"Tests whether the timeout/fallback is measured from the last 006D progress tick."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "repeat",
|
||||||
|
"count": 5,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_progress_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
53
scenarios/copy-repeat-006d-2x-006c.json
Normal file
53
scenarios/copy-repeat-006d-2x-006c.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-repeat-006d-2x-006c",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D twice one second apart, then send 006C.",
|
||||||
|
"Tests whether repeating the in-progress selector extends/resets the completion window or interferes with completion."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d_1",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d_2",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
59
scenarios/copy-repeat-006d-3x-006c.json
Normal file
59
scenarios/copy-repeat-006d-3x-006c.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-repeat-006d-3x-006c",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D three times one second apart, then send 006C.",
|
||||||
|
"Tests whether repeated in-progress selectors can keep the copy state alive longer than the single-start window."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d_1",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d_2",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d_3",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
41
scenarios/copy-step-006c-only.json
Normal file
41
scenarios/copy-step-006c-only.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-step-006c-only",
|
||||||
|
"notes": [
|
||||||
|
"Control run: recover to CONNECT OK, then send only command-5 selector 0x006C.",
|
||||||
|
"Expected from ROM model: 006C alone may clear/blank state because the 006D copy-in-progress flags are not live."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c_only",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 8.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
scenarios/copy-step-006d-006c-1000ms.json
Normal file
47
scenarios/copy-step-006d-006c-1000ms.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-step-006d-006c-1000ms",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D, then send 006C 1 second later.",
|
||||||
|
"This gives the LCD time to visibly enter COPY IN PROGRESS before the completion candidate arrives."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
scenarios/copy-step-006d-006c-1500ms.json
Normal file
47
scenarios/copy-step-006d-006c-1500ms.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-step-006d-006c-1500ms",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D, then send 006C 1.5 seconds later.",
|
||||||
|
"Narrows the working copy-complete window between the known 1.0s success and 2.5s failure."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
scenarios/copy-step-006d-006c-2000ms.json
Normal file
47
scenarios/copy-step-006d-006c-2000ms.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-step-006d-006c-2000ms",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D, then send 006C 2.0 seconds later.",
|
||||||
|
"Narrows the copy-complete acceptance window before the known 2.5s failure."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 2.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
scenarios/copy-step-006d-006c-2500ms.json
Normal file
47
scenarios/copy-step-006d-006c-2500ms.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-step-006d-006c-2500ms",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D, then send 006C 2.5 seconds later.",
|
||||||
|
"This tests whether completion remains accepted after the in-progress display has settled."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 2.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
scenarios/copy-step-006d-006c-250ms.json
Normal file
47
scenarios/copy-step-006d-006c-250ms.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-step-006d-006c-250ms",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, send 006D, then send 006C 250 ms later.",
|
||||||
|
"This tests whether completion must arrive while the 006D copy flags are freshly live."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_complete_candidate_006c",
|
||||||
|
"frame": "05 00 6C 00 00 33",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
41
scenarios/copy-step-006d-only.json
Normal file
41
scenarios/copy-step-006d-only.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "copy-step-006d-only",
|
||||||
|
"notes": [
|
||||||
|
"Recover to CONNECT OK, then send command-5 selector 0x006D.",
|
||||||
|
"Expected from ROM model: 006D starts the copy state, displays COPY IN PROGRESS, then eventually times out if no completion/CCU transfer follows."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"timeout": 10.0,
|
||||||
|
"heartbeats": 2,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_1",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 0.7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_baseline_2",
|
||||||
|
"frame": "04 00 00 80 00 DE",
|
||||||
|
"listen": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "copy_start_006d",
|
||||||
|
"frame": "05 00 6D 00 00 32",
|
||||||
|
"listen": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
227
scenarios/iris-mblack-link-mirror-state-machine.json
Normal file
227
scenarios/iris-mblack-link-mirror-state-machine.json
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
{
|
||||||
|
"name": "iris-mblack-link-mirror-state-machine",
|
||||||
|
"notes": [
|
||||||
|
"IRIS/M.BLACK LINK closed-loop state-machine probe.",
|
||||||
|
"The RCP reports local button intent as selector 0x0013. This scenario ACKs the report and mirrors the reported selector value back as a command-0 CCU table update.",
|
||||||
|
"Expected cycle if the CCU owns the latched state: press 1 emits 00 00 13 40 00 09, mirror active, press 2 emits 00 00 13 00 00 49, mirror clear, press 3 emits active again.",
|
||||||
|
"If every press still emits active, the local handler is not seeing the mirrored E800/F791 state or the physical input path is level-style rather than toggle-style."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "prompt",
|
||||||
|
"message": "Prepare to test the closed-loop IRIS/M.BLACK LINK state machine. Press Enter to power-cycle and start."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "cmd5_latch_clear_0096",
|
||||||
|
"frame": "05 01 16 00 00 48",
|
||||||
|
"listen": 0.02
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack_until_quiet",
|
||||||
|
"seconds": 28.00,
|
||||||
|
"quiet_seconds": 0.90,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.005,
|
||||||
|
"post_ack_read": 0.035,
|
||||||
|
"poll_interval": 0.003,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 180,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "force_selector_0013_clear_baseline",
|
||||||
|
"frame": "00 00 13 00 00 49",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "recover_connect_ok_seed_before_press_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS IRIS/M.BLACK LINK ONCE NOW. The script will mirror selector 0x0013 back to the RCP when it sees the report.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 9.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.005,
|
||||||
|
"post_ack_read": 0.035,
|
||||||
|
"poll_interval": 0.003,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 80,
|
||||||
|
"abort_on_limit": false,
|
||||||
|
"respond_on": [
|
||||||
|
{
|
||||||
|
"frames": [
|
||||||
|
"00 00 13 40 00 09",
|
||||||
|
"01 00 13 40 00 08",
|
||||||
|
"02 00 13 40 00 0B"
|
||||||
|
],
|
||||||
|
"send": "00 00 13 40 00 09",
|
||||||
|
"label": "mirror_selector_0013_active_from_button",
|
||||||
|
"delay": 0.050,
|
||||||
|
"listen": 0.20,
|
||||||
|
"once": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frames": [
|
||||||
|
"00 00 13 00 00 49",
|
||||||
|
"01 00 13 00 00 48",
|
||||||
|
"02 00 13 00 00 4B"
|
||||||
|
],
|
||||||
|
"send": "00 00 13 00 00 49",
|
||||||
|
"label": "mirror_selector_0013_clear_from_button",
|
||||||
|
"delay": 0.050,
|
||||||
|
"listen": 0.20,
|
||||||
|
"once": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "readback_selector_0013_after_press_1",
|
||||||
|
"frame": "01 00 13 00 00 48",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS IRIS/M.BLACK LINK A SECOND TIME NOW. If the mirror completed the latch, this should report clear.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 9.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.005,
|
||||||
|
"post_ack_read": 0.035,
|
||||||
|
"poll_interval": 0.003,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 80,
|
||||||
|
"abort_on_limit": false,
|
||||||
|
"respond_on": [
|
||||||
|
{
|
||||||
|
"frames": [
|
||||||
|
"00 00 13 40 00 09",
|
||||||
|
"01 00 13 40 00 08",
|
||||||
|
"02 00 13 40 00 0B"
|
||||||
|
],
|
||||||
|
"send": "00 00 13 40 00 09",
|
||||||
|
"label": "mirror_selector_0013_active_from_button",
|
||||||
|
"delay": 0.050,
|
||||||
|
"listen": 0.20,
|
||||||
|
"once": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frames": [
|
||||||
|
"00 00 13 00 00 49",
|
||||||
|
"01 00 13 00 00 48",
|
||||||
|
"02 00 13 00 00 4B"
|
||||||
|
],
|
||||||
|
"send": "00 00 13 00 00 49",
|
||||||
|
"label": "mirror_selector_0013_clear_from_button",
|
||||||
|
"delay": 0.050,
|
||||||
|
"listen": 0.20,
|
||||||
|
"once": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "readback_selector_0013_after_press_2",
|
||||||
|
"frame": "01 00 13 00 00 48",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS IRIS/M.BLACK LINK A THIRD TIME NOW to check that active returns after a mirrored clear.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 9.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.005,
|
||||||
|
"post_ack_read": 0.035,
|
||||||
|
"poll_interval": 0.003,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 80,
|
||||||
|
"abort_on_limit": false,
|
||||||
|
"respond_on": [
|
||||||
|
{
|
||||||
|
"frames": [
|
||||||
|
"00 00 13 40 00 09",
|
||||||
|
"01 00 13 40 00 08",
|
||||||
|
"02 00 13 40 00 0B"
|
||||||
|
],
|
||||||
|
"send": "00 00 13 40 00 09",
|
||||||
|
"label": "mirror_selector_0013_active_from_button",
|
||||||
|
"delay": 0.050,
|
||||||
|
"listen": 0.20,
|
||||||
|
"once": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frames": [
|
||||||
|
"00 00 13 00 00 49",
|
||||||
|
"01 00 13 00 00 48",
|
||||||
|
"02 00 13 00 00 4B"
|
||||||
|
],
|
||||||
|
"send": "00 00 13 00 00 49",
|
||||||
|
"label": "mirror_selector_0013_clear_from_button",
|
||||||
|
"delay": 0.050,
|
||||||
|
"listen": 0.20,
|
||||||
|
"once": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "readback_selector_0013_after_press_3",
|
||||||
|
"frame": "01 00 13 00 00 48",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "repeat_last_for_hidden_report_check",
|
||||||
|
"frame": "07 00 00 00 00 5D",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
114
scenarios/iris-mblack-link-report-after-quiet-press.json
Normal file
114
scenarios/iris-mblack-link-report-after-quiet-press.json
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"name": "iris-mblack-link-report-after-quiet-press",
|
||||||
|
"notes": [
|
||||||
|
"Second-pass IRIS/M.BLACK LINK button report test.",
|
||||||
|
"This drains the startup/current-state report queue until it goes quiet before asking for a physical press, so a fresh selector-0013 report is easier to separate from boot backlog.",
|
||||||
|
"Expected fresh report shapes are 00/01/02 00 13 40 00 for active, or 00/01/02 00 13 00 00 for clear. Page-1 selector 01 13 is not this button; it is selector 0x0093."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "prompt",
|
||||||
|
"message": "Prepare to test IRIS/M.BLACK LINK after the report queue drains. Press Enter to power-cycle and start."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "cmd5_latch_clear_0096",
|
||||||
|
"frame": "05 01 16 00 00 48",
|
||||||
|
"listen": 0.02
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack_until_quiet",
|
||||||
|
"seconds": 28.00,
|
||||||
|
"quiet_seconds": 0.90,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.005,
|
||||||
|
"post_ack_read": 0.035,
|
||||||
|
"poll_interval": 0.003,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 180,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "recover_connect_ok_seed_before_press",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "QUEUE IS QUIET. PRESS IRIS/M.BLACK LINK ONCE NOW; fresh-report ACK window is running.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack_until_quiet",
|
||||||
|
"seconds": 14.00,
|
||||||
|
"quiet_seconds": 1.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.005,
|
||||||
|
"post_ack_read": 0.035,
|
||||||
|
"poll_interval": 0.003,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 64,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "recover_connect_ok_seed_before_second_press",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS IRIS/M.BLACK LINK A SECOND TIME NOW to test the opposite toggle state.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack_until_quiet",
|
||||||
|
"seconds": 14.00,
|
||||||
|
"quiet_seconds": 1.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.005,
|
||||||
|
"post_ack_read": 0.035,
|
||||||
|
"poll_interval": 0.003,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 64,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "repeat_last_for_hidden_report_check",
|
||||||
|
"frame": "07 00 00 00 00 5D",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
125
scenarios/iris-mblack-link-report-press.json
Normal file
125
scenarios/iris-mblack-link-report-press.json
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"name": "iris-mblack-link-report-press",
|
||||||
|
"notes": [
|
||||||
|
"Focused queued-report test for the IRIS/M.BLACK LINK physical button.",
|
||||||
|
"The ROM path at H'200E toggles E800[0x0013].14 when F006.7/F6DB.7 is active and F731 <= 3, then calls loc_3E54 with R2=0x80 R3=0x0013.",
|
||||||
|
"This scenario services the local report queue by ACKing checksum-valid 00/01/02 report frames with command 5 for the observed selector.",
|
||||||
|
"Expected selector-0013 report shapes include 00 00 13 40 00 09 for active, 00 00 13 00 00 49 for clear, and bit15 variants 00 00 13 80 00 C9 / 00 00 13 C0 00 89. Queued 01/02 variants are also valid evidence."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "prompt",
|
||||||
|
"message": "Prepare to test the IRIS/M.BLACK LINK button. Press Enter to power-cycle and start the live ACK windows."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_connect_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "cmd5_latch_clear_0096",
|
||||||
|
"frame": "05 01 16 00 00 48",
|
||||||
|
"listen": 0.05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 3.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 48,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "recover_connect_ok_seed",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 1.20,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 32,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS IRIS/M.BLACK LINK ONCE NOW; live queued-report ACK window is running.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 10.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 96,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "refresh_connect_ok_seed_before_second_press",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "note",
|
||||||
|
"message": "PRESS IRIS/M.BLACK LINK A SECOND TIME NOW to test the toggle-clear report.",
|
||||||
|
"banner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen_ack",
|
||||||
|
"seconds": 10.00,
|
||||||
|
"target_mode": "queued_reports",
|
||||||
|
"ack_mode": "cmd5_selector",
|
||||||
|
"ack_guard": 0.010,
|
||||||
|
"post_ack_read": 0.070,
|
||||||
|
"poll_interval": 0.004,
|
||||||
|
"once_per_frame": false,
|
||||||
|
"limit_scope": "local",
|
||||||
|
"max_acks": 96,
|
||||||
|
"abort_on_limit": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "repeat_last_for_hidden_report_check",
|
||||||
|
"frame": "07 00 00 00 00 5D",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
100
scenarios/knee-detail-physical-button-watch.json
Normal file
100
scenarios/knee-detail-physical-button-watch.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"name": "knee-detail-physical-button-watch",
|
||||||
|
"notes": [
|
||||||
|
"Create clean watch windows for the physical DETAIL and KNEE buttons near the LCD.",
|
||||||
|
"The ROM trace suggests these buttons may be the missing F104 panel-input transition into loc_1795.",
|
||||||
|
"Watch the console labels and LCD. During the labeled windows, press DETAIL, KNEE, then DETAIL/KNEE alternately."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "prepare_clear_0110",
|
||||||
|
"frame": "00 01 90 00 00 CB",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "prepare_00b9_bit13_gate",
|
||||||
|
"frame": "00 01 39 20 00 42",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 5.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "read_00bc_after_detail_press_window",
|
||||||
|
"frame": "01 01 3C 00 00 66",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "refresh_00b9_bit13_gate_for_knee_press",
|
||||||
|
"frame": "00 01 39 20 00 42",
|
||||||
|
"listen": 0.50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 5.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "read_00bc_after_knee_press_window",
|
||||||
|
"frame": "01 01 3C 00 00 66",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "prepare_00b9_a0_for_page_label_watch",
|
||||||
|
"frame": "00 01 39 A0 00 C2",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 6.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "prepare_0110_dl_override_then_press_buttons",
|
||||||
|
"frame": "00 01 90 80 00 4B",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 6.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "final_read_00bc",
|
||||||
|
"frame": "01 01 3C 00 00 66",
|
||||||
|
"listen": 0.80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
206
scenarios/knee-rom-dtl-knee-isolation.json
Normal file
206
scenarios/knee-rom-dtl-knee-isolation.json
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
{
|
||||||
|
"name": "knee-rom-dtl-knee-isolation",
|
||||||
|
"notes": [
|
||||||
|
"Isolate which ROM-derived KNEE selector sequence creates the bench DTL/KNEE LCD state.",
|
||||||
|
"Record the exact case label visible in the console when DTL/KNEE appears or disappears.",
|
||||||
|
"Cases separate 0x00B9.13 gate, 0x00B9.15 label bit, and 0x0110.15 timed override."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_a_ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_a_ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_a_clear_00b9",
|
||||||
|
"frame": "00 01 39 00 00 62",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_a_clear_0110",
|
||||||
|
"frame": "00 01 90 00 00 CB",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_a_set_00b9_bit13_only_watch_dtl_knee",
|
||||||
|
"frame": "00 01 39 20 00 42",
|
||||||
|
"listen": 3.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_b_ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_b_ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_b_clear_0110",
|
||||||
|
"frame": "00 01 90 00 00 CB",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_b_set_00b9_bits15_and_13_watch_dtl_knee",
|
||||||
|
"frame": "00 01 39 A0 00 C2",
|
||||||
|
"listen": 3.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_c_ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_c_ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_c_clear_00b9",
|
||||||
|
"frame": "00 01 39 00 00 62",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_c_set_0110_bit15_only_watch_dtl_knee",
|
||||||
|
"frame": "00 01 90 80 00 4B",
|
||||||
|
"listen": 3.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_d_ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_d_ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_d_set_00b9_bit13_then_0110_override_part1",
|
||||||
|
"frame": "00 01 39 20 00 42",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_d_set_0110_bit15_after_00b9_bit13_watch_dtl_knee",
|
||||||
|
"frame": "00 01 90 80 00 4B",
|
||||||
|
"listen": 3.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_e_ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_e_ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_e_set_00b9_a0_then_0110_override_part1",
|
||||||
|
"frame": "00 01 39 A0 00 C2",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "case_e_set_0110_bit15_after_00b9_a0_watch_dtl_knee",
|
||||||
|
"frame": "00 01 90 80 00 4B",
|
||||||
|
"listen": 3.00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
132
scenarios/knee-rom-gate-and-value-probe.json
Normal file
132
scenarios/knee-rom-gate-and-value-probe.json
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"name": "knee-rom-gate-and-value-probe",
|
||||||
|
"notes": [
|
||||||
|
"Probe the ROM-derived KNEE model from docs/pt2-knee-rom-trace.md.",
|
||||||
|
"The key correction is that 0x00B9.13 gates the live KNEE value/report branch, while 0x0110.15 forces the timed KNEE page.",
|
||||||
|
"Serial writes may only prepare the state. If the panel has a KNEE-related control, move it during the long gate windows and watch for selector 0x00BC reports or visible KNEE changes."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "clear_00b9",
|
||||||
|
"frame": "00 01 39 00 00 62",
|
||||||
|
"listen": 0.30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "clear_0110",
|
||||||
|
"frame": "00 01 90 00 00 CB",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "read_00bc_baseline",
|
||||||
|
"frame": "01 01 3C 00 00 66",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "set_00b9_bit13_gate_move_knee_control_if_possible",
|
||||||
|
"frame": "00 01 39 20 00 42",
|
||||||
|
"listen": 4.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "read_00bc_after_gate_window",
|
||||||
|
"frame": "01 01 3C 00 00 66",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "set_00b9_bits15_and_13_preset_label_plus_gate",
|
||||||
|
"frame": "00 01 39 A0 00 C2",
|
||||||
|
"listen": 1.50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "set_0110_bit15_dl_timed_page_override",
|
||||||
|
"frame": "00 01 90 80 00 4B",
|
||||||
|
"listen": 2.50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "clear_0110_return_to_value_path",
|
||||||
|
"frame": "00 01 90 00 00 CB",
|
||||||
|
"listen": 1.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "read_00bc_after_override_clear",
|
||||||
|
"frame": "01 01 3C 00 00 66",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "repeat",
|
||||||
|
"count": 2,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "00bc_direct_low_probe",
|
||||||
|
"frame": "00 01 3C 00 00 67",
|
||||||
|
"listen": 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "00bc_direct_mid_probe",
|
||||||
|
"frame": "00 01 3C 40 00 27",
|
||||||
|
"listen": 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "00bc_direct_high_probe",
|
||||||
|
"frame": "00 01 3C 80 00 E7",
|
||||||
|
"listen": 0.35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "read_00bc_direct_probe",
|
||||||
|
"frame": "01 01 3C 00 00 66",
|
||||||
|
"listen": 0.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "final_clear_00b9",
|
||||||
|
"frame": "00 01 39 00 00 62",
|
||||||
|
"listen": 0.50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "final_clear_0110",
|
||||||
|
"frame": "00 01 90 00 00 CB",
|
||||||
|
"listen": 1.50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
160
scenarios/lamp-0093-lowbyte-sweep.json
Normal file
160
scenarios/lamp-0093-lowbyte-sweep.json
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"name": "lamp-0093-lowbyte-sweep",
|
||||||
|
"notes": [
|
||||||
|
"Hold CONNECT OK and vary only the low byte of E000[0x0093] while keeping high byte 0x90.",
|
||||||
|
"Record white-balance AUTO/PRESET/MANUAL, black/flare AUTO/MANUAL/FLARE, iris AUTO, shutter display, and LCD.",
|
||||||
|
"Known references: 0x9020 has behaved like a black/flare manual context; 0x90FF has produced black/flare auto toggles."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "repeat",
|
||||||
|
"count": 2,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9000",
|
||||||
|
"frame": "00 01 13 90 00 D8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9000",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9001",
|
||||||
|
"frame": "00 01 13 90 01 D9",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9001",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9002",
|
||||||
|
"frame": "00 01 13 90 02 DA",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9002",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9004",
|
||||||
|
"frame": "00 01 13 90 04 DC",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9004",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9008",
|
||||||
|
"frame": "00 01 13 90 08 D0",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9008",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9010",
|
||||||
|
"frame": "00 01 13 90 10 C8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9010",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9040",
|
||||||
|
"frame": "00 01 13 90 40 98",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9040",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "candidate_0093_9080",
|
||||||
|
"frame": "00 01 13 90 80 58",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_9080",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "positive_0093_90ff",
|
||||||
|
"frame": "00 01 13 90 FF 27",
|
||||||
|
"listen": 0.55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "baseline_0093_9020_after_90ff",
|
||||||
|
"frame": "00 01 13 90 20 F8",
|
||||||
|
"listen": 0.55
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 0.80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
184
scenarios/lamp-broad-status-selector-sweep.json
Normal file
184
scenarios/lamp-broad-status-selector-sweep.json
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
{
|
||||||
|
"name": "lamp-broad-status-selector-sweep",
|
||||||
|
"notes": [
|
||||||
|
"Cautious broader primary-table sweep for ROM-mined status selectors that may drive lamps/readouts.",
|
||||||
|
"Each selector is tested with 0x8000 then 0xFFFF, followed by 0x0000 clear. Record visible lamp/readout changes inside each short window.",
|
||||||
|
"If the panel enters CONNECT NOT ACT, stop the run and note the selector label immediately before the transition."
|
||||||
|
],
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"action": "power_cycle",
|
||||||
|
"off_seconds": 1.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "wait_ready",
|
||||||
|
"heartbeats": 2,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "drain",
|
||||||
|
"seconds": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_ok_seed_1",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_zero_ok_seed_2",
|
||||||
|
"frame": "00 00 00 80 00 DA",
|
||||||
|
"listen": 0.60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0003_default_high",
|
||||||
|
"frame": "00 00 03 80 00 D9",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0003_all_bits",
|
||||||
|
"frame": "00 00 03 FF FF 59",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0003_clear",
|
||||||
|
"frame": "00 00 03 00 00 59",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0040_high",
|
||||||
|
"frame": "00 00 40 80 00 9A",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0040_all_bits",
|
||||||
|
"frame": "00 00 40 FF FF 1A",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0040_clear",
|
||||||
|
"frame": "00 00 40 00 00 1A",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0081_high",
|
||||||
|
"frame": "00 01 01 80 00 DA",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0081_all_bits",
|
||||||
|
"frame": "00 01 01 FF FF 5A",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0081_clear",
|
||||||
|
"frame": "00 01 01 00 00 5A",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0092_high",
|
||||||
|
"frame": "00 01 12 80 00 C9",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0092_all_bits",
|
||||||
|
"frame": "00 01 12 FF FF 49",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0092_clear",
|
||||||
|
"frame": "00 01 12 00 00 49",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00a7_high",
|
||||||
|
"frame": "00 01 27 80 00 FC",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00a7_all_bits",
|
||||||
|
"frame": "00 01 27 FF FF 7C",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00a7_clear",
|
||||||
|
"frame": "00 01 27 00 00 7C",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00b7_high",
|
||||||
|
"frame": "00 01 37 80 00 EC",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00b7_all_bits",
|
||||||
|
"frame": "00 01 37 FF FF 6C",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00b7_clear",
|
||||||
|
"frame": "00 01 37 00 00 6C",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00b9_high",
|
||||||
|
"frame": "00 01 39 80 00 E2",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00b9_all_bits",
|
||||||
|
"frame": "00 01 39 FF FF 62",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_00b9_clear",
|
||||||
|
"frame": "00 01 39 00 00 62",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0110_high",
|
||||||
|
"frame": "00 01 90 80 00 4B",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0110_all_bits",
|
||||||
|
"frame": "00 01 90 FF FF CB",
|
||||||
|
"listen": 0.80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "send",
|
||||||
|
"label": "selector_0110_clear",
|
||||||
|
"frame": "00 01 90 00 00 CB",
|
||||||
|
"listen": 0.40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "listen",
|
||||||
|
"seconds": 0.80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user