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).
This commit is contained in:
Ashin Walpola
2026-09-23 17:46:40 +02:00
parent 2f60623e18
commit 0e9525162d
9881 changed files with 1582523 additions and 17 deletions
+461
View File
@@ -0,0 +1,461 @@
# 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).
```powershell
# 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:
```powershell
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 <filename> | Local 802.11 PCAP captured via the USB test channel (--mirror copies all frames while the radio transmits). |
| **Receiver (RX) PCAP (CLI)** | --pcap-out <filename> | 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. |
```sh
# 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.