From 90dba1c81de86a4fb807f2a81215b4964a43e07a Mon Sep 17 00:00:00 2001 From: mikecarper Date: Fri, 23 Oct 2026 00:54:59 -0700 Subject: [PATCH] Record RAK3401 mixed-PHY radio2 preamble384 test --- ...ak3401_radio2_sf5_bw500_p384_2026-10-23.md | 115 +++++ ...radio2_sf5_bw500_p384_2026-10-23.safe.json | 434 ++++++++++++++++++ 2 files changed, 549 insertions(+) create mode 100644 docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.md create mode 100644 docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.safe.json diff --git a/docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.md b/docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.md new file mode 100644 index 000000000..6d4c4ea12 --- /dev/null +++ b/docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.md @@ -0,0 +1,115 @@ + + + + +# RAK3401 radio2 SF5/BW500/preamble384, 23 October 2026 + +Status: **COMPLETE, VALID MEASUREMENT, DELIVERY FAIL.** Radio2 received +**100/100** packets at SF5/BW500/preamble384, but the same scanning RAK missed +**3/100 primary packets** at SF7/BW62.5/preamble32. Both fixed receivers +received all their applicable packets. No firmware was changed or flashed. +The [sanitized receipt](rak3401_radio2_sf5_bw500_p384_2026-10-23.safe.json) +binds the counters, settings, independent audit and debugger capture hashes. + +## Physical setup and scope + +| Role | Installed firmware source | Frequency MHz | BW kHz | SF | CR | Preamble | +| --- | --- | ---: | ---: | ---: | ---: | ---: | +| RAK3401 Full unified Repeater, primary scanner | `adb11e7b` | 917.000 | 62.5 | 7 | 5 | 32 | +| Same RAK3401, secondary scanner, RX mode | `adb11e7b` | 918.000 | 500 | 5 | 5 | 384 | +| RAK WisMesh Tag Companion, sender | `08650f64` | Both tuples, one at a time | Both | Both | 5 | Both | +| T1000-E Companion, fixed primary witness | `08650f64` | 917.000 | 62.5 | 7 | 5 | 32 | +| Heltec T096 Companion, fixed secondary witness | `4773ac42` | 918.000 | 500 | 5 | 5 | 384 | + +The sender used 2 dBm and sent 200 fresh 92-byte RAW_CUSTOM payloads, each +94 bytes on air including its direct, zero-hop route header. Ten seeded +pairs of ten-packet batches produced 100 packets per profile. Profile order +within each pair was shuffled. These are successive transmissions from one +sender, not concurrent traffic on two frequencies. The requested minimum gap +after physical TX_DONE was randomly 0.5-0.9 seconds; retunes increased actual +gaps to at most 3.416 seconds. The RF window lasted 201.569 seconds. +Only 917.000 and 918.000 MHz were commanded for the measured packet set. + +Every transmission required one acceptance and exactly one advancing direct +physical TX counter. There were no retries, replacement packets or floods. +The scanner and both witnesses observed the same physical packet set. +RAK USB logging matched full raw packet bytes; Companion witnesses matched +both custom-payload pushes and independent raw-frame pushes. These two +Companion observations count as one delivery. No channel or contact changes +were needed. This test does not qualify message decryption or forwarding. + +## Results + +| Receiver | Primary received / 100 | Secondary received / 100 | Result | +| --- | ---: | ---: | --- | +| RAK3401 scanner | 97 | 100 | FAIL primary; PASS secondary | +| T1000-E fixed witness | 100 | Not applicable | PASS | +| T096 fixed witness | Not applicable | 100 | PASS | + +The missing primary packets were **P022, P044 and P070**, corresponding to +global zero-based ordinals **51, 93 and 139**. Every one reached the primary +witness. There were no duplicates, opposite-profile witness deliveries, log +drops, framing losses or collector errors. All three receivers transmitted +zero packets during the RF measurement. All four radios accumulated zero new +receive/CRC errors. Thus the three scanner misses are supported by independent +same-packet witness evidence; their cause is not established by correlation. + +RAK, Tag and T096 already had historical core flag `0x08` before this test; +T1000-E had zero. Those values were unchanged throughout the completed run. +No error flags were cleared. RAK soft/hard radio watchdog counters remained +`0/0`, queues were empty and uptimes remained continuous. This is not a claim +that the radios had zero historical errors. + +## Switch timing and CPU observation + +For the current pair, the firmware reported a **1,176 us guarded switch +budget**. This is the 1,069 us sampled maximum plus a rounded-up 10% margin. +nRF52 dual-profile timing uses RTC2 at 32,768 Hz, approximately 30.52 us per +tick. This measures the guarded RX-to-RX path, including activity checks, +standby/FS transition, frequency and changed modem/preamble parameters, and +restarting receive. It is not a bare PLL retune measurement or an average. + +The derived visits were `9421,20995` us and the recommended preambles were +`32,200`. The requested radio2 preamble384 exceeds that estimate. Its 100/100 +delivery does not explain the three primary misses despite primary meeting +the displayed recommendation. The separately displayed lifetime maximum +`3906us` is not reset with the pair's timing self-test and cannot be assigned +to a particular lost packet from these counters. + +The attached debugger completed a **300.008-second nonhalting memory-AP +capture with 27,118 samples**. Before/after hardware identity matched. No +debug enable, halt, stepping, interrupt masking, lockup or reset was observed; +capture integrity passed. SWD did not set breakpoints, stop the CPU or flash +anything. It sampled CPU status, not per-hop instruction traces. + +Only eight samples observed sleep, a weighted sampled fraction of **0.0307%**. +This is not a CPU sleep qualification: Full Repeater deliberately inhibits +CPU power saving during an active USB data session, which was needed here +for exact RF logging. A saved `powersaving on` preference does not bypass +that gate. The existing nRF52 scanner idle path requires more than 48 RTC2 +ticks, approximately 1.465 ms, remaining before it admits a task delay; the +roughly 2 ms practical idle-budget consideration is separate from retune +time. An unplugged-USB, SWD-only sleep experiment would be a separate test. + +Source inspection found concrete speed candidates, without changing this +baseline: RAK currently uses the default 2 MHz byte-at-a-time SPI path, +although a buffered nRF52 HAL exists; the retune path also performs modem +queries during setup and maximum-airtime calculation. Their individual costs +were not measured by this capture. Faster switching and any receiver repair +need fresh source-bound RF validation before a qualification claim. + +## Controller correction and restoration + +An earlier, separate attempt stopped after 50 transmissions because its +fixed 2.1-second wait ended while the sender still reported pending settings. +Those packets are excluded from every final count. Its settings and gateway +were restored. The corrected private controller polls for the exact active +tuple and preamble before any next transmission, retains partial evidence +on abort, and waits for live radio2 shutdown during cleanup. The firmware +was unchanged. The completed run used fresh nonces and a new receipt. + +All four nodes passed final settings preservation. Temporary radio leases +were removed, RAK live radio2 status returned to off, original TX power and +USB logging were restored, and the gateway service reacquired its radio with +its configuration unchanged. The independent offline audit reproduced the +three missing primary packets and reported no measurement confounds. diff --git a/docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.safe.json b/docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.safe.json new file mode 100644 index 000000000..decbe6cbe --- /dev/null +++ b/docs/test-results/rak3401_radio2_sf5_bw500_p384_2026-10-23.safe.json @@ -0,0 +1,434 @@ +{ + "schema": 1, + "date": "2026-10-23", + "test": "RAK3401 radio2 SF5 BW500 preamble384 physical receive test", + "passed": false, + "complete": true, + "delivery_passed": false, + "firmware_writes": 0, + "packet_retries": 0, + "physical_packet_bytes": 94, + "application_payload_bytes": 92, + "frequency_profiles": { + "primary": { + "frequency_mhz": 917, + "bandwidth_khz": 62.5, + "sf": 7, + "cr": 5, + "preamble_symbols": 32 + }, + "secondary": { + "frequency_mhz": 918, + "bandwidth_khz": 500, + "sf": 5, + "cr": 5, + "preamble_symbols": 384 + } + }, + "sender_power_dbm": 2, + "traffic_pattern": "20 batches of10; ten batches/profile; seeded pair order; no per-packet interleaving claim", + "transmissions": 200, + "rf_duration_seconds": 201.56887236999864, + "gap_seconds": { + "minimum": 0.5018097600004694, + "maximum": 3.4158496399995784 + }, + "evidence": { + "private_report_sha256": "a6f39391895ca1ec4c163b446c83e43cb2ca84f1098a7f22efddd67eebe88dd2", + "controller_sha256": "a172870af04a36c0186f8d9289ca52f0c2bc416da26fc54decc553d940045299", + "debugger_log_sha256": "13545ca7ab5ac3b3934760c5452aff2919f42ef52916f1002c028b473774b12e" + }, + "nodes": { + "rak": { + "board": "RAK 3401", + "firmware_version": "v1.18.0.1-halo-keymind-cascade-dev-adb11e7b (Build: 23-Oct-2026)", + "power_and_logging": { + "get powersaving": "on", + "get radio.rxps.config": "off,level=0,preamble=0,rx=65625,sleep=60000", + "get usb.logging": "on" + }, + "core_before": { + "battery_mv": 4248, + "uptime_secs": 5423, + "errors": 8, + "queue_len": 0 + }, + "core_after": { + "battery_mv": 4182, + "uptime_secs": 5630, + "errors": 8, + "queue_len": 0 + }, + "packet_deltas": { + "sent": 0, + "sent_direct": 0, + "sent_flood": 0, + "recv": 197, + "recv_errors": 0 + }, + "rf_packet_counters_before": { + "sent": 24, + "sent_direct": 1, + "sent_flood": 23, + "recv": 73, + "recv_errors": 0 + }, + "rf_packet_counters_after": { + "sent": 24, + "sent_direct": 1, + "sent_flood": 23, + "recv": 270, + "recv_errors": 0 + }, + "configured": { + "get tempradio": "917.000,62.500,7,5,32", + "get tempradio2": "918.000,500.000,5,5,rx,384; 0d0h13m (13 min) left; WARN recommended preamble: radio2=200; switch=1176us", + "get radio2.status": "rx; RX=35,38 TX=24,0 switches=18181 errors=0 max=3906us preamble=32,384; test=4/4,4/4 ready budget=1176us; WARN recommended preamble: radio2=200", + "get radio2.scan": "slow=radio; listen_us=9421,20995; preamble=32,384; WARN recommended preamble: radio2=200; switch=1176us", + "get radio2.timing": "chirps=4.60,328.05; need=32,200; switch=1176us; loop=0us; test=4/4,4/4 ready; WARN recommended preamble: radio2=200" + }, + "radio_diagnostics_before_configuration": "state=1(RX), recv=61, sent=12, errors=0, err_flags=8, outbound=no, last_rx=147s ago", + "radio_diagnostics_after": "state=1(RX), recv=270, sent=24, errors=0, err_flags=8, outbound=no, last_rx=5s ago", + "watchdog_before": "soft=0,hard=0", + "watchdog_after": "soft=0,hard=0", + "restoration_passed": true, + "restored_live_radio2_status": "off; RX=132,138 TX=24,0 switches=35454 errors=0 max=3906us preamble=32,32", + "collection": { + "expected": 200, + "received": 197, + "missing_ordinals": [ + 51, + 93, + 139 + ], + "duplicates": 0, + "wrong_profile_ordinals": [], + "drops": [], + "errors": [], + "framing": { + "ports_opened": 0, + "ports_closed": 0, + "request_frames_sent": 0, + "response_frames_received": 0, + "request_bytes_sent": 0, + "response_payload_bytes": 0, + "discarded_prefix_bytes": 0, + "invalid_length_candidates": 0, + "ignored_push_frames": 0 + } + }, + "profile_results": { + "primary": { + "expected": 100, + "received": 97, + "missing_ordinals": [ + 51, + 93, + 139 + ] + }, + "secondary": { + "expected": 100, + "received": 100, + "missing_ordinals": [] + } + } + }, + "sender": { + "board": "RAK WisMesh Tag", + "firmware_version": "v1.18.0.1-halo-keymind-cascade-dev-08650f64 (Build: 20-Oct-2026)", + "power_and_logging": { + "get powersaving": "on", + "get radio.rxps.config": "on,level=8,preamble=16,rx=39657,sleep=15975", + "get usb.logging": "usb.logging off; port: dedicated USB CDC 1, interface 02 (Linux: *-if02; tty/COM name is host-assigned)" + }, + "core_before": { + "battery_mv": 4121, + "uptime_secs": 11971, + "errors": 8, + "queue_len": 0 + }, + "core_after": { + "battery_mv": 4122, + "uptime_secs": 12177, + "errors": 8, + "queue_len": 0 + }, + "packet_deltas": { + "sent": 200, + "sent_direct": 200, + "sent_flood": 0, + "recv": 0, + "recv_errors": 0 + }, + "rf_packet_counters_before": { + "sent": 73, + "sent_direct": 62, + "sent_flood": 11, + "recv": 43, + "recv_errors": 4 + }, + "rf_packet_counters_after": { + "sent": 273, + "sent_direct": 262, + "sent_flood": 11, + "recv": 43, + "recv_errors": 4 + }, + "configured": { + "get tempradio": "TempRadio active: 917.000,62.50,7,5 780s left,preamble=32", + "get tempradio2": "off", + "get radio2.status": "off; RX=42,0 TX=73,0 switches=0 errors=0 max=0us preamble=32,32", + "get radio2.scan": "off", + "get radio2.timing": "off" + }, + "radio_diagnostics_before_configuration": "state=1(RX), recv=27, sent=69, errors=4, err_flags=8, outbound=no, last_rx=254s ago", + "radio_diagnostics_after": "state=1(RX), recv=43, sent=273, errors=4, err_flags=8, outbound=no, last_rx=263s ago", + "watchdog_before": "Unknown command", + "watchdog_after": "Unknown command", + "restoration_passed": true, + "restored_live_radio2_status": "off; RX=42,0 TX=273,0 switches=0 errors=0 max=0us preamble=32,32" + }, + "primary": { + "board": "Seeed Tracker T1000-E", + "firmware_version": "v1.18.0.1-halo-keymind-cascade-dev-08650f64 (Build: 20-Oct-2026)", + "power_and_logging": { + "get powersaving": "off", + "get radio.rxps.config": "off,level=3,preamble=16,rx=44079,sleep=5732", + "get usb.logging": "usb.logging off; port: primary USB serial port (tty/COM name is host-assigned)" + }, + "core_before": { + "battery_mv": 4185, + "uptime_secs": 12406, + "errors": 0, + "queue_len": 0 + }, + "core_after": { + "battery_mv": 4193, + "uptime_secs": 12613, + "errors": 0, + "queue_len": 0 + }, + "packet_deltas": { + "sent": 0, + "sent_direct": 0, + "sent_flood": 0, + "recv": 100, + "recv_errors": 0 + }, + "rf_packet_counters_before": { + "sent": 8, + "sent_direct": 0, + "sent_flood": 8, + "recv": 6070, + "recv_errors": 290 + }, + "rf_packet_counters_after": { + "sent": 8, + "sent_direct": 0, + "sent_flood": 8, + "recv": 6170, + "recv_errors": 290 + }, + "configured": { + "get tempradio": "TempRadio active: 917.000,62.50,7,5 781s left,preamble=32", + "get tempradio2": "off", + "get radio2.status": "off; RX=6060,0 TX=8,0 switches=0 errors=0 max=0us preamble=32,32", + "get radio2.scan": "off", + "get radio2.timing": "off" + }, + "radio_diagnostics_before_configuration": "state=1(RX), recv=6055, sent=4, errors=290, err_flags=0, outbound=no, last_rx=8s ago", + "radio_diagnostics_after": "state=1(RX), recv=6170, sent=8, errors=290, err_flags=0, outbound=no, last_rx=13s ago", + "watchdog_before": "Unknown command", + "watchdog_after": "Unknown command", + "restoration_passed": true, + "restored_live_radio2_status": "off; RX=6160,0 TX=8,0 switches=0 errors=0 max=0us preamble=32,32", + "collection": { + "expected": 100, + "received": 100, + "missing_ordinals": [], + "duplicates": 0, + "wrong_profile_ordinals": [], + "drops": [], + "errors": [], + "framing": { + "ports_opened": 0, + "ports_closed": 0, + "request_frames_sent": 0, + "response_frames_received": 328, + "request_bytes_sent": 0, + "response_payload_bytes": 24176, + "discarded_prefix_bytes": 0, + "invalid_length_candidates": 0, + "ignored_push_frames": 0 + } + }, + "profile_results": { + "primary": { + "expected": 100, + "received": 100, + "missing_ordinals": [] + } + } + }, + "secondary": { + "board": "Heltec T096", + "firmware_version": "v1.18.0.1-halo-keymind-cascade-dev-4773ac42 (Build: 20-Oct-2026)", + "power_and_logging": { + "get powersaving": "on", + "get radio.rxps.config": "on,level=8,preamble=16,rx=39657,sleep=15975", + "get usb.logging": "usb.logging off; port: dedicated USB CDC 1, interface 02 (Linux: *-if02; tty/COM name is host-assigned)" + }, + "core_before": { + "battery_mv": 4184, + "uptime_secs": 13046, + "errors": 8, + "queue_len": 0 + }, + "core_after": { + "battery_mv": 4191, + "uptime_secs": 13253, + "errors": 8, + "queue_len": 0 + }, + "packet_deltas": { + "sent": 0, + "sent_direct": 0, + "sent_flood": 0, + "recv": 100, + "recv_errors": 0 + }, + "rf_packet_counters_before": { + "sent": 8, + "sent_direct": 0, + "sent_flood": 8, + "recv": 31, + "recv_errors": 111 + }, + "rf_packet_counters_after": { + "sent": 8, + "sent_direct": 0, + "sent_flood": 8, + "recv": 131, + "recv_errors": 111 + }, + "configured": { + "get tempradio": "TempRadio active: 918.000,500.00,5,5 782s left,preamble=384", + "get tempradio2": "off", + "get radio2.status": "off; RX=30,0 TX=8,0 switches=0 errors=0 max=0us preamble=384,32", + "get radio2.scan": "off", + "get radio2.timing": "off" + }, + "radio_diagnostics_before_configuration": "state=1(RX), recv=31, sent=4, errors=111, err_flags=8, outbound=no, last_rx=149s ago", + "radio_diagnostics_after": "state=1(RX), recv=131, sent=8, errors=111, err_flags=8, outbound=no, last_rx=5s ago", + "watchdog_before": "Unknown command", + "watchdog_after": "Unknown command", + "restoration_passed": true, + "restored_live_radio2_status": "off; RX=130,0 TX=8,0 switches=0 errors=0 max=0us preamble=32,32", + "collection": { + "expected": 100, + "received": 100, + "missing_ordinals": [], + "duplicates": 0, + "wrong_profile_ordinals": [], + "drops": [], + "errors": [], + "framing": { + "ports_opened": 0, + "ports_closed": 0, + "request_frames_sent": 0, + "response_frames_received": 313, + "request_bytes_sent": 0, + "response_payload_bytes": 23986, + "discarded_prefix_bytes": 0, + "invalid_length_candidates": 0, + "ignored_push_frames": 0 + } + }, + "profile_results": { + "secondary": { + "expected": 100, + "received": 100, + "missing_ordinals": [] + } + } + } + }, + "debugger": { + "schema": 1, + "log_sha256": "13545ca7ab5ac3b3934760c5452aff2919f42ef52916f1002c028b473774b12e", + "parser_sha256": "0b4ed3ff71291954012424d493b8550b19c3da0f0e5b97f55e5f78d57d4fb76f", + "config_sha256": "1feb1f14a80cc0e9f1ad403d4c67d73866eea8c548c8df4c3c93b66b0be8ee6f", + "duration_ms": 300000, + "maximum_elapsed_ms": 300500, + "minimum_sample_count": 20000, + "sampled_cpu_integrity_passed": true, + "sampled_cpu_integrity_failures": [], + "physical_RF_delivery_qualified_by_this_parser": false, + "cpu_sampling": { + "passed": false, + "failures": [ + "insufficient_sleeping_observations" + ], + "sample_count": 27118, + "counts": { + "C_DEBUGEN": 0, + "C_HALT": 0, + "C_STEP": 0, + "C_MASKINTS": 0, + "S_HALT": 0, + "S_SLEEP": 8, + "S_LOCKUP": 0, + "S_RESET_ST": 0 + }, + "elapsed_ms": 300008, + "observed_ms": 300004.0, + "weighted_sleep_ms": 92.0, + "weighted_sleep_fraction": 0.00030666257783229555, + "unweighted_sleep_fraction": 0.00029500700641640237, + "max_gap_ms": 18.0, + "max_read_ms": 3, + "baseline_debug_ctrl_hex": "0000", + "baseline_C_DEBUGEN": false, + "initial_sticky_reset": false, + "final_DHCSR": "01010000", + "openocd_version": [ + "Open On-Chip Debugger 0.12.0+dev-snapshot (2025-07-16-14:15)" + ], + "qualification": "sampled CPU sleep observations, not exact residency or electrical current", + "firmware_identity_must_be_bound_separately": true, + "exact_hardware_identity_matched_before_and_after": true + }, + "offline_analysis_of_physical_capture": true, + "cpu_sleep_qualified": false, + "cpu_sleep_scope": "Active USB logging inhibits repeater CPU sleep; this is an RF/debug-integrity capture." + }, + "cleanup_passed": true, + "gateway_restored": true, + "errors": [], + "independent_audit": { + "audit_passed": false, + "failures": [ + "missing_packets_rak" + ], + "cautions": [], + "same_packet_witness_discrepancies": { + "primary": [ + "P022", + "P044", + "P070" + ], + "secondary": [] + }, + "auditor_sha256": "3333440a28445932beef62adab177301a492cb1306fb87e9a7080cb350422669" + }, + "earlier_incomplete_attempt": { + "transmissions": 50, + "primary_transmissions": 20, + "secondary_transmissions": 30, + "reason": "Sender was still pending after the controller fixed 2.1-second retune wait.", + "included_in_final_counts": false, + "settings_and_gateway_restored": true, + "controller_only_repair": "Poll for exact active tuple and preamble before any next transmission." + } +}