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.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// micrOBU firmware: the ESP32-C5 half of the VRU ITS-S on vanetza-idf.
|
||||
// One station task owns stack, security entity and radio; the serial reader task only
|
||||
// enqueues frames. Link messages: implementation/station-link/README.md.
|
||||
#include "link_protocol.hpp"
|
||||
#include "link_service.hpp"
|
||||
#include "board_controls.hpp"
|
||||
#include "simple_ble.hpp"
|
||||
#include "serial_link.hpp"
|
||||
#include "station.hpp"
|
||||
#if CONFIG_MICROBU_TEST_CHANNEL
|
||||
#include "test_channel.hpp"
|
||||
#endif
|
||||
#include <esp_log.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_timer.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <new>
|
||||
|
||||
namespace {
|
||||
const char* TAG = "microbu";
|
||||
|
||||
/// @brief Represents one frame received from a transport, to be queued for the station task.
|
||||
struct Incoming {
|
||||
microbu::LinkTransport transport;
|
||||
microbu::serial::Frame frame;
|
||||
};
|
||||
|
||||
QueueHandle_t frames = nullptr; // FIFO of Incoming* (8 bytes each); frame bytes never enter the queue itself
|
||||
|
||||
/// @brief Attempts to heap-allocate an Incoming so it can be pushed onto the frames queue.
|
||||
/// @param transport Transport the frame arrived on.
|
||||
/// @param frame Decoded frame to take ownership of.
|
||||
/// @return Owning pointer to push onto the queue, or nullptr if allocation failed.
|
||||
Incoming* try_new_incoming(microbu::LinkTransport transport, microbu::serial::Frame frame) noexcept {
|
||||
try {
|
||||
// std::move here steals frame's payload buffer into the heap Incoming (no byte copy);
|
||||
// it has nothing to do with the queue below, which only ever transports the resulting pointer.
|
||||
return new Incoming {transport, std::move(frame)};
|
||||
} catch (const std::bad_alloc&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Dispatches one queued frame to the link service (or the test channel); logs and drops it on failure.
|
||||
void handle_incoming(microbu::Station& station, microbu::LinkService& link, const Incoming& incoming) {
|
||||
try {
|
||||
if (incoming.frame.type == microbu::serial::FrameType::LINK)
|
||||
link.handle(incoming.frame.payload, incoming.transport);
|
||||
#if CONFIG_MICROBU_TEST_CHANNEL
|
||||
else if (incoming.transport == microbu::LinkTransport::serial &&
|
||||
incoming.frame.type == microbu::serial::FrameType::TEST)
|
||||
microbu::serial::write(microbu::serial::FrameType::TEST,
|
||||
microbu::test_channel_execute(station, incoming.frame.payload));
|
||||
#endif
|
||||
} catch (const std::bad_alloc&) {
|
||||
ESP_LOGE(TAG, "out of memory while handling a frame");
|
||||
} catch (const std::exception& e) {
|
||||
ESP_LOGE(TAG, "frame handling failed: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief MicrOBU: BLE and ITS-G5 share the C5's one RF front end. While the phone is talking over
|
||||
/// USB there is no reason for BLE to take airtime, so advertising stops until the USB link has been
|
||||
/// quiet for CONFIG_MICROBU_BLE_USB_IDLE_MS. A phone already connected over BLE is left connected.
|
||||
void pause_ble_while_usb_in_use() {
|
||||
const auto last = microbu::serial::last_frame_ms();
|
||||
const auto now = static_cast<std::uint32_t>(esp_timer_get_time() / 1000);
|
||||
const bool usb_in_use = last != 0 && now - last < CONFIG_MICROBU_BLE_USB_IDLE_MS;
|
||||
microbu::ble::set_advertising_allowed(!usb_in_use);
|
||||
}
|
||||
|
||||
/// @brief FreeRTOS task body: owns the station, link service and board controls. Drains the incoming queue.
|
||||
void station_task(void*) {
|
||||
microbu::Station station;
|
||||
microbu::LinkService link(station);
|
||||
microbu::BoardControls controls;
|
||||
station.on_disseminated([&controls] { controls.blink(); });
|
||||
station.on_received([&controls] { controls.blink(); });
|
||||
ESP_LOGI(TAG, "station task ready; the phone/PC configures the station over BLE or USB Serial-JTAG");
|
||||
for (;;) { /// main loop: drain the incoming queue, tick the station and link service, tick the controls
|
||||
Incoming* incoming = nullptr;
|
||||
// MicrOBU: at least one tick. At CONFIG_FREERTOS_HZ=100 pdMS_TO_TICKS(5) is 0, so the wait
|
||||
// never blocked: this priority-10 task spun on the single core, starved every lower-priority
|
||||
// task and the idle task (task watchdog), for as long as the board ran.
|
||||
if (xQueueReceive(frames, &incoming, std::max<TickType_t>(1, pdMS_TO_TICKS(5))) == pdTRUE && incoming) {
|
||||
handle_incoming(station, link, *incoming);
|
||||
delete incoming;
|
||||
}
|
||||
station.tick();
|
||||
link.tick();
|
||||
controls.tick();
|
||||
pause_ble_while_usb_in_use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Firmware entry point: initializes NVS, watchdog, serial/BLE transports, and starts the station task.
|
||||
extern "C" void app_main() {
|
||||
// NVS: the credential store (and the Wi-Fi driver's calibration data)
|
||||
esp_err_t nvs = nvs_flash_init();
|
||||
if (nvs == ESP_ERR_NVS_NO_FREE_PAGES || nvs == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
nvs = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(nvs);
|
||||
// Signing and verification take tens of milliseconds each on the C5. This keeps the idle
|
||||
// watchdog from reporting a busy station task (5 s default) while a credential bundle applies.
|
||||
esp_task_wdt_config_t watchdog = {};
|
||||
watchdog.timeout_ms = 30000;
|
||||
watchdog.idle_core_mask = (1 << portNUM_PROCESSORS) - 1;
|
||||
watchdog.trigger_panic = false; // log, don't reboot, on timeout
|
||||
esp_task_wdt_reconfigure(&watchdog);
|
||||
|
||||
frames = xQueueCreate(32, sizeof(Incoming*));
|
||||
microbu::serial::start([](microbu::serial::Frame frame) {
|
||||
auto* copy = try_new_incoming(microbu::LinkTransport::serial, std::move(frame));
|
||||
if (!copy) return;
|
||||
// xQueueSend copies the 8-byte pointer value into the queue, not *copy itself.
|
||||
// station_task then pops pointers FIFO and owns/deletes whatever it receives.
|
||||
if (xQueueSend(frames, ©, 0) != pdTRUE) delete copy; // the phone retries on a missing RESULT
|
||||
});
|
||||
if (!microbu::ble::start([](microbu::link::Bytes message) {
|
||||
microbu::serial::Frame frame {microbu::serial::FrameType::LINK, std::move(message)};
|
||||
auto* copy = try_new_incoming(microbu::LinkTransport::ble, std::move(frame));
|
||||
if (!copy) return;
|
||||
if (xQueueSend(frames, ©, 0) != pdTRUE) delete copy; // same hand-off as the serial callback above
|
||||
})) {
|
||||
ESP_LOGE(TAG, "BLE station link failed to start. USB remains available");
|
||||
}
|
||||
ESP_LOGI(TAG, "MicrOBU obu-firmware on vanetza-idf, station-link v1 + V2X_RX, console on UART0%s",
|
||||
#if CONFIG_MICROBU_TEST_CHANNEL
|
||||
", test channel enabled");
|
||||
#else
|
||||
"");
|
||||
#endif
|
||||
xTaskCreate(station_task, "station", 12288, nullptr, 10, nullptr);
|
||||
}
|
||||
Reference in New Issue
Block a user