Files
MicrOBU/microbu-esp32c5/station-link/README.md
T
Ashin Walpola 0e9525162d Keep the colleague's microbu-esp32c5 tree in this repository
obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but
that tree was gitignored, so a clone of this repository could not build the
firmware it ships. It is now committed here as ordinary files in its own
folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP
bridge's signature verification (--trust) used on the bench. Nothing is
fetched from or pushed to the colleague's repository; this repository and
its remotes carry everything. The folder's own .gitignore keeps build output,
downloaded components and private key material out, as it did there; the
committed file set is identical to that repository's tracked files.

The ESP32-C5 is still flashed from obu-firmware/, which only takes
vanetza-idf from microbu-esp32c5/, so the two stay separate folders.
FLASHING.md says how to take a newer version of the colleague's tree (copy
it over the folder, rebuild, test, commit).
2026-09-23 17:46:40 +02:00

25 KiB
Raw Blame History

VRU ITS-S station-internal link (phone ⇄ micrOBU)

Project interface IF-PROJ-NF-BLE-001 made concrete, plus its DEC-VBS-004 companions. One VRU ITS-S (ETSI EN 302 665 clause 4.5.1) is split over two physical units at the NF-SAP (DEC-VBS-003):

Unit Hosts
Phone (app) VRU basic service, VAM encode/decode, HMI, PoTi, credential manager (TS 102 941 client)
micrOBU (ESP32-C5, this firmware) BTP-B, GeoNetworking SHB/GBC, SN-SAP security entity (TS 103 097 signing, ticket pool, identifier change, SN-DECAP), ITS-G5 access

Everything that crosses the link is a standards-defined primitive (BTP-DATA.request/indication of TS 102 723-11 / TS 103 836-5-1 Annex A; the PoTi data set of EN 302 890-2 Tables 2 and 3; the SF-SAP identifier-change primitives of TS 102 723-9 Tables 10 to 21; the legacy/reference MF-SET parameter semantics of TS 103 175 Table 11) or one of three project-defined management messages (station configuration, credential provisioning with a VCR1 bundle, status). No SN-, MN- or MI-SAP primitive crosses the link (ACT-015).

The control/reference transport is the micrOBU's wired native USB Serial/JTAG port. Encrypted BLE GATT is an experimental alternative and shall not replace the wired architecture until ACT-021's controlled coexistence experiment shows that concurrent BLE does not disturb C5 PHY/CCA observation, ITS-G5 RF behavior, timing, reliability, or power measurements. The message layer below is transport independent. Nothing here is an ETSI on-air format or a conformance claim for the phone-to-micrOBU link.

Implementations: firmware firmware/main/link_protocol.*, ble_framing.* and ble_link.* (C++); phone-side protocol module microbuapp/station-link/ and Android central microbuapp/app/src/main/java/com/hawhamburg/micr0bu/data/transport/BleStationLinkTransport.kt (Kotlin); bench reference station-link/python/microbu_link/ (Python, with serial and BLE adapters used by the phone emulator, TTCN SUT bridge and unit tests). Golden vectors and fragmentation are checked across the Python, C++ and Kotlin implementations.

Message layer (version 1)

