1
0
Files
h8-536-decoder/scripts/build_rom_button_output_sweep.py
2026-05-27 21:37:50 +10:00

168 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""Build a fresh-boot webcam sweep for ROM-derived panel-output candidates."""
from __future__ import annotations
import argparse
import json
from dataclasses import dataclass
from pathlib import Path
CHECKSUM_SEED = 0x5A
CONNECT_OK_FRAME = "00 00 00 80 00 DA"
@dataclass(frozen=True)
class Candidate:
label: str
selector: int
value: int
note: str
CANDIDATES: tuple[Candidate, ...] = (
Candidate("positive_0013_4000_iris_mblack_link", 0x0013, 0x4000, "known IRIS/M.BLACK LINK lamp positive control"),
Candidate("positive_0013_8000_slave", 0x0013, 0x8000, "known SLAVE lamp positive control"),
Candidate("positive_0015_8000_call", 0x0015, 0x8000, "known CALL lamp positive control"),
Candidate("positive_0017_8000_bars", 0x0017, 0x8000, "known BARS lamp positive control"),
Candidate("positive_0110_8000_knee_auto", 0x0110, 0x8000, "known KNEE AUTO positive control"),
Candidate("rom_001a_0808_multi_button_default", 0x001A, 0x0808, "F6D3 group default/fallback value"),
Candidate("rom_001a_2020_f6d3_bit3_family", 0x001A, 0x2020, "F6D3.3-style packed state candidate"),
Candidate("rom_001a_4040_f6d3_bit4_family", 0x001A, 0x4040, "F6D3.4-style packed state candidate"),
Candidate("rom_001a_8080_f6d3_bit5_family", 0x001A, 0x8080, "F6D3.5-style packed state candidate"),
Candidate("rom_006b_8000_f6d4_bit6_candidate", 0x006B, 0x8000, "F6D4.6 handler report value"),
Candidate("rom_0083_0004_f6d0_step_candidate", 0x0083, 0x0004, "F6D0.1 lower-step value candidate"),
Candidate("rom_0083_4000_high_tag_candidate", 0x0083, 0x4000, "0x0083 high-bit/tag candidate"),
Candidate("rom_0083_2000_high_tag_candidate", 0x0083, 0x2000, "0x0083 high-bit/tag candidate"),
Candidate("rom_008f_8000_f6d0_bit7_local", 0x008F, 0x8000, "F6D0.7 local SHUTTER/OTHERS report bit"),
Candidate("rom_008f_2000_f6d0_bit6_local", 0x008F, 0x2000, "F6D0.6 local SHUTTER/OTHERS report bit"),
Candidate("known_008f_0800_evs_display", 0x008F, 0x0800, "known EVS/shutter display positive control"),
Candidate("known_008f_1000_off_display", 0x008F, 0x1000, "known OFF/shutter display positive control"),
Candidate("rom_0093_1020_f6dc_bit5_context", 0x0093, 0x1020, "F6DC.5 handler context candidate"),
Candidate("rom_0093_4040_f6dc_bit4_context", 0x0093, 0x4040, "F6DC.4 handler context candidate"),
Candidate("rom_0093_8040_f6dc_bit3_context", 0x0093, 0x8040, "F6DC.3 handler context candidate"),
Candidate("rom_0093_0020_f6dc_bit1_context", 0x0093, 0x0020, "F6DC.1 handler low-field candidate"),
Candidate("rom_0093_0040_f6dc_bit0_context", 0x0093, 0x0040, "F6DC.0 handler low-field candidate"),
Candidate("rom_009a_0800_iris_auto_candidate", 0x009A, 0x0800, "F6DB.3 IRIS AUTO report candidate"),
Candidate("rom_00b7_2000_f6d4_bit0_bundle", 0x00B7, 0x2000, "F6D4.0 bundle selector candidate"),
Candidate("rom_00b9_4000_f6dc_bit7_candidate", 0x00B9, 0x4000, "F6DC.7 handler value candidate"),
Candidate("rom_00c4_8000_f6d4_bit0_bundle", 0x00C4, 0x8000, "F6D4.0 bundle selector candidate"),
Candidate("rom_00c6_8000_f6d4_bit0_bundle", 0x00C6, 0x8000, "F6D4.0 bundle selector candidate"),
Candidate("rom_00f8_8000_f6d4_bit1_candidate", 0x00F8, 0x8000, "F6D4.1 handler candidate"),
)
def main() -> int:
args = build_arg_parser().parse_args()
scenario = build_scenario(args)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(scenario, indent=2) + "\n", encoding="utf-8")
print(f"wrote {args.output}")
print(f"candidates={len(CANDIDATES)} snapshots={len(CANDIDATES)}")
print(f"estimated_hold_time={len(CANDIDATES) * args.listen / 60:.1f}min plus power-cycle/ready time")
return 0
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"output",
nargs="?",
type=Path,
default=Path("scenarios/panel-atlas-rom-button-output-candidates-v1.json"),
)
parser.add_argument("--listen", type=float, default=0.75, help="seconds to listen after each candidate send")
parser.add_argument("--ok-listen", type=float, default=0.25, help="seconds to listen after CONNECT OK seeds")
parser.add_argument("--drain", type=float, default=0.25, help="seconds to drain after ready")
parser.add_argument("--off-seconds", type=float, default=1.5, help="relay power-off time before each candidate")
parser.add_argument("--ready-heartbeats", type=int, default=2, help="heartbeats before each candidate")
parser.add_argument("--ready-timeout", type=float, default=10.0, help="ready wait timeout")
return parser
def build_scenario(args: argparse.Namespace) -> dict[str, object]:
steps: list[dict[str, object]] = []
for index, candidate in enumerate(CANDIDATES, start=1):
steps.extend(candidate_steps(args, index, candidate))
steps.append({"action": "listen", "seconds": 0.8})
return {
"name": "panel-atlas-rom-button-output-candidates-v1",
"notes": [
"Fresh-boot webcam sweep for ROM-derived button/report output candidates.",
"This deliberately skips physical RCP button presses: each candidate sends command 0 directly.",
"Each candidate gets its own power-cycle/CONNECT-OK baseline to reduce latch contamination.",
"Candidate snapshots only are enabled; setup, CONNECT OK seeds, and clears should not produce webcam images.",
"Run with --camera-index 4 --snapshot-delays 0.5 on the current bench.",
],
"steps": steps,
}
def candidate_steps(args: argparse.Namespace, index: int, candidate: Candidate) -> list[dict[str, object]]:
label_base = f"case{index:03d}_{candidate.label}"
return [
{"action": "power_cycle", "off_seconds": args.off_seconds},
{
"action": "wait_ready",
"heartbeats": args.ready_heartbeats,
"timeout": args.ready_timeout,
"require": True,
},
{"action": "drain", "seconds": args.drain},
send_step(f"{label_base}_ok_seed_1", CONNECT_OK_FRAME, args.ok_listen, snapshot=False),
send_step(f"{label_base}_ok_seed_2", CONNECT_OK_FRAME, args.ok_listen, snapshot=False),
{
"action": "note",
"message": (
f"{label_base}: selector 0x{candidate.selector:04X}=0x{candidate.value:04X}; "
f"{candidate.note}"
),
},
send_step(label_base, frame_hex(0x00, candidate.selector, candidate.value), args.listen, snapshot=True),
send_step(
f"{label_base}_clear",
frame_hex(0x00, candidate.selector, 0x0000),
0.12,
snapshot=False,
),
]
def send_step(label: str, frame: str, listen: float, *, snapshot: bool) -> dict[str, object]:
step: dict[str, object] = {
"action": "send",
"label": label,
"frame": frame,
"listen": listen,
}
if not snapshot:
step["snapshot"] = False
return step
def frame_hex(command: int, selector: int, value: int) -> str:
selector_hi, selector_lo = selector_bytes(selector)
data = bytes([command & 0xFF, selector_hi, selector_lo, (value >> 8) & 0xFF, value & 0xFF])
return " ".join(f"{byte:02X}" for byte in data + bytes([frame_checksum(data)]))
def selector_bytes(selector: int) -> tuple[int, int]:
selector &= 0x01FF
if selector <= 0x007F:
return 0x00, selector
if selector <= 0x017F:
return 0x01, selector - 0x0080
return 0x02, selector - 0x0180
def frame_checksum(data: bytes) -> int:
value = CHECKSUM_SEED
for byte in data[:5]:
value ^= byte & 0xFF
return value & 0xFF
if __name__ == "__main__":
raise SystemExit(main())