diff --git a/captures/rcp-buttons-call-45-followup-discovery-b5.txt b/captures/rcp-buttons-call-45-followup-discovery-b5.txt new file mode 100644 index 0000000..873330b --- /dev/null +++ b/captures/rcp-buttons-call-45-followup-discovery-b5.txt @@ -0,0 +1,39 @@ +Button test on COM5 at 38400 8N1 +Listening for 15.0s; respond_to_cam_power=False, respond_to_call=True, mirror_call=False +16:48:44.407 RX 001 bytes 00 +16:48:44.439 RX 005 bytes 00 00 00 80 DA +16:48:45.016 RX 006 bytes 00 00 15 80 00 CF +16:48:45.016 DETECT call-on x1 +16:48:45.068 TX button response frame 006 00 00 15 80 00 CF +16:48:45.118 TX button response frame 006 00 00 15 00 00 4F +16:48:45.149 RX 006 bytes 07 80 45 20 D0 68 +16:48:45.149 DETECT watch-frame 07 80 45 20 D0 68 x1 +16:48:45.200 TX watch follow-up frame 006 00 00 00 00 80 DA +16:48:45.202 TX watch follow-up frame 006 00 00 B5 00 80 6F +16:48:47.216 RX 001 bytes 00 +16:48:47.246 RX 005 bytes 00 00 00 80 DA +16:48:47.916 RX 001 bytes 00 +16:48:47.946 RX 005 bytes 00 00 00 80 DA +16:48:48.619 RX 001 bytes 00 +16:48:48.650 RX 005 bytes 00 00 00 80 DA +16:48:49.323 RX 006 bytes 00 00 00 00 80 DA +16:48:49.323 DETECT heartbeat x1 +16:48:50.024 RX 006 bytes 00 00 00 00 80 DA +16:48:50.024 DETECT heartbeat x1 +16:48:50.725 RX 006 bytes 00 00 00 00 80 DA +16:48:50.725 DETECT heartbeat x1 +16:48:51.429 RX 006 bytes 00 00 00 00 80 DA +16:48:51.429 DETECT heartbeat x1 +16:48:52.130 RX 006 bytes 00 00 00 00 80 DA +16:48:52.130 DETECT heartbeat x1 +16:48:52.831 RX 006 bytes 00 00 00 00 80 DA +16:48:52.831 DETECT heartbeat x1 +16:48:53.530 RX 006 bytes 00 00 00 00 80 DA +16:48:53.530 DETECT heartbeat x1 +16:48:54.233 RX 006 bytes 00 00 00 00 80 DA +16:48:54.233 DETECT heartbeat x1 +16:48:54.935 RX 006 bytes 00 00 00 00 80 DA +16:48:54.935 DETECT heartbeat x1 +16:48:55.607 RX 001 bytes 00 +16:48:55.637 RX 005 bytes 00 00 00 80 DA +Stopped. diff --git a/docs/discovery-notes.md b/docs/discovery-notes.md index 9693109..b3d2565 100644 --- a/docs/discovery-notes.md +++ b/docs/discovery-notes.md @@ -3196,3 +3196,83 @@ python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt If this produces the known `B5` response `07 80 6D 20 D8 48`, then the CALL path does not consume the one-shot discovery response. If it returns heartbeat only, CALL/`0x45` may put the RCP into a similar one-shot consumed state. + +### 2026-05-13 CALL `0x45` Then Discovery Result + +Capture: + +- `captures/rcp-buttons-call-45-followup-discovery-b5.txt` + +Observed sequence: + +```text +RCP CALL high: 00 00 15 80 00 CF +Host CALL high echo: 00 00 15 80 00 CF +Host CALL low echo: 00 00 15 00 00 4F +RCP CALL response: 07 80 45 20 D0 68 +Host primer: 00 00 00 00 80 DA +Host B5 query: 00 00 B5 00 80 6F +``` + +Result: + +- After the follow-up `00 -> B5` query, the RCP returned heartbeat-compatible + traffic only. +- The known `B5` response `07 80 6D 20 D8 48` did not appear. + +Interpretation: + +- The CALL/`0x45` path does not unlock the known discovery query. +- It may consume or bypass the same cold one-shot discovery window, or the RCP + may simply ignore discovery-style queries once the CALL event path has been + exercised. +- This pushes the CALL path into the "useful diagnostic but probably not the + activation handshake" bucket. + +### Cold No-Button CALL Injection Tests + +Question: have we tried sending the CALL response frames without first pressing +the `CALL` button? + +Answer: partially, but not in the exact form that now matters. + +- Earlier command `0x15` matrix tests sent individual `0x15` frames from a cold + panel and saw `CONNECT NOT ACT`, but no non-heartbeat serial response. +- The newer reproducible `0x45` result depends on sending the CALL-high and + CALL-low frames as a pair with a gap. That exact cold/no-button pair has not + been tested yet. + +Tooling note: + +- `scripts/serial_button_response_test.py` now supports `--startup-frame`. These + frames are sent automatically after the listen window begins, without waiting + for a physical button event. + +Test C1: cold CALL pair, 50 ms gap, no physical button press. + +```powershell +python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-pair-gap-50ms.txt +``` + +Test C2: cold CALL pair, 80 ms gap, no physical button press. + +```powershell +python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame-interval 0.08 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-pair-gap-80ms.txt +``` + +Test C3: cold CALL high only, no physical button press. + +```powershell +python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame "00 00 15 80 00 CF" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-high-only.txt +``` + +Test C4: cold CALL low only, no physical button press. + +```powershell +python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-low-only.txt +``` + +For each test, power-cycle first and do not press any panel buttons. If C1/C2 +produce `0x45`, the host can synthesize the CALL event path. If they do not, +the RCP's own physical CALL transition is required before the echo pair has +meaning. diff --git a/scripts/__pycache__/serial_button_response_test.cpython-312.pyc b/scripts/__pycache__/serial_button_response_test.cpython-312.pyc index 80bc613..d3cb795 100644 Binary files a/scripts/__pycache__/serial_button_response_test.cpython-312.pyc and b/scripts/__pycache__/serial_button_response_test.cpython-312.pyc differ diff --git a/scripts/serial_button_response_test.py b/scripts/serial_button_response_test.py index 2484fa2..72b8446 100644 --- a/scripts/serial_button_response_test.py +++ b/scripts/serial_button_response_test.py @@ -8,6 +8,7 @@ This helper can: 3. Optionally transmit response frames when CAM POWER or CALL is observed. 4. Optionally mirror CALL high/low events with matching host responses. 5. Optionally transmit follow-up frames when a watched response frame appears. +6. Optionally transmit startup frames without waiting for a button event. Known RCP-origin button frames: @@ -175,6 +176,19 @@ def parse_args() -> argparse.Namespace: help="hex frame to send after a watched frame appears; can be repeated", ) parser.add_argument("--followup-delay", type=float, default=0.05) + parser.add_argument( + "--startup-frame", + type=parse_hex_bytes, + action="append", + help="hex frame to send after listening starts, without waiting for a button event", + ) + parser.add_argument("--startup-delay", type=float, default=0.5) + parser.add_argument( + "--startup-frame-interval", + type=float, + default=0.05, + help="delay between multiple startup frames", + ) parser.add_argument("--response-delay", type=float, default=0.05) parser.add_argument( "--response-frame-interval", @@ -238,7 +252,10 @@ def main() -> int: emit_known_counts(emit, "LATCH QUERY", read_window(ser, args.after_latch)) if args.prompt: - input("Ready to listen. Press Enter, then press CAM POWER/CALL on the RCP: ") + if args.startup_frame: + input("Ready to listen. Press Enter; startup frames will be sent automatically: ") + else: + input("Ready to listen. Press Enter, then press CAM POWER/CALL on the RCP: ") ser.reset_input_buffer() emit( @@ -249,6 +266,13 @@ def main() -> int: ) stop_at = time.monotonic() + args.duration buffer = bytearray() + if args.startup_frame: + time.sleep(args.startup_delay) + for frame_index, frame in enumerate(args.startup_frame): + if frame_index: + time.sleep(args.startup_frame_interval) + send_frame(ser, emit, "startup", frame) + while time.monotonic() < stop_at: data = ser.read(args.chunk_size) if not data: