Files
MicrOBU/obu-firmware/main/c5_radio.hpp
T
Ashin Walpola d2fd222a62 Sign ITS messages on the ESP32-C5 with vanetza-idf, over USB or BLE
obu-firmware is now a port of the colleague's standalone VRU station
(microbu-esp32c5/firmware, kept beside this repository and gitignored): the
vanetza-idf C-ITS stack with the TS 103 097 security entity, credentials in
NVS, the station-link v1 protocol over the native USB port (frame type 0x10
in the existing 0xAA55 framing) and over a BLE GATT peripheral, and its
ITS-G5 radio adapter. The phone still builds CAM and VAM; the board adds
GeoNetworking/BTP and signs with the provisioned authorization ticket. The
private key never leaves the board. Builds with ESP-IDF 6.0.2 only, which
vanetza-idf pins for the radio's private driver ABI. The previous C firmware
stays on disk unbuilt; a full-flash backup of the bench board is kept in
firmware-backups/ (gitignored).

Changed against the colleague's firmware, marked MicrOBU: in the sources:
- Reception unchanged for the app. vanetza-idf drops what it cannot verify
  (unsigned traffic, every RSU), so each captured frame also goes through the
  previous gn_unwrap.c and reaches the phone as link opcode V2X_RX (0x85),
  whose body is the old SERIAL_MSG_V2X_RX payload.
- Unsigned transmission still possible, with the previous geonet.c header;
  the phone chooses per message.
- Console on UART0 (CH343 port); the native USB port carries only link frames.
- BLE advertising pauses while the USB link is in use: BLE and ITS-G5 share
  one RF front end.
- NVS 80 KB (app at 0x20000). At 24 KB, with Wi-Fi settings the previous
  firmware left behind, the BLE bond could not be stored and the phone had to
  pair on every connection.
- Bench fixes: the radio queue is drained before the first PoTi (no RX and
  ~177 queue drops before); the station loop waited pdMS_TO_TICKS(5) = 0
  ticks at 100 Hz and starved the idle task; the 2.4 KB RX capture buffer is
  off the Wi-Fi task stack; BLE notifications longer than the MTU are dropped
  instead of cut short, MTU 517; serial writes are skipped with no USB host.
- Manual country policy and TX-power read-back from the previous radio setup;
  logs for BLE encryption changes and the number of stored bonds.

Verified on the bench board (COM3) with the phone over USB and BLE: CAM and
VAM, signed and unsigned, go out; reception of the sim car and the RSU's
CAM/SPATEM/MAPEM continues; the board survives app restarts and reconnects.
See docs/06-signed-its-vam-ble.md.
2026-09-23 17:27:54 +02:00

118 lines
5.2 KiB
C++