A reassembled message is at most 512 octets. All integers are little-endian (the app's existing serial framing convention); ETSI-encoded payloads inside (UPER VAMs, COER certificates, GeoNetworking octets) are opaque and keep their own byte order.

octet 0     opcode
octet 1     flags     bit 0: FRAGMENT_FIRST, bit 1: FRAGMENT_MORE (see Fragmentation); other bits 0
octets 2-3  sequence  u16, see below
octets 4-   body      opcode specific

Sequence: every request the phone sends carries its own counter; the micrOBU answers with a RESULT (or the typed reply named below) carrying the same sequence. Messages the micrOBU originates (indications, events, status) carry the micrOBU's own counter and are never answered, except SF_IDCHANGE_EVENT which the phone answers with SF_IDCHANGE_EVENT_RESPONSE echoing the event's sequence.

Phone → micrOBU

Opcode Name Reply Standard basis
0x01 STATION_CONFIGURE RESULT + station info project management (MIB values the micrOBU cannot know)
0x02 POTI_UPDATE none (RESULT only on error) EN 302 890-2 Table 2 (NF-SAP) and Table 3 (SF-SAP) minimum data sets, plus speed/heading for the GN position vector
0x03 BTP_DATA_REQUEST RESULT TS 102 723-11 clause 5 / TS 103 836-5-1 Annex A.2; VBS PCI TS 103 300-3 Table 4
0x04 CREDENTIALS_PROVISION RESULT + apply report (last segment) DEC-VBS-004: one VCR1 bundle (vanetza_idf/credentials.hpp)
0x05 CREDENTIALS_ERASE RESULT project
0x06 SF_IDCHANGE_SUBSCRIBE RESULT + subscription handle TS 102 723-9 Tables 10/11
0x07 SF_IDCHANGE_UNSUBSCRIBE RESULT Tables 14/15
0x08 SF_IDCHANGE_EVENT_RESPONSE none Table 13
0x09 SF_IDCHANGE_TRIGGER RESULT Tables 16/17
0x0A SF_ID_LOCK RESULT + lock handle Tables 18/19
0x0B SF_ID_UNLOCK RESULT Tables 20/21
0x0C STATUS_REQUEST STATUS project

micrOBU → phone

Opcode Name Standard basis
0x80 RESULT project (the library's Result plus link codes)
0x81 BTP_DATA_INDICATION TS 103 836-5-1 Annex A.3 with the GN-DATA.indication parameters it preserves
0x82 SF_IDCHANGE_EVENT TS 102 723-9 Table 12
0x83 MF_SET_REQUEST TS 103 175 Table 11 (DCC F-Params of Table 13)
0x84 STATUS project

Bodies

RESULT (0x80): u8 result, u8 detail_length, detail[]. result is vanetza_idf::Result (0 accepted, 1 invalid_argument, 2 unsupported, 3 wrong_entry_point, 4 security_unavailable, 5 resource_limit, 6 rejected, 7 time_regression, 8 identity_change_pending) or a link code: 0x10 unknown_opcode, 0x11 malformed, 0x12 not_configured (no STATION_CONFIGURE yet), 0x13 busy, 0x14 no_credentials. detail depends on the request:

Request detail
STATION_CONFIGURE u8[8] gn_address (as on the wire, TS 103 836-4-1 clause 6.3), u8[8] identifier (HashedId8 of the current authorization ticket, zeros when unsecured), u8 credentials_loaded, u8 tickets
CREDENTIALS_PROVISION (last segment) u8 roots, u8 authorities, u8 tickets accepted (ApplyReport)
SF_IDCHANGE_SUBSCRIBE u64 subscription
SF_ID_LOCK u64 lock_handle
others empty

STATION_CONFIGURE (0x01): u8 station_type (TS 102 894-2 StationType, 2 = cyclist), u8 security (0 unsecured, 1 secured: itsGnSecurity), u8 address_configuration (0 AUTO with the given MID, 1 ANONYMOUS: MID from the ticket digest, TS 103 836-4-1 clause 10.2.1.4), u8[6] mid (AUTO only), u8 beaconing (0/1, GN-MGMT beacons), u16 channel_number (ITS-G5 channel, 180 = 5 900 MHz), u8 transmit_power_dbm, u8 radio (0 off, 1 receive only, 2 transmit and receive — laboratory transmission stays an explicit choice), u8 default_traffic_class (raw TC octet used when a request carries none), u8 default_lifetime (raw GN Lifetime octet). Configuring (re)creates the stack on the micrOBU; a request before it is answered not_configured.

POTI_UPDATE (0x02): u64 timestamp (TimestampIts, TAI ms since 2004-01-01; also synchronises the micrOBU's ITS clock), i32 latitude, i32 longitude (1/10 µdeg, WGS84), u16 semi_major, u16 semi_minor (cm, 95 % horizontal confidence ellipse), u16 orientation (0.1°, semi-major axis from north), u8 flags (bit 0 altitude present, bit 1 speed present, bit 2 heading present, bit 3 position accuracy indicator), i32 altitude (cm), u16 speed (0.01 m/s), u16 heading (0.1°). The micrOBU answers only when it rejects the update (a RESULT with time_regression or invalid_argument); accepted updates are silent so a 10 Hz PoTi does not double the link traffic.

BTP_DATA_REQUEST (0x03) (TS 103 300-3 Table 4 order):

u8  btp_type                    0 BTP-A, 1 BTP-B
u16 destination_port
u16 destination_port_info       (BTP-B) or source_port (BTP-A)
u8  gn_packet_transport_type    1 SHB, 2 GBC (3 TSB, 4 GAC, 5 GUC are refused: unsupported)
u8  gn_communication_profile    0 unspecified, 1 ITS-G5, 2 LTE-V2X
u8  gn_security_profile         0 not given (station configuration), 1 unsecured, 2 secured
u8  gn_traffic_class            raw TC octet (TS 103 836-4-1 clause 9.7.5); 0xFF = station default
u8  gn_maximum_packet_lifetime  raw Lifetime octet (clause 9.6.4); 0xFF = station default
u8  gn_maximum_hop_limit        0 = station default
u16 gn_repetition_interval_ms   0 = no repetition
u16 gn_repetition_maximum_ms
u32 its_aid                     ITS-AID (TS 102 965), the SN-ENCAP input
u8  permissions_length          SSP octets (TS 103 097 / TS 102 965) …
u8  permissions[]
u8  context_length              SN-ENCAP context_information (vam_cluster = 01) …
u8  context[]
    if GBC: u8 shape (0 circle, 1 rectangle, 2 ellipse), i32 latitude, i32 longitude,
            u16 distance_a, u16 distance_b (m), u16 angle (°)
u16 length                      of the FL-SDU (the VAM)
u8  fl_sdu[length]

The micrOBU hands this to NF_SAP::BTP_DATA_request_submit. RESULT.result is the library's acceptance (accepted = submitted for protocol processing, not proof of transmission). A secured request whose signing the entity refuses (no valid ticket, identifier change pending) is visible in STATUS counters.

BTP_DATA_INDICATION (0x81): u8 btp_type, u16 destination_port, u16 destination_port_info (or source_port), u8 gn_packet_transport_type, u8 gn_traffic_class, u8 gn_remaining_packet_lifetime (raw, 0xFF absent), u8 gn_remaining_hop_limit (0xFF absent), u8[8] source_gn_address, u32 source_timestamp (TST ms), i32 source_latitude, i32 source_longitude, u8 security_report (0 unsecured, 1 + VerificationReport ordinal: 1 Success, 2 False_Signature, …), u32 its_aid (0 when absent), u8 permissions_length, permissions[], u8 certificate_present, u8[8] certificate_id, u8 area_present, [u8 shape, i32 lat, i32 lon, u16 a, u16 b, u16 angle], u16 length, received_fl_sdu[].

CREDENTIALS_PROVISION (0x04): u16 total_length, u16 offset, u8 segment_length, segment[]. The segments of one bundle arrive in order; offset + segment_length == total_length ends it. The micrOBU decodes the bundle, applies it through the security entity's checks (security::apply), saves it in the NVS credential store when every item was accepted, and rebuilds the station with it. Keys travel in the clear: the transport must be a trusted one (a paired, encrypted BLE link; a USB cable on the bench).

SF_IDCHANGE_SUBSCRIBE (0x06): u8 subscriber_data_length, subscriber_data[]. SF_IDCHANGE_UNSUBSCRIBE (0x07): u64 subscription. SF_IDCHANGE_EVENT (0x82): u64 subscription, u8 command (0 PREPARE, 1 COMMIT, 2 ABORT, 3 DEREG), u8[8] id (HashedId8 of the next ticket), u8 subscriber_data_length, subscriber_data[]. SF_IDCHANGE_EVENT_RESPONSE (0x08): u64 subscription, u8 return_code — sent for PREPARE and COMMIT with the event's sequence; the service's response timeout (500 ms) applies across the link. SF_IDCHANGE_TRIGGER (0x09): empty. SF_ID_LOCK (0x0A): u8 duration_seconds. SF_ID_UNLOCK (0x0B): u64 lock_handle.

MF_SET_REQUEST (0x83): u32 fac_id, u8 command_ref, u8 count, then count × (u8 f_param_no, u32 value) with TS 103 175 Table 13 numbers (0 CHANNEL_NUMBER, 1 AVAILABLE_RESOURCE). The micrOBU sends it only for measurements its access layer actually made; it never invents a channel load (ACT-021).

STATUS (0x84): u32 uptime_ms, u8 configured, u8[8] gn_address, u8[8] identifier, u8 change_pending, u8 tickets, u32 signed_messages, u32 refused_no_ticket, u32 refused_change_pending, u32 refused_permission, u32 sign_failed, u32 verified, u32 rejected (SN-DECAP rejections of every kind), u32 requests_accepted, u32 requests_refused, u32 indications, u32 radio_submitted, u32 radio_failed, u32 radio_received, u32 radio_dropped, u32 link_rx_frames, u32 link_crc_errors, u32 link_malformed, u32 poti_updates, u64 its_time (current micrOBU ITS clock, ms; 0 before the first POTI_UPDATE). The micrOBU sends it every second unsolicited and on STATUS_REQUEST.

Fragmentation

A message longer than the transport's unit (ATT_MTU − 3 for a notification; a GATT client's long write handles 512 B values itself) is sent as fragments, each with the full 4-octet header and the same sequence: the first with FRAGMENT_FIRST, every one but the last with FRAGMENT_MORE. The receiver concatenates the bodies. The serial transport carries whole messages and never sets these bits.

Transports

Serial (bench and conformance tests)

Native USB Serial/JTAG of the micrOBU's ESP32-C5 (VID 303A, PID 1001), the app's Phase 03 framing (obu-firmware/main/serial_link.h, SerialFrame.kt):

[0xAA][0x55][type:1][length:2 LE][payload][crc16:2 LE]

CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF) over type + length + payload. Frame types of this firmware:

Type Payload
0x10 one link message (above), ≤ 512 octets
0x11 test channel request (PC → micrOBU) / reply (micrOBU → PC), ≤ 1 536 octets, only in a test build
0x7F UTF-8 log line from the firmware (ESP_LOG output; the console stays free of binary frames)

The maximum frame payload is 1 536 octets (a mirrored GNPDU can exceed 512). Frames the phone does not know are ignored; text outside frames (ROM boot messages) is skipped by the sync/CRC search.

BLE GATT (production transport)

The transport uses the Nordic UART Service UUID layout so standard BLE tools can inspect it. It is a byte transport for the project message layer, not the Nordic UART wire protocol:

GATT object UUID Properties Direction
Primary service 6e400001-b5a3-f393-e0a9-e50e24dcca9e primary —
RX/request 6e400002-b5a3-f393-e0a9-e50e24dcca9e write, write without response phone → micrOBU
TX/notification 6e400003-b5a3-f393-e0a9-e50e24dcca9e notify micrOBU → phone

The peripheral advertises the service and the name micrOBU-XXXX, where the suffix is derived from its Bluetooth MAC. GATT access requires an authenticated, encrypted connection. The ESP32 uses LE Secure Connections, bonding and a fresh six-digit passkey printed to the USB log (DisplayOnly); NimBLE persists the bond in NVS. Android presents the normal system pairing UI and retains the bond. This protects the credential bundle in transit; it is not application- layer encryption.

Android requests an ATT MTU of 517; the ESP32 preference is 512. The Android writer uses ATT_MTU - 3, capped at 512, after negotiation (509 octets with the current ESP32 configuration) and falls back to 20 octets. The ESP32 notification path deliberately emits values of at most 20 octets so it also works at the mandatory default MTU. Every value uses the fragmentation flags above and repeats the link header; a complete message is limited to 512 octets. Writes are acknowledged and serialized. Notifications are enabled before the Android adapter reports READY. The USB test channel is never exposed over BLE.

Test channel (test firmware only, USB only)

CONFIG_MICROBU_TEST_CHANNEL compiles a software lower tester and the diagnostic hooks the ETSI campaigns need, behind frame type 0x11. It is not part of the phone interface and never exists on BLE. Requests: u8 command, reply u8 result, u8 record_count, records [kind u8][length u16 LE][bytes]:

Command Body Effect
0x01 MIRROR u8 mode (0 off, 1 mirror, 2 divert) every AL_DATA.request the stack makes is copied (mirror) or kept from the radio (divert) and queued as kind-1 records
0x02 INJECT u8[6] source, u8[6] destination, gnpdu[] AL_DATA.indication from the lower tester into the stack
0x03 GN_REQUEST u8 traffic_class, payload[] GN-DATA.request SHB with next header "Any" (the Security ATS GN-MGMT/GENMSG cases; N-SAP, not a phone primitive)
0x04 DRAIN – returns the queued records: kind 1 AL_DATA.request (the GNPDU as submitted), kind 2 BTP-DATA.indication ([type][dest port][port info][SDU]), kind 3 GN-DATA.indication payload
0x05 RESET – drops the queue and the mirror mode

The queue is bounded (32 records, 8 KiB); an overflow is reported as resource_limit on the next DRAIN and the records lost are counted.

Phone emulator, SUT bridge and campaigns (PC side)

python/microbu_link/ is the phone side of the message layer (codec, serial transport, request/reply client, the small VBS of vbs.py), shared by:

  • python/phone_emulator.py — the phone half of the VRU ITS-S on a PC: configures the micrOBU, provisions a VCR1 bundle, feeds PoTi (static or a bicycle on a circle), assembles VAMs with asn1tools from the ETSI ASN.1 of the submodule and hands them over with the PCI of TS 103 300-3 Table 4 under the generation rules of clause 6.4, subscribes to the identifier-change events (stop on PREPARE, new StationId after COMMIT), prints the indications, status and log lines; --divert --pcap records what the micrOBU signs for an independent verifier. Its default synthetic trajectory is a 20 m-radius circle at 2 m/s centred at 53.5652470 N, 10.0082390 E over the Außenalster's open water; --lat, --lon, --radius and --speed override it. Radio emission remains opt-in through --radio txrx. The center is not itself a transmitted position: generated positions lie on the circle perimeter. The firmware's standalone BOOT-button VAM is a separate fixed payload and does not use these phone-emulator coordinates.
  • python/sut_bridge.py — the SUT process of the vanetza-idf ETSI adapters (hex-line protocol of hil_sut.hpp) with the micrOBU behind the phone interfaces: BTP-DATA.request, PoTi and credentials over the link, the software lower tester over the USB test channel, CAM/DENM stimuli assembled on the PC. --listen host:port serves the protocol over TCP; python/sut_tcp_relay.py inside WSL is the SUT executable that forwards it (WSL interop drops piped stdin of Windows executables).
# Windows: the micrOBU on COM11, the campaign pool packed as a bundle
python sut_bridge.py --port COM11 --bundle pool.vcr --listen 0.0.0.0:7788

LinkClient is transport-independent. SerialTransport and BleTransport implement the same TransportAdapter.write(frame_type, payload) / close() boundary and feed the same receive callback; configure, PoTi, credential, BTP-DATA, status and event code is therefore identical. Select only the adapter at the command line:

cd implementation\station-link\python
python -m pip install -r requirements.txt

# Safe first BLE run: hand VAMs to an unsecured station, with ITS-G5 RF off.
python phone_emulator.py --ble --unsecured --radio off --period 1000 --duration 10 --verbose `
  --report ble-vam-session.json

# Select a particular advertisement when several micrOBUs are present.
python phone_emulator.py --ble micrOBU-1234 --unsecured --radio off --duration 10

For the signed run, replace --unsecured with --bundle <path-to-chain.vcr> (or use credentials already retained in the micrOBU NVS). Change --radio off to --radio txrx only for the intended laboratory RF test. The USB test channel has no BLE equivalent, so --mirror, --divert and --pcap are rejected with --ble; all station-link/VAM behavior above that adapter is the same.

Instead of an offline --bundle, --pki-url makes the emulator obtain an enrolment credential and authorization ticket from tools/local_pki.py, build the same VCR1 bundle, and provision it before starting the VBS. Supply --pki-root, --pki-ea, --pki-aa, --pki-canonical-key, --pki-its-id, --pki-issue-tool, --pki-client-tool, and --pki-bundle-tool together; use --pki-hours to override the 24-hour AT validity. The message flow and retained host-side proof are documented in docs/idf/evidence/enrolment-authorization-01 and the EA/AA section of docs/idf/test-campaigns.md in vanetza-idf. local_pki.py is a separate localhost HTTP process, normally bound to 127.0.0.1; it is not imported into or hosted by the emulator. Consequently, the lab authority can later be replaced by a hosted PKI without changing the phone flow: set --pki-url to the hosted access point and keep the same S3/S4 HTTP interface.

End-to-end hardware workflow: Online PKI, Transmission & Reception

This workflow runs the full online TS 102 941 enrolment/authorization and 5.9 GHz ITS-G5 over-the-air transmission between two ESP32-C5 boards:

  • COM4 (TX): micrOBU running firmware microbu_firmware.bin
  • COM5 (RX): independent receiver running 2x2map-0.3.0 bridge firmware

1. Initialize the lab PKI hierarchy (run once)

`powershell $dir = "C:\microbu-pki-wt\pki-lab" New-Item -ItemType Directory -Force -Path "$dir\chain", "$dir\issued" & C:\Strawberry\c\bin\openssl.exe ecparam -name prime256v1 -genkey -noout -out "$dir\root.pem" & C:\Strawberry\c\bin\openssl.exe ecparam -name prime256v1 -genkey -noout -out "$dir\canonical.pem"

$issue = "C:\bachelorthesis_micrOBU\implementation\external\vanetza-idf\build-host-sec\vidf_issue.exe" & $issue root --key "$dir\root.pem" --name "lab root" --id ROOT --out "$dir\chain" & $issue authority --issuer "$dir\chain\ROOT.oer" --issuer-key "$dir\root.pem" --name "lab EA" --id EA --out "$dir\chain" & $issue authority --issuer "$dir\chain\ROOT.oer" --issuer-key "$dir\root.pem" --name "lab AA" --id AA --out "$dir\chain" `

2. Start the localhost PKI server (Terminal 1)

powershell python C:\microbu-pki-wt\implementation\external\vanetza-idf\ports\esp_idf\tools\local_pki.py --issue-tool C:\bachelorthesis_micrOBU\implementation\external\vanetza-idf\build-host-sec\vidf_issue.exe --ea C:\microbu-pki-wt\pki-lab\chain\EA.oer --ea-key C:\microbu-pki-wt\pki-lab\chain\EA.vkey --ea-enc-key C:\microbu-pki-wt\pki-lab\chain\EA.ekey --canonical-key C:\microbu-pki-wt\pki-lab\canonical.pem --aa C:\microbu-pki-wt\pki-lab\chain\AA.oer --aa-key C:\microbu-pki-wt\pki-lab\chain\AA.vkey --aa-enc-key C:\microbu-pki-wt\pki-lab\chain\AA.ekey --dir C:\microbu-pki-wt\pki-lab\issued --port 8092

3. Start the independent v2x2map receiver (Terminal 2)

powershell python C:\microbu-pki-wt\tools\vendor\v2x2map-0.3.0\bridge\its_g5_bridge.py --port COM5 --node-id V2X2MAP:001122334455 --no-mqtt --open-browser --pcap-out C:\microbu-pki-wt\rx_v2x2map.pcap `

  • Automatically opens the live dashboard at http://127.0.0.1:8080.
  • --pcap-out <file.pcap> continuously records all received 802.11 frames to disk.
  • Alternatively, click Record and Stop in the web dashboard: a direct Download PCAP button appears to download the file, which is also saved under ools/vendor/v2x2map-0.3.0/bridge/recordings/.

4. Transmit signed VAMs via Phone Emulator (Terminal 3)

powershell python C:\microbu-pki-wt\implementation\station-link\python\phone_emulator.py --port COM4 --radio txrx --mirror --pcap C:\microbu-pki-wt\tx_vams.pcap --pki-url http://127.0.0.1:8092/ --pki-root C:\microbu-pki-wt\pki-lab\chain\ROOT.oer --pki-ea C:\microbu-pki-wt\pki-lab\chain\EA.oer --pki-aa C:\microbu-pki-wt\pki-lab\chain\AA.oer --pki-canonical-key C:\microbu-pki-wt\pki-lab\canonical.pem --pki-its-id vidf-vru-station --pki-issue-tool C:\bachelorthesis_micrOBU\implementation\external\vanetza-idf\build-host-sec\vidf_issue.exe --pki-client-tool C:\microbu-pki-wt\implementation\external\vanetza-idf\ports\esp_idf\tools\pki_client.py --pki-bundle-tool C:\microbu-pki-wt\implementation\external\vanetza-idf\ports\esp_idf\tools\credential_bundle.py --duration 30

  • --mirror --pcap tx_vams.pcap ensures the transmitter saves an exact PCAP capture of all generated and signed GNPDUs.
  • Omit --duration 30 to transmit continuously until Ctrl+C.

Where recordings are saved

Recording Destination Description
Transmitter (TX) PCAP --pcap Local 802.11 PCAP captured via the USB test channel (--mirror copies all frames while the radio transmits).
Receiver (RX) PCAP (CLI) --pcap-out Continuous capture of all over-the-air frames received by the ESP32-C5 on COM5.
Receiver (RX) PCAP (Web) ools/vendor/v2x2map-0.3.0/bridge/recordings/ Saved automatically when clicking Record and Stop in the web dashboard, with a direct Download PCAP button in the browser.
# WSL: the security campaigns with the compiled AtsSecurity (docs/idf/test-campaigns.md of the submodule)
python3 run_etsi.py --titan ... --binary /root/vidf-ats-security/AtsSecurity \
  --config tests/etsi_security_gn.cfg --expected-cases tests/etsi_security_gn_cases.json \
  --sut /usr/bin/python3 --sut-args "/root/sut_tcp_relay.py --connect <windows host>:7788" \
  --pool /root/vidf-pool --out /root/vidf-results/security-gn-microbu

Retained runs of 2026-09-14: experiments/raw/microbu-link-20260914/ (manifests in experiments/manifests/test-runs/microbu-link-20260914-*.yaml).

BLE verification (2026-09-16)

The retained host verification is described by experiments/manifests/test-runs/microbu-link-20260916-ble-host-01.yaml:

  • Python/C++ protocol and framing suite: 8 tests passed, including the common serial/BLE adapter contract and a 512-octet message split into 32 default-MTU values, reassembly, malformed interleaving rejection and recovery.
  • Kotlin/JVM station-link suite: 4 tests passed against the Python/C++ golden bytes, including fragmentation, recovery and credential segmentation.
  • Android :app:compileDebugKotlin: passed with the BLE central and manifest permissions included.
  • ESP-IDF 6.0.2 ESP32-C5 build: passed with NimBLE and the encrypted GATT peripheral; the binary is 0x1d8780 bytes and leaves 38% of the app partition.

No ESP32 serial device or Pixel 9 was attached to this laptop during this run, so pairing, notification exchange, the <= 2 s reconnection requirement and the connection-loss-rate requirement were not measured. TEST-AC-SYS-MICROBU-013 and TEST-AC-SYS-MICROBU-014 intentionally remain planned hardware tests.