Research note · 2026-05-11
nRF54L15 PAwR Sniffer Method And Limits
Date: 2026-05-11
This document answers a narrow question: what does the external sniffer actually prove in the nRF54L15 PAwR/PAST investigation, and what does it not prove yet?
The short answer is that the sniffer gives us real RF-layer evidence, including CRC-good packets, channel, RSSI, packet timestamp, AuxPtr fields, and SyncInfo fields. It does not yet give us a complete event-by-event PAwR schedule trace. For that, the next sniffer improvement should follow the SyncInfo-derived periodic advertising schedule instead of mainly sweeping or briefly following AuxPtr targets.
Sniffer Hardware And Host Script
The capture setup uses a seven-device racoon SPI sniffer rig controlled from a Raspberry Pi. The local host copies and runs ../scripts/run_correlated_ap_sniffer.py, which in turn deploys and executes the racoon host probe script from the local development checkout:
~/ncs/racoon_project/host_spi/pawr_probe.py
The probe layout is:
- D0-D2: fixed on BLE primary advertising channels 37, 38, and 39.
- D3-D6: sweep BLE secondary/data channels 0-36.
- Primary access address:
0x8E89BED6. - Primary advertising CRC init:
0x555555. - Optional secondary PHY: LE 1M or LE 2M.
- Optional AuxPtr following: when a CRC-good target primary advertisement contains AuxPtr, a secondary worker retunes briefly to the indicated channel and PHY.
The probe writes two artifacts per run:
sniffer.jsonl: one JSON object per saved packet.sniffer_summary.json: counters, recent AuxPtr/SyncInfo examples, and target-match summaries.
How The Sniffer Works
The script starts with normal BLE advertising discovery. It listens on the primary advertising access address and decodes ADV_EXT_IND extended advertising headers. From those headers it extracts:
- ADI/SID, used to identify the PAwR advertiser SID
8. - AuxPtr, used to find the secondary channel, secondary PHY, and offset from the primary packet.
- SyncInfo, when present on a secondary packet, used to learn the periodic advertising access address, CRC init, interval, channel map, and event counter.
- Advertising data fields such as service UUID
0x1857when present.
With --follow-aux, the probe uses CRC-good AuxPtr records to retune secondary workers to the pointed channel and PHY for a short hold time. This is why the validation docs insist on CRC-good AuxPtr and secondary evidence: a CRC-bad AuxPtr could send the sniffer to a false channel and create a convincing-looking but wrong story.
For direct periodic-AA checks, we use SyncInfo values from an observed AP advertisement and run the sniffer with the periodic advertising access address and CRC init. That proves that packets with the SyncInfo-derived access address are present on air. It is not the same thing as proving every expected periodic event arrived on its predicted channel at its predicted time.
What Timing Data We Get
The current sniffer provides several timing-related fields:
- Per packet:
timestamp_us,wall_time_s,channel,rssi_dbm,device_id,role,worker_phy,access_address, andcrc_ok. - AuxPtr:
channel,phy_name,offset_us,offset_unit_us, and clock accuracy flagca. - SyncInfo:
offset_us,interval_units,interval_ms,channel_map_hex,active_channels,sca,access_address,crc_init, andevent_counter.
Important limits:
- Existing
wall_time_isovalues in older JSONL logs are useful for relative ordering inside the run, not for absolute UTC timestamps. Some captures show a 1970 date because the field came from the probe/runtime time base rather than a real wall clock. timestamp_usis useful for packet ordering and rough deltas from each sniffer device, but it is not yet used as a calibrated, cross-device timing trace.- Direct periodic-AA captures prove CRC-good packets on a SyncInfo-derived access address/CRC. They do not yet decode the full PAwR response timing or prove a missing packet on a specific event/channel.
- For non-advertising access addresses, the current JSON decoder can still label the first PDU bits with advertising-style names. In direct periodic-AA mode, trust
access_address,crc_ok, channel, RSSI, and raw bytes first; treat thepdu_typelabel as secondary.
What We Know Is Working
Sniffer conclusions are only drawn from CRC-good records. The main validation document is PAWR_SNIFFER_VALIDATION.md.
Controlled nRF54 validation run:
- Artifact:
../logs/sniffer_validation/20260511_125546Z/sniffer_summary.json target_crc_good.total = 1094target_crc_good.by_role = {"primary": 1068, "secondary": 26}crc_good.aux_ptr_count = 1168crc_good.aux_ptr_2m_count = 1068crc_good.secondary_count = 26crc_good.sync_info_count = 26target_crc_good.by_reason.service_uuid = 26
This proves the sniffer can catch a known nRF54 source with SID 8, LE 2M AuxPtr, secondary packets, SyncInfo, and service UUID 0x1857 in CRC-good records.
Real nRF54 AP diagnostic preflight:
- Artifact:
../logs/correlated_pawr/20260511_140333Z/preflight/sniffer_summary.json target_crc_good.total = 2502target_crc_good.by_role = {"primary": 2445, "secondary": 57}crc_good.aux_ptr_2m_count = 2445crc_good.secondary_count = 57crc_good.sync_info_count = 57
This proves the real nRF54 AP can emit CRC-good SID 8, LE 2M secondary packets, and SyncInfo under the AP-only diagnostic configuration.
Real nRF54 AP forced-1M full run:
- Artifact:
../logs/correlated_pawr/20260511_150808Z/sniffer/sniffer_summary.json target_crc_good.total = 4642target_crc_good.by_role = {"primary": 4420, "secondary": 222}crc_good.aux_ptr_2m_count = 0crc_good.secondary_count = 251crc_good.sync_info_count = 222
This proves the validated sniffer can also catch the real nRF54 AP on the LE 1M secondary path when the AP is configured for forced 1M.
Direct periodic-AA checks:
../logs/correlated_pawr/20260511_172925Z/direct_periodic_aa/sniffer.jsonlcontains 6 CRC-good records on SyncInfo-derived access address0x647D6EBE.../logs/correlated_pawr/20260511_181151Z/direct_periodic_aa/sniffer.jsonlcontains 7 CRC-good records on SyncInfo-derived access address0xD9522E15.
These direct-AA checks prove that the AP periodic train is not purely fictional after SyncInfo is advertised. They still do not prove whether every later PAwR event follows the expected channel/timing schedule.
How We Avoid Fooling Ourselves
The investigation treats AP/controller API success as insufficient by itself. A useful result needs cross-checks:
- AP log: PAST and
bt_le_per_adv_set_subevent_data()return success. - Tag RTT: the receiver either reports valid PAwR RX or explicit
buf=NULLRX_FAILED callbacks. - Sniffer: only CRC-good records count as RF proof.
- Positive control: stock Zephyr PAwR/PAST samples must work on the same boards.
- Parameter split: change one PAwR profile group at a time and preserve raw logs.
This is why an empty serial log such as ttyACM1.log from the stock-sample run is not meaningful evidence. The AP evidence for that run is the non-empty ttyACM0.log documented in NRF54L15_MINIMAL_ZEPHYR_SAMPLE_RESULT.md.
Next Sniffer Improvement
The most useful next RF tool is a SyncInfo follower:
- Parse SyncInfo from a CRC-good secondary packet.
- Extract access address, CRC init, channel map, interval, sleep clock accuracy, and event counter.
- Predict the next periodic advertising event channel and time from the BLE channel-selection algorithm.
- Retune a secondary worker to the predicted channel before each event.
- Log expected event number, expected channel, actual packets, RSSI, CRC status, and timing error.
That would answer the remaining RF question directly: after the tag receives one PAwR event and then reports RX_FAILED, does the AP keep transmitting on the correct PAwR schedule, or does the periodic train drift away from what SyncInfo promised?