#pragma once
#include <vanetza_idf/access.hpp>
#include <esp_err.h>
#include <functional>
#include <memory>
namespace microbu {
struct C5RadioConfig {
std::uint16_t channel_number = 180;
double transmit_power_dbm = 10.0;
unsigned receive_queue_length = 16;
bool laboratory_transmission = true;
};
#if CONFIG_MICROBU_TEST_CHANNEL
/// Result of polling the PHY's own (undocumented) CCA state as fast as possible for a
/// fixed window (feasibility probe for EN 303 797 clause 4.6.2)
/// to determine if the platform can observe channel-busy state at >=1 kHz at all.
struct CcaSampleResult {
std::uint32_t samples = 0;
std::uint32_t duration_us = 0;
std::uint32_t min_delta_us = 0;
std::uint32_t max_delta_us = 0;
std::uint32_t busy_count = 0;
std::int32_t first_cca = 0;
std::int32_t last_cca = 0;
std::int32_t noise_floor_dbm = 0;
std::int32_t cca_total_cycles_delta = 0; // out[0]/0x600a7c5c: 40 MHz free-running counter
std::int32_t cca_busy_cycles_delta = 0; // out[1]/0x600a7c60: increments while channel busy
std::int32_t cca_status = 0;
};
#endif
/// Snapshot of the ESP32-C5 27-bit hardware PHY counters for ETSI TS 102 687 / EN 303 797 DCC.
/// total_cycles: 40 MHz FE clock cycles (register 0x600a7c5c[26:0]).
/// busy_cycles: cycles during which received RF energy exceeded the CCA threshold (register 0x600a7c60[26:0]).
struct CcaCounters {
std::uint32_t total_cycles = 0;
std::uint32_t busy_cycles = 0;
};
/**
* ITS-G5 802.11p Radio Access Adapter for ESP32-C5 (EN 303 797 Annex B.2).
* Direct Wi-Fi promiscuous RX mode on 5.9 GHz (ITS-G5 Channel 180, 10 MHz BW)
* and 802.11p frame transmission via esp_wifi_80211_tx_custom.
*/
class C5Radio final : public vanetza_idf::Access {
public:
using Receive = std::function<void(vanetza_idf::AlDataIndication)>;
using Capture = std::function<void(const vanetza::ByteBuffer&, int, std::uint32_t)>;
/// @brief Constructs a radio adapter with the given configuration (not yet started).
/// @param config Radio configuration; defaults to channel 180, 10 dBm, 16-deep RX queue.
explicit C5Radio(C5RadioConfig config = {});
/// @brief Stops the radio and releases all resources.
~C5Radio() override;
C5Radio(const C5Radio&) = delete;
C5Radio& operator=(const C5Radio&) = delete;
/// @brief Initializes Wi-Fi in promiscuous 802.11p mode and starts the radio.
/// @return ESP_OK on success, otherwise an ESP-IDF error code.
esp_err_t start();
/// @brief Stops the radio and releases all resources.
void stop();
// vanetza_idf::Access
/// @brief Encodes and transmits an ITS-G5 frame requested by the access layer.
/// @param request Frame and transmit parameters from the access layer.
/// @return Result of the transmit attempt.
vanetza_idf::Result request(vanetza_idf::AlDataRequest request) override;
/// @brief Dispatches queued received frames to the owning task callback.
/// @param receive Invoked once per queued frame with its decoded indication.
/// @param capture Optional; invoked with the raw frame bytes, RSSI and timestamp for diagnostics.
void poll(const Receive& receive, const Capture& capture = {});
#if CONFIG_MICROBU_TEST_CHANNEL
/// @brief Sends a burst of raw 802.11p test frames for RF characterization.
/// @param channel ITS-G5 channel number to transmit on.
/// @param power_dbm Transmit power in dBm.
/// @param mcs 802.11p modulation and coding scheme index.
/// @param count Number of frames to send.
/// @param interval_ms Interval between frames in milliseconds.
/// @param payload_len Length of each frame's payload in bytes.
/// @return ESP_OK if all frames were sent, otherwise the last transmit error.
esp_err_t transmit_burst(std::uint16_t channel, double power_dbm, unsigned mcs,
unsigned count, unsigned interval_ms, std::size_t payload_len);
/// @brief Polls the CCA state as fast as possible for a fixed window (see CcaSampleResult); requires start().
/// @note This is a diagnostic tool/ feasibility probe for EN 303 797 clause 4.6.2 to determine if the platform can observe channel-busy state at >=1 kHz at all.
/// @param duration_ms Duration of the sampling window in milliseconds.
/// @return Sampling statistics and counter deltas over the window.
CcaSampleResult sample_cca(unsigned duration_ms);
#endif
/// @brief Non-blocking read of the hardware CCA counters for periodic DCC evaluation.
/// Continuous 40 MHz hardware integration satisfies EN 303 797 Profile-1 (>=1 kHz) with zero CPU busy-wait.
/// @return Snapshot of total and busy cycle counts.
CcaCounters read_cca_counters() const;
/// @brief Calculates Channel Busy Ratio (CBR) between two counter snapshots: delta(busy) / delta(total).
/// @param current More recent counter snapshot.
/// @param previous Earlier counter snapshot.
/// @return CBR in [0, 1].
static double calculate_cbr(const CcaCounters& current, const CcaCounters& previous);
/// @brief Number of frames dropped since start() due to queue overflow or invalid length.
/// @return Dropped frame count.
std::uint32_t dropped_frames() const;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace microbu