diff --git a/.gitignore b/.gitignore index 4fe1f84..bf59e6a 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,16 @@ sdkconfig.old # Office lock files. Word/Excel create these beside a document while it is open # and remove them on close, so they are transient and machine-local. ~$* + +# Colleague's standalone ESP32-C5 VRU station (signed ITS via Vanetza). It is its +# own git repository with its own history; kept beside the project as a reference +# for porting the signed-TX path into obu-firmware, not tracked here. +/microbu-esp32c5/ + +# Full-flash images read back off the bench boards before reflashing them (16 MB each). +# Restore with: esptool --chip esp32c5 -p COM write-flash 0 +/firmware-backups/ + +# ESP-IDF component manager downloads (espressif/esp-boost for obu-firmware's vanetza-idf), ~125 MB. +# dependencies.lock beside the project pins them and is committed; this is its cache. +managed_components/ diff --git a/obu-firmware/CMakeLists.txt b/obu-firmware/CMakeLists.txt index e02013a..80d432c 100644 --- a/obu-firmware/CMakeLists.txt +++ b/obu-firmware/CMakeLists.txt @@ -1,9 +1,62 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.22) +# obu-firmware: the ESP32-C5 half of the MicrOBU station, on the vanetza-idf C-ITS stack. +# +# Since 2026-09-23 this is a port of the colleague's standalone VRU station +# (microbu-esp32c5/firmware, its own git repository beside this one and gitignored here). From it: +# BTP/GeoNetworking and the TS 103 097 security entity (vanetza-idf), the station-link message +# layer over native USB Serial/JTAG and BLE GATT, the NVS credential store, and the C5 radio adapter. +# Added here for this project: the raw receive path of the previous firmware (gn_unwrap.c, forwarded +# as link opcode V2X_RX) so unsigned and non-demo-signed traffic still reaches the phone, the +# unsigned transmit path of the previous firmware (geonet.c), and BLE pausing while USB is in use. +# See NOTES.md. +# +# ESP-IDF 6.0.2 exactly: the C5 radio's private Wi-Fi driver ABI (otm_tx_custom.c) is pinned to it +# by vanetza-idf's radio_c5.cmake and has only been validated there. The previous C firmware was +# built with IDF 6.1; see FLASHING.md for the export script of each. +# +# vanetza-idf is taken from the colleague's tree rather than vendored (it is ~90 MB). Override with +# -DVANETZA_IDF_DIR=... if it lives elsewhere. +if(NOT VANETZA_IDF_DIR) + set(VANETZA_IDF_DIR "${CMAKE_CURRENT_LIST_DIR}/../microbu-esp32c5/external/vanetza-idf") +endif() +if(NOT EXISTS "${VANETZA_IDF_DIR}/idf_component.yml") + message(FATAL_ERROR "vanetza-idf not found at ${VANETZA_IDF_DIR}: clone microbu-esp32c5 beside this " + "repository or pass -DVANETZA_IDF_DIR=") +endif() + +# Provenance guard for main/otm_tx_custom.c, copied unchanged from microbu-esp32c5/firmware/CMakeLists.txt: +# this checked-in copy is what microbu::C5Radio::request() links against for every transmission. +# Fail configure if either the pinned upstream or this file drifts from the reviewed revision. +set(_otm_upstream "${VANETZA_IDF_DIR}/ports/esp_idf/third_party/otm/main/tx_custom.c") +if(EXISTS "${_otm_upstream}") + file(READ "${_otm_upstream}" _otm_upstream_text) + string(REPLACE "\r\n" "\n" _otm_upstream_text "${_otm_upstream_text}") + string(SHA256 _otm_upstream_hash "${_otm_upstream_text}") + if(NOT _otm_upstream_hash STREQUAL "cb1dccfef96912ca59275e8a9102f41f56925b94a19d4a4629082aaeb779be1b") + message(FATAL_ERROR "OpenTrafficMap upstream tx_custom.c differs from the reviewed source revision (674e3412) -- review before updating main/otm_tx_custom.c and this hash") + endif() +endif() +set(_otm_checked_in "${CMAKE_CURRENT_LIST_DIR}/main/otm_tx_custom.c") +file(READ "${_otm_checked_in}" _otm_checked_in_text) +string(REPLACE "\r\n" "\n" _otm_checked_in_text "${_otm_checked_in_text}") +string(SHA256 _otm_checked_in_hash "${_otm_checked_in_text}") +if(NOT _otm_checked_in_hash STREQUAL "114693af99ce3866cfc767066d484e45822eaf15335d94a03626b60ac5777277") + message(FATAL_ERROR "main/otm_tx_custom.c differs from the reviewed copy -- review the change, then update this hash") +endif() + +list(APPEND EXTRA_COMPONENT_DIRS "${VANETZA_IDF_DIR}") +set(SDKCONFIG_DEFAULTS "${CMAKE_CURRENT_LIST_DIR}/sdkconfig.defaults") +set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -# No longer need -Wl,-zmuldefs here - that was only for main/wifi_patches.c's -# symbol-override attempt (which didn't work anyway; see docs/04-transmit-setup.md), -# and that file is no longer part of the build. Superseded by main/tx_custom.c, -# which bypasses the gate at a different layer instead of trying to override it. - project(obu_firmware) +add_compile_options(-Wno-error -Wno-cpp -Wno-error=implicit-function-declaration) +idf_component_get_property(vanetza_lib vanetza-idf COMPONENT_LIB) +if(vanetza_lib) + target_compile_options(${vanetza_lib} PRIVATE -Wno-error=implicit-function-declaration -Wno-error=cpp) +endif() +# GCC 15's stricter -Warray-bounds false-positives on NimBLE's fixed-size bond-store arrays +# (upstream Apache Mynewt code); demote to a warning so the component still builds. +idf_component_get_property(bt_lib bt COMPONENT_LIB) +if(bt_lib) + target_compile_options(${bt_lib} PRIVATE -Wno-error=array-bounds) +endif() diff --git a/obu-firmware/FLASHING.md b/obu-firmware/FLASHING.md index 54287d4..6efd82e 100644 --- a/obu-firmware/FLASHING.md +++ b/obu-firmware/FLASHING.md @@ -2,24 +2,38 @@ ## Two toolchains - use a dedicated terminal for each -This project builds against the receiver-firmware's pinned ESP-IDF **6.1**. -The separate `obu-cam-transmistter` project builds against the global ESP-IDF -**5.5.4**. Exporting both in one PowerShell window fails: the second export +Since 2026-09-23 this project builds against ESP-IDF **6.0.2** exactly +(`C:\Espressif\frameworks\esp-idf-v6.0.2`): it is the vanetza-idf port (see +NOTES.md), and vanetza-idf's `radio_c5.cmake` refuses any other version because +the raw TX path pokes private Wi-Fi driver structures only validated there. The +previous C firmware used the receiver firmware's IDF **6.1**; the separate +`obu-cam-transmistter` project builds against the global ESP-IDF **5.5.4**. +Exporting two of them in one PowerShell window fails: the second export inherits the first's `IDF_PYTHON_ENV_PATH` and reports every Python dependency as unmet. Don't run `install.bat` to "fix" that - open a fresh terminal, or clear the state with `$env:IDF_PYTHON_ENV_PATH = $null; $env:IDF_PATH = $null`. -## Every new PowerShell session +The build also needs the colleague's `microbu-esp32c5` checkout beside this +repository (gitignored here): vanetza-idf is taken from its +`external/vanetza-idf`. Pass `-DVANETZA_IDF_DIR=` to `idf.py` if it lives +elsewhere. The first build downloads `espressif/esp-boost` into +`managed_components/`. -Activate the toolchain (obu-firmware has no esp-idf of its own — reuse the -receiver firmware's already-installed checkout): +## Every new PowerShell session ```powershell Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -C:\Users\Ashin\Documents\micrOBU_workspace\its-g5-receiver-firmware\esp-idf\export.ps1 -idf.py --version +$env:IDF_TOOLS_PATH = "C:\Espressif" +C:\Espressif\frameworks\esp-idf-v6.0.2\export.ps1 +idf.py --version # v6.0.2 ``` +## Going back to the previous firmware + +`firmware-backups/` in the repository root (gitignored) holds a full-flash image +of the COM3 board as it was before the port, with the esptool command to write +it back in its README.txt. + ## Build & flash ```powershell @@ -97,10 +111,22 @@ Work down this list — each step isolates the layer below it. CAMs reach the ESP32 but `esp_wifi_80211_tx` rejects them — a radio problem, not a link problem. +## Connecting over Bluetooth instead + +Settings > Connection > ESP32-C5 > link: Bluetooth, then Connect. The board +advertises as `micrOBU-XXXX` (last two bytes of its BT MAC; `micrOBU-4AFA` on +COM3), but only while nothing uses its native USB port. Android asks to pair +the first time: passkey **123456** (fixed in `simple_ble.cpp`). The bond is kept +on both sides; the board keeps one bond, so pairing a second phone or a PC +replaces the first. Log lines on the console start with `cits_ble:`. + ## Notes - No `git submodule update` needed here — obu-firmware has no pinned submodule of its own, unlike its-g5-receiver-firmware. -- Don't use the global "ESP-IDF 5.5 PowerShell" shortcut — always export from - the receiver-firmware's pinned checkout, since this firmware's undocumented - PHY/driver internals were verified against that specific build. +- Don't use the global "ESP-IDF 5.5 PowerShell" shortcut or the receiver + firmware's 6.1 checkout — export from `esp-idf-v6.0.2`, the version the + vanetza-idf radio's undocumented driver internals were verified against. +- The console (ESP_LOG, boot messages, panics) stays on the UART-bridge port. + Opening it resets the board; do that only while nothing else depends on the + session. diff --git a/obu-firmware/NOTES.md b/obu-firmware/NOTES.md index fdaa5db..1a5b3bd 100644 --- a/obu-firmware/NOTES.md +++ b/obu-firmware/NOTES.md @@ -1,4 +1,57 @@ -# OBU transmit firmware - Phase 2 (in progress: HLN-SV DENM beacon) +# obu-firmware + +## Since 2026-09-23: signed ITS on vanetza-idf + +This firmware is a port of the colleague's standalone ESP32-C5 VRU station +(`microbu-esp32c5/firmware`, its own git repository kept beside this one and gitignored here). +From it: the vanetza-idf C-ITS stack (BTP, GeoNetworking, the TS 103 097 security entity with +credentials in NVS), the station-link protocol v1 (`link_protocol.*`, `link_service.*`) over the +native USB port (`serial_link.*`, frame type 0x10 in the same 0xAA55 framing as before) and over BLE +GATT (`simple_ble.*`), and the radio adapter (`c5_radio.*`, `otm_tx_custom.c`). Build with ESP-IDF +**6.0.2**; see FLASHING.md. + +The phone builds CAM or VAM, configures the station, provisions credentials, sends PoTi and hands +each message over as a BTP-DATA.request; the firmware adds GN/BTP, signs with the authorization +ticket, and transmits. The phone side is `Esp32Link.kt` in the app. + +Changed or added for this project (search for `MicrOBU:` in the sources): + +- **Reception stays as it was.** vanetza-idf decapsulates with `itsGnSnDecapResultHandling = + STRICT`, so it drops unsigned traffic (the bench sim car) and everything signed under a root other + than the provisioned demo root (every RSU). Every captured frame therefore also goes through the + previous firmware's `gn_unwrap.c` and reaches the phone as link opcode `V2X_RX` (0x85), whose body + is exactly the old `SERIAL_MSG_V2X_RX` payload (`Station::forward_raw`). The app's receive side is + unchanged. +- **Unsigned transmission is still possible.** The colleague's station refuses unsecured requests. + Here a request with GN security profile 1 goes out with the previous firmware's `geonet.c` header + and the position of the last PoTi (`Station::unsecured_request`); the app's "Sign outgoing + messages" setting decides per message. +- **Console on UART0.** ESP_LOG stays on the CH343 bridge port (COM3 on the bench); the native port + carries only link frames. The colleague's single-port board routes the log into LOG frames there + (`CONFIG_MICROBU_LOG_OVER_LINK`, off here). +- **BLE pauses while USB is in use** (`CONFIG_MICROBU_BLE_USB_IDLE_MS`, 3 s): BLE and ITS-G5 share + the C5's one RF front end. Whether a live BLE connection disturbs 5.9 GHz is still unmeasured + (TODO.md). +- A serial write no longer stalls the station task when no USB host is present (BLE-only use). +- A notification longer than the ATT MTU is dropped instead of silently truncated. +- The Wi-Fi RX callback's 2.4 KB capture buffer is static rather than on the driver task's stack + (the previous firmware's commit 04b0076 fixed the same risk). +- Manual country policy and the TX-power read-back from the previous firmware's radio bring-up. +- The activity LED (GPIO27 on the colleague's XIAO board) is off unless configured. + +Credentials: the app ships `assets/demo-chain.vcr`, a throwaway chain generated 2026-09-23 with the +colleague's `vidf_issue` (root `6E7D0374FB021901`, AA `B3312F29844299E0`, AT `B80B49387A4C12EB` +valid two years, permissions psid 36 SSP `010000` and psid 638 SSP `01`). The colleague's own demo +chain only grants psid 638 and so cannot sign CAMs. Not EU-registered: receivers that verify against +the EU trust list drop what it signs. + +Time: the signature's generationTime comes from the PoTi timestamp, which follows the app's +`ItsTime` convention (UTC-based, no leap seconds). The colleague's VBS adds the 5 leap seconds. +Which one is right is the open question documented in `ItsTime.kt`. + +## Earlier notes (Phase 2, superseded) + +### OBU transmit firmware - Phase 2 (in progress: HLN-SV DENM beacon) Started. See `docs/04-transmit-setup.md` in the project root for build/flash steps and how to validate this against your own sniffer. diff --git a/obu-firmware/dependencies.lock b/obu-firmware/dependencies.lock new file mode 100644 index 0000000..e1420d5 --- /dev/null +++ b/obu-firmware/dependencies.lock @@ -0,0 +1,21 @@ +dependencies: + espressif/esp-boost: + component_hash: 45cfa63ade2ad7c489203ab2ddf3f33be50ec9cab752b6eb17a79f06aee8f8f0 + dependencies: + - name: idf + require: private + version: '>=5.3' + source: + registry_url: https://components.espressif.com/ + type: service + version: 0.4.1 + idf: + source: + type: idf + version: 6.0.2 +direct_dependencies: +- espressif/esp-boost +- idf +manifest_hash: 3fca1283d556ade5b08c63857e23cb3b08582f1d1c24bb7c1d5e374f438415d7 +target: esp32c5 +version: 3.0.0 diff --git a/obu-firmware/main/CMakeLists.txt b/obu-firmware/main/CMakeLists.txt index 5c012fc..ebe0905 100644 --- a/obu-firmware/main/CMakeLists.txt +++ b/obu-firmware/main/CMakeLists.txt @@ -1,18 +1,22 @@ -# wifi_patches.c is intentionally NOT in this list anymore - superseded by -# tx_custom.c (see that file for why). Left on disk, unused, for history. +# C++ station (from microbu-esp32c5/firmware/main) plus the C files of the previous firmware that +# still do a job here: +# gn_unwrap.c - raw receive path: 802.11/LLC-SNAP/GeoNetworking/BTP-B down to the ITS payload, +# for every frame on air, signed or not (station.cpp, forward_raw). +# geonet.c - unsigned transmit path: GN SHB + BTP-B exactly as the previous firmware built it +# (station.cpp, unsecured_request). # -# cam.c is ALSO intentionally not in this list anymore (Phase 03): CAM is now built on the -# phone and sent down over serial_link, so this firmware never encodes CAM itself. cam.c/.h are -# left on disk as the byte-exact reference the Kotlin encoder was ported from - do not delete. -# -# serial_link.c/.h - binary phone<->ESP32 framing over the native USB Serial/JTAG port -# (Phase 03; changed from a GPIO UART1 wire to native USB because neither -# USB-C port on the ESP32-C5-WIFI6-KIT was actually routed to that UART). -# gn_unwrap.c/.h - strips 802.11/LLC-SNAP/GeoNetworking/BTP-B off received frames down to CAM -# UPER bytes, for forwarding to the phone over serial_link. -idf_component_register( - SRCS "main.c" "denm.c" "geonet.c" "dot11p.c" "tx_custom.c" "serial_link.c" "gn_unwrap.c" - INCLUDE_DIRS "." - REQUIRES esp_event esp_netif nvs_flash driver esp_phy esp_driver_gpio esp_driver_usb_serial_jtag - PRIV_REQUIRES esp_wifi -) +# Left on disk and NOT built, like cam.c before them: +# main.c, serial_link.c/.h - the previous firmware's entry point and phone link (frame types +# 0x01-0x05). Superseded by app_main.cpp and the station-link protocol. +# dot11p.c/.h - previous 802.11 framing; c5_radio.cpp frames through vanetza-idf now. +# Still compiled by the host tests in test/host. +# tx_custom.c/.h - previous copy of the OpenTrafficMap raw TX; otm_tx_custom.c replaces it. +# denm.c/.h, cam.c/.h, wifi_patches.c - reference only, as before. +idf_component_register(SRCS "app_main.cpp" "board_controls.cpp" "simple_ble.cpp" "c5_radio.cpp" "otm_tx_custom.c" + "link_protocol.cpp" "serial_link.cpp" "station.cpp" "station_test.cpp" + "link_service.cpp" "test_channel.cpp" + "gn_unwrap.c" "geonet.c" + INCLUDE_DIRS "." + LDFRAGMENTS "linker.lf" + REQUIRES vanetza-idf esp_wifi esp_phy esp_event bt esp_driver_gpio esp_driver_usb_serial_jtag nvs_flash esp_timer) +target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17) diff --git a/obu-firmware/main/Kconfig.projbuild b/obu-firmware/main/Kconfig.projbuild new file mode 100644 index 0000000..93b3b09 --- /dev/null +++ b/obu-firmware/main/Kconfig.projbuild @@ -0,0 +1,39 @@ +menu "micrOBU firmware" +config MICROBU_TEST_CHANNEL + bool "Test channel on the serial link (software lower tester, ETSI campaign hooks)" + default y + help + Serves serial frame type 0x11: mirror or divert every AL_DATA.request the stack + makes to the tester, inject AL_DATA.indication, GN-DATA.request for the + Security ATS. Not part of the phone interface; disable for a production image. +config MICROBU_LOG_OVER_LINK + bool "Also carry ESP_LOG output as LOG frames (0x7F) on the native USB link" + default n + help + The colleague's single-port board has no other place for its log. This board keeps + the console on UART0 (the CH343 bridge port), so the phone's link carries no text. +config MICROBU_BLE_USB_IDLE_MS + int "USB link idle time before BLE advertising resumes (ms)" + range 1000 60000 + default 3000 + help + BLE and the 5.9 GHz radio share the C5's single RF front end. While the phone is + using the USB link (any frame received within this window), BLE advertising is + stopped so it cannot take airtime from ITS-G5. An existing BLE connection is left + alone. The phone sends a PoTi update with every GNSS fix, which keeps the window + open for as long as it is connected over USB. +config MICROBU_TX_LED_GPIO + int "Active-low activity LED GPIO (-1: none)" + range -1 28 + default -1 + help + The colleague's XIAO ESP32-C5 has its yellow user LED on GPIO27. On this project's + board GPIO27 is not a plain LED, so the indicator is off unless configured. +config MICROBU_TX_LED_BLINK_MS + int "LED indication pulse (ms)" + range 10 2000 + default 100 +config MICROBU_LED_INVERTED_RX + bool "Inverted LED mode for receiver/bridge (normally ON, blink OFF on packet)" + default n +endmenu diff --git a/obu-firmware/main/app_main.cpp b/obu-firmware/main/app_main.cpp new file mode 100644 index 0000000..a7d378c --- /dev/null +++ b/obu-firmware/main/app_main.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(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); +} diff --git a/obu-firmware/main/board_controls.cpp b/obu-firmware/main/board_controls.cpp new file mode 100644 index 0000000..7847a54 --- /dev/null +++ b/obu-firmware/main/board_controls.cpp @@ -0,0 +1,65 @@ +#include "board_controls.hpp" + +#include +#include +#include + +namespace microbu { +namespace { +const char* TAG = "board"; +constexpr std::int64_t blink_us = CONFIG_MICROBU_TX_LED_BLINK_MS * 1000LL; +} + +BoardControls::BoardControls() { +#if CONFIG_MICROBU_TX_LED_GPIO < 0 + ESP_LOGI(TAG, "no activity LED configured"); +#else + gpio_config_t led = {}; + led.pin_bit_mask = 1ULL << CONFIG_MICROBU_TX_LED_GPIO; + led.mode = GPIO_MODE_OUTPUT; + led.pull_up_en = GPIO_PULLUP_DISABLE; + led.pull_down_en = GPIO_PULLDOWN_DISABLE; + led.intr_type = GPIO_INTR_DISABLE; + ESP_ERROR_CHECK(gpio_config(&led)); +#if CONFIG_MICROBU_LED_INVERTED_RX + mode_ = LedMode::inverted_rx; +#else + mode_ = LedMode::normal_tx; +#endif + apply_led_state(); + ESP_LOGI(TAG, "LED GPIO %d (mode: %s)", CONFIG_MICROBU_TX_LED_GPIO, + mode_ == LedMode::inverted_rx ? "inverted_rx (normally ON)" : "normal_tx (normally OFF)"); +#endif +} + +void BoardControls::apply_led_state() { +#if CONFIG_MICROBU_TX_LED_GPIO >= 0 + // The XIAO ESP32-C5 user LED is wired active-low (0 = ON, 1 = OFF). + // In normal_tx mode: default OFF (1), pulsing ON (0). + // In inverted_rx mode: default ON (0), pulsing OFF (1). + bool led_illuminated = false; + if (mode_ == LedMode::normal_tx) { + led_illuminated = pulsing_; + } else { + led_illuminated = !pulsing_; + } + gpio_set_level(static_cast(CONFIG_MICROBU_TX_LED_GPIO), led_illuminated ? 0 : 1); +#endif +} + +void BoardControls::blink() { + pulse_until_us_ = esp_timer_get_time() + blink_us; + if (!pulsing_) { + pulsing_ = true; + apply_led_state(); + } +} + +void BoardControls::tick() { + if (pulsing_ && esp_timer_get_time() >= pulse_until_us_) { + pulsing_ = false; + apply_led_state(); + } +} + +} // namespace microbu diff --git a/obu-firmware/main/board_controls.hpp b/obu-firmware/main/board_controls.hpp new file mode 100644 index 0000000..9be896c --- /dev/null +++ b/obu-firmware/main/board_controls.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace microbu { + +/// XIAO ESP32-C5 active-low user LED: a visual indicator for TX/RX activity. +class BoardControls { +public: + /// @brief Configures the LED GPIO and sets its idle state. + BoardControls(); + + enum class LedMode { + normal_tx, // Normally off, pulses on upon frame dissemination + inverted_rx // Normally on, pulses off upon frame reception + }; + + /// @brief Triggers visual indication (blink on in normal_tx mode, blink off in inverted_rx mode). + void blink(); + /// @brief Updates LED state and checks indication timers; call periodically. + void tick(); + +private: + void apply_led_state(); + + LedMode mode_ = LedMode::normal_tx; + bool pulsing_ = false; + std::int64_t pulse_until_us_ = 0; +}; + +} // namespace microbu diff --git a/obu-firmware/main/c5_radio.cpp b/obu-firmware/main/c5_radio.cpp new file mode 100644 index 0000000..ea39042 --- /dev/null +++ b/obu-firmware/main/c5_radio.cpp @@ -0,0 +1,469 @@ +#include "c5_radio.hpp" +#include "otm_tx_custom.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { //all of these arentt in the esp-idf public api +// phy_11p_set/phy_change_channel: undocumented esp_phy/lib/esp32c5/libphy.a entry points, not +// declared in any Espressif header. +// The call sites and argument values below (phy_11p_set(1, 0), phy_change_channel(freq, 1, 0, 0)) +// are copied from OpenTrafficMap's its-g5-receiver-firmware_txenabled, main/cmd_sniffer.c +// (https://codeberg.org/opentrafficmap/its-g5-receiver-firmware_txenabled, community reverse +// engineering, no stated license). + +void phy_11p_set(int enable, int arg2); +void phy_change_channel(int freq_mhz, int arg2_ignored, int arg3_ignored, int arg4); + +// phy_get_cca/phy_set_cca: register 0x600a701c[7:0] holds the configured CCA energy +// detection threshold (defaults to 191 = 0xBF = -65 dBm in 8-bit two's complement). +// phy_get_cca() reads this configured threshold. +int phy_get_cca(void); +void phy_set_cca(int enable, int threshold); + +// phy_get_cca_cnt/phy_set_cca_cnt: register 0x600a7c58 arms the 27-bit hardware CCA +// cycle counters (0x600a7c5c = total cycles, 0x600a7c60 = busy cycles; confirmed on +// hardware -- out[0] free-runs at ~40 MHz, out[1] stays near zero on a quiet channel). +// phy_get_cca_cnt returns bit 27 (busy/status bit) and writes both counters to out[2]. +int phy_get_cca_cnt(std::int32_t out[2]); +void phy_set_cca_cnt(std::int32_t val, bool enable); +void phy_enable_cca(void); +void phy_disable_cca(void); +int phy_get_noise_floor(void); +} + +namespace microbu { +namespace { +const char* TAG = "c5_radio"; + +} + +class C5Radio::Impl { +public: + /// @brief Raw received frame metadata and bytes from the promiscuous RX callback. + struct Raw { + std::uint16_t length; + std::int8_t rssi; + std::uint32_t timestamp; + std::uint8_t bytes[2346]; // max 802.11 frame size + }; + C5RadioConfig config; + QueueHandle_t queue = nullptr; + bool initialized = false, started = false, own_event_loop = false; + std::uint16_t sequence = 0; + std::atomic dropped {0}; + static Impl* active; + static std::mutex callback_mutex; + + explicit Impl(C5RadioConfig c) : config(c) {} + + static void receive(void* buffer, wifi_promiscuous_pkt_type_t type) { + if (!buffer || type != WIFI_PKT_DATA) return; + const auto* packet = static_cast(buffer); + if (packet->rx_ctrl.rx_state != 0) return; + const auto length = packet->rx_ctrl.sig_len; + std::lock_guard lock(callback_mutex); + if (!active || !active->queue) return; + // MicrOBU: a frame too short to hold any GN packet is not ITS traffic, so it is ignored + // rather than counted; dropped_frames() then means what the phone shows it as: lost frames. + if (length < 38) return; + if (length > sizeof(Raw::bytes)) { ++active->dropped; return; } + // MicrOBU: static, not a local. Raw is ~2.4 KB and this runs on the Wi-Fi driver's own task, + // several frames deep, on a stack of roughly 3.5 KB: the previous firmware hit exactly this + // with an 800-byte buffer (obu-firmware commit 04b0076). Safe as a static because only that + // one task calls this, and xQueueSend copies it out before the next call. + static Raw raw; + raw.length = length; + raw.rssi = packet->rx_ctrl.rssi; + raw.timestamp = packet->rx_ctrl.timestamp; + std::memcpy(raw.bytes, packet->payload, length); + if (xQueueSend(active->queue, &raw, 0) != pdTRUE) ++active->dropped; + } +}; + +C5Radio::Impl* C5Radio::Impl::active = nullptr; +std::mutex C5Radio::Impl::callback_mutex; + +C5Radio::C5Radio(C5RadioConfig c) : impl_(std::make_unique(c)) {} +C5Radio::~C5Radio() { stop(); } + +esp_err_t C5Radio::start() { + auto& p = *impl_; + const auto& c = p.config; + if (p.initialized) return ESP_ERR_INVALID_STATE; + // Reject channel/power/queue config outside the supported ITS-G5 range + if (c.channel_number < 172 || c.channel_number > 184 || c.channel_number % 2 || + !std::isfinite(c.transmit_power_dbm) || c.transmit_power_dbm < 2 || c.transmit_power_dbm > 23 || + std::floor(c.transmit_power_dbm * 4) != c.transmit_power_dbm * 4 || + c.receive_queue_length == 0 || c.receive_queue_length > 32) { + return ESP_ERR_INVALID_ARG; + } + + { + std::lock_guard lock(Impl::callback_mutex); + if (Impl::active) return ESP_ERR_INVALID_STATE; + p.queue = xQueueCreate(c.receive_queue_length, sizeof(Impl::Raw)); + if (!p.queue) return ESP_ERR_NO_MEM; + Impl::active = &p; + } + + auto result = esp_event_loop_create_default(); + p.own_event_loop = (result == ESP_OK); + if (result != ESP_OK && result != ESP_ERR_INVALID_STATE) { + stop(); + return result; + } + + // Establish modem FE clock for 802.11p OFDM + modem_syscon_ll_enable_fe_40m_clock(&MODEM_SYSCON, true); + + wifi_init_config_t wifi = WIFI_INIT_CONFIG_DEFAULT(); + wifi.nvs_enable = 0; + result = esp_wifi_init(&wifi); + if (result != ESP_OK) { + stop(); + return result; + } + p.initialized = true; + + auto attempt = [&](esp_err_t r) { if (result == ESP_OK) result = r; }; + attempt(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + attempt(esp_wifi_set_mode(WIFI_MODE_STA)); + if (result == ESP_OK) { + result = esp_wifi_start(); + p.started = (result == ESP_OK); + } + if (result != ESP_OK) { + stop(); + return result; + } + + // MicrOBU, from the previous firmware: under the default WIFI_COUNTRY_POLICY_AUTO the driver's + // 5 GHz table does not authorise transmission on the ITS band, which there left RX working and + // TX silent. The colleague's board transmits without this (esp_wifi_80211_tx_custom goes around + // that gate), so it is belt and braces here: manual policy, every 5 GHz channel enabled. Not + // fatal if refused. + wifi_country_t country = {}; + country.cc[0] = 'U'; country.cc[1] = 'S'; + country.schan = 1; + country.nchan = 11; + country.policy = WIFI_COUNTRY_POLICY_MANUAL; + country.wifi_5g_channel_mask = 0x1FFFFFFE; + if (const auto e = esp_wifi_set_country(&country); e != ESP_OK) + ESP_LOGW(TAG, "esp_wifi_set_country(MANUAL) failed: %s (continuing)", esp_err_to_name(e)); + attempt(esp_wifi_set_band_mode(WIFI_BAND_MODE_5G_ONLY)); + attempt(esp_wifi_set_ps(WIFI_PS_NONE)); + attempt(esp_wifi_set_max_tx_power(static_cast(c.transmit_power_dbm * 4))); + + wifi_promiscuous_filter_t filter {}; + filter.filter_mask = WIFI_PROMIS_FILTER_MASK_DATA; + attempt(esp_wifi_set_promiscuous_filter(&filter)); + attempt(esp_wifi_set_promiscuous_rx_cb(Impl::receive)); + attempt(esp_wifi_set_promiscuous(true)); + if (result != ESP_OK) { + stop(); + return result; + } + + // 10 MHz channel bandwidth (ITS-G5 / 802.11p) + phy_11p_set(1, 0); + phy_change_channel(5000 + 5 * c.channel_number, 1, 0, 0); // = 5900 MHz + + // Enable and arm hardware CCA counters (40 MHz baseband clock timebase) for DCC + phy_enable_cca(); + phy_set_cca_cnt(0x07FFFFFF, true); + + // MicrOBU, from the previous firmware: the power request is a ceiling, not a promise. The driver + // clamps it to its calibrated table, and 5900 MHz is above the chip's rated range, so log what + // the driver admits to rather than what was asked for. + std::int8_t power_q = 0; + if (esp_wifi_get_max_tx_power(&power_q) == ESP_OK) { + ESP_LOGI(TAG, "tx power: %d quarter-dBm = %d.%02d dBm (%.2f requested)", + power_q, power_q / 4, (power_q % 4) * 25, c.transmit_power_dbm); + } + + ESP_LOGI(TAG, "ITS-G5 802.11p radio started on channel %u (5900 MHz), %s", + unsigned(c.channel_number), c.laboratory_transmission ? "TX/RX" : "RX only"); + return ESP_OK; +} + +void C5Radio::stop() { + if (!impl_) return; + auto& p = *impl_; + if (p.started) esp_wifi_set_promiscuous(false); + { + std::lock_guard lock(Impl::callback_mutex); + if (Impl::active == &p) Impl::active = nullptr; + if (p.queue) { + vQueueDelete(p.queue); + p.queue = nullptr; + } + } + if (p.started) esp_wifi_stop(); + if (p.initialized) esp_wifi_deinit(); + if (p.own_event_loop) esp_event_loop_delete_default(); + p.started = p.initialized = p.own_event_loop = false; +} + +vanetza_idf::Result C5Radio::request(vanetza_idf::AlDataRequest request) { + auto& p = *impl_; + if (!p.started) return vanetza_idf::Result::rejected; + if (!p.config.laboratory_transmission) return vanetza_idf::Result::unsupported; + + if (request.bandwidth_mhz != 10 || request.channel_number != p.config.channel_number || + request.transceiver_id != 0 || request.transceiver_mode || request.datastream_id || + request.transmit_power_dbm != p.config.transmit_power_dbm) { + return vanetza_idf::Result::unsupported; + } + + constexpr wifi_phy_rate_t rates[] = { + WIFI_PHY_RATE_6M, WIFI_PHY_RATE_9M, WIFI_PHY_RATE_12M, + WIFI_PHY_RATE_18M, WIFI_PHY_RATE_24M, WIFI_PHY_RATE_36M, + WIFI_PHY_RATE_48M, WIFI_PHY_RATE_54M + }; + const auto index = static_cast(request.mcs); + if (index >= std::size(rates)) return vanetza_idf::Result::invalid_argument; + + vanetza::ByteBuffer bytes; + const auto encoded = vanetza_idf::its_g5::encode_frame(request, p.sequence, bytes); + if (encoded != vanetza_idf::Result::accepted) { + ESP_LOGE(TAG, "encode_frame failed: %d", int(encoded)); + return encoded; + } + + p.sequence = (p.sequence + 1) & 4095; + wifi_tx_rate_config_t rate {}; + rate.phymode = WIFI_PHY_MODE_11A; + rate.rate = rates[index]; + + // Transmit frame via 802.11p driver + const auto result = esp_wifi_80211_tx_custom( + WIFI_IF_STA, bytes.data(), bytes.size(), false, + &rate, WIFI_BAND_5G, WIFI_BW20); + + if (result != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_80211_tx_custom failed: %s (0x%x)", esp_err_to_name(result), result); + } + + return result == ESP_OK ? vanetza_idf::Result::accepted : + result == ESP_ERR_NO_MEM ? vanetza_idf::Result::resource_limit : + vanetza_idf::Result::rejected; +} + +void C5Radio::poll(const Receive& receive, const Capture& capture) { + auto& p = *impl_; + if (!p.queue) return; + Impl::Raw raw {}; + for (unsigned i = 0; i < p.config.receive_queue_length && xQueueReceive(p.queue, &raw, 0) == pdTRUE; ++i) { + if (capture) { + capture(vanetza::ByteBuffer(raw.bytes, raw.bytes + raw.length), raw.rssi, raw.timestamp); + } + vanetza_idf::AlDataIndication ind; + if (vanetza_idf::its_g5::decode_frame(raw.bytes, raw.length, true, ind) != vanetza_idf::Result::accepted) { + continue; + } + ind.channel_number = p.config.channel_number; + ind.received_power_dbm = raw.rssi; + if (receive) receive(std::move(ind)); + } +} + +std::uint32_t C5Radio::dropped_frames() const { + return impl_->dropped.load(); +} + +#if CONFIG_MICROBU_TEST_CHANNEL +esp_err_t C5Radio::transmit_burst(std::uint16_t channel, double power_dbm, unsigned mcs, + unsigned count, unsigned interval_ms, std::size_t payload_len) { + auto& p = *impl_; + if (!p.started) return ESP_ERR_INVALID_STATE; + if (channel < 172 || channel > 184 || channel % 2 != 0) return ESP_ERR_INVALID_ARG; + if (power_dbm < 2.0 || power_dbm > 20.0) return ESP_ERR_INVALID_ARG; + if (mcs > 7) return ESP_ERR_INVALID_ARG; + if (count == 0) return ESP_OK; + + constexpr wifi_phy_rate_t rates[] = { + WIFI_PHY_RATE_6M, WIFI_PHY_RATE_9M, WIFI_PHY_RATE_12M, + WIFI_PHY_RATE_18M, WIFI_PHY_RATE_24M, WIFI_PHY_RATE_36M, + WIFI_PHY_RATE_48M, WIFI_PHY_RATE_54M + }; + + // Dynamically retune channel or adjust TX power if different from running config + if (channel != p.config.channel_number) { + phy_11p_set(1, 0); + phy_change_channel(5000 + 5 * channel, 1, 0, 0); + p.config.channel_number = channel; + } + const auto power_quarter_db = static_cast(std::round(power_dbm * 4.0)); // esp_wifi power is in 0.25 dBm units + esp_wifi_set_max_tx_power(power_quarter_db); + p.config.transmit_power_dbm = power_dbm; + + wifi_tx_rate_config_t rate {}; + rate.phymode = WIFI_PHY_MODE_11A; + rate.rate = rates[mcs]; + + // Assemble a standard IEEE 802.11 QoS data / LLC frame (EtherType 0x8947 GeoNetworking) + // Header: Frame Control (0x0088 QoS Data), Duration (0x0000), Addr1 (Broadcast FF..FF), + // Addr2 (Source 02:00:00:00:00:01), Addr3 (BSSID FF..FF), Sequence, QoS Control (0x0000), + // LLC/SNAP header (AA AA 03 00 00 00 89 47). + std::vector frame; + const std::size_t actual_payload = std::clamp(payload_len, 32, 1400); + frame.reserve(34 + actual_payload); + + // MAC Header (26 bytes with QoS) + frame.push_back(0x88); frame.push_back(0x00); // Frame Control: QoS Data + frame.push_back(0x00); frame.push_back(0x00); // Duration + for (int i = 0; i < 6; ++i) frame.push_back(0xFF); // RA / Destination: Broadcast + frame.push_back(0x02); frame.push_back(0x00); frame.push_back(0x00); + frame.push_back(0x00); frame.push_back(0x00); frame.push_back(0x01); // TA / Source + for (int i = 0; i < 6; ++i) frame.push_back(0xFF); // BSSID: Broadcast + frame.push_back(0x00); frame.push_back(0x00); // Sequence (updated per frame) + frame.push_back(0x00); frame.push_back(0x00); // QoS Control + + // LLC/SNAP header (8 bytes) + frame.push_back(0xAA); frame.push_back(0xAA); frame.push_back(0x03); + frame.push_back(0x00); frame.push_back(0x00); frame.push_back(0x00); + frame.push_back(0x89); frame.push_back(0x47); // EtherType 0x8947 (GeoNetworking) + + // Test payload with identifiable sequence numbers + const std::size_t header_len = frame.size(); + frame.resize(header_len + actual_payload, 0x5A); + + esp_err_t last_err = ESP_OK; + for (unsigned i = 0; i < count; ++i) { + p.sequence = (p.sequence + 1) & 4095; + frame[22] = static_cast((p.sequence << 4) & 0xF0); + frame[23] = static_cast((p.sequence >> 4) & 0xFF); + + // Put burst counter inside payload + frame[header_len + 0] = static_cast(i & 0xFF); + frame[header_len + 1] = static_cast((i >> 8) & 0xFF); + + esp_err_t err = esp_wifi_80211_tx_custom( + WIFI_IF_STA, frame.data(), frame.size(), false, + &rate, WIFI_BAND_5G, WIFI_BW20); + + if (err == ESP_ERR_NO_MEM) { + // Buffer briefly full: yield task to allow DMA descriptors to clear + vTaskDelay(pdMS_TO_TICKS(2)); + err = esp_wifi_80211_tx_custom( + WIFI_IF_STA, frame.data(), frame.size(), false, + &rate, WIFI_BAND_5G, WIFI_BW20); + } + + if (err != ESP_OK) { + last_err = err; + } + + const auto delay_ms = std::max(interval_ms, 2); + if (i + 1 < count) { + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + } + } + + return last_err; +} + +CcaSampleResult C5Radio::sample_cca(unsigned duration_ms) { + CcaSampleResult result; + auto& p = *impl_; + if (!p.started) return result; + + result.noise_floor_dbm = phy_get_noise_floor(); + + // Enable CCA hardware and arm the 27-bit cycle counters with full window (0x07FFFFFF). + phy_enable_cca(); + phy_set_cca_cnt(0x07FFFFFF, true); + + std::int32_t cca_cnt_before[2] = {}; + phy_get_cca_cnt(cca_cnt_before); + + const auto t_start = esp_timer_get_time(); + const auto t_deadline = t_start + static_cast(duration_ms) * 1000; + std::int64_t last_t = t_start; + result.min_delta_us = std::numeric_limits::max(); + + while (esp_timer_get_time() < t_deadline) { + std::int32_t cur_cnt[2] = {}; + const auto status = phy_get_cca_cnt(cur_cnt); + const auto cca_threshold = phy_get_cca(); + const auto now = esp_timer_get_time(); + + if (result.samples == 0) { + result.first_cca = cca_threshold; + } else { + const auto delta = static_cast(now - last_t); + result.min_delta_us = std::min(result.min_delta_us, delta); + result.max_delta_us = std::max(result.max_delta_us, delta); + } + last_t = now; + result.last_cca = cca_threshold; + if (status) ++result.busy_count; + ++result.samples; + + // Cooperative yielding: prevent starving IDLE task, esp_timer, and bb_wdt + // on the single-core C5 during multi-millisecond polling windows. + if ((result.samples & 0x3F) == 0) { + taskYIELD(); + } + } + + result.duration_us = static_cast(esp_timer_get_time() - t_start); + std::int32_t cca_cnt_after[2] = {}; + result.cca_status = phy_get_cca_cnt(cca_cnt_after); + + // Both words are 27-bit hardware counters (mask 0x07FFFFFF). + constexpr std::int32_t mask27 = 0x07FFFFFF; + auto delta27 = [](std::int32_t after, std::int32_t before) -> std::int32_t { + std::int32_t diff = (after & mask27) - (before & mask27); + if (diff < 0) diff += (mask27 + 1); + return diff; + }; + result.cca_total_cycles_delta = delta27(cca_cnt_after[0], cca_cnt_before[0]); + result.cca_busy_cycles_delta = delta27(cca_cnt_after[1], cca_cnt_before[1]); + + if (result.samples < 2) result.min_delta_us = 0; + return result; +} +#endif // CONFIG_MICROBU_TEST_CHANNEL + +CcaCounters C5Radio::read_cca_counters() const { + CcaCounters c; + std::int32_t out[2] = {}; + phy_get_cca_cnt(out); + constexpr std::int32_t mask27 = 0x07FFFFFF; // remomve the busy/status bit (bit 27) from the 27-bit hardware counters + c.total_cycles = static_cast(out[0] & mask27); + c.busy_cycles = static_cast(out[1] & mask27); + return c; +} + +double C5Radio::calculate_cbr(const CcaCounters& current, const CcaCounters& previous) { + constexpr std::uint32_t counter_range = 1u << 27; + auto delta27 = [](std::uint32_t after, std::uint32_t before) -> std::uint32_t { + if (after >= before) { + return after - before; + } + // Counter wrapped from 2^27 - 1 back to zero. + return after + counter_range - before; + }; + + const std::uint32_t dt = delta27(current.total_cycles, previous.total_cycles); + const std::uint32_t db = delta27(current.busy_cycles, previous.busy_cycles); + + if (dt == 0) return 0.0; + return static_cast(db) / static_cast(dt); +} + +} // namespace microbu diff --git a/obu-firmware/main/c5_radio.hpp b/obu-firmware/main/c5_radio.hpp new file mode 100644 index 0000000..a7bd63f --- /dev/null +++ b/obu-firmware/main/c5_radio.hpp @@ -0,0 +1,117 @@ +#pragma once +#include +#include +#include +#include + +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; + using Capture = std::function; + + /// @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_; +}; + +} // namespace microbu diff --git a/obu-firmware/main/link_protocol.cpp b/obu-firmware/main/link_protocol.cpp new file mode 100644 index 0000000..87ea759 --- /dev/null +++ b/obu-firmware/main/link_protocol.cpp @@ -0,0 +1,186 @@ +#include "link_protocol.hpp" + +namespace microbu::link { + +bool encode(const Message& message, Bytes& out) { + if (header_size + message.body.size() > maximum_message) return false; + out.clear(); + out.reserve(header_size + message.body.size()); + out.push_back(static_cast(message.header.opcode)); + out.push_back(message.header.flags); + out.push_back(message.header.sequence & 0xFF); + out.push_back(message.header.sequence >> 8); + out.insert(out.end(), message.body.begin(), message.body.end()); + return true; +} + +bool decode(const Bytes& octets, Message& out) { + if (octets.size() < header_size || octets.size() > maximum_message) return false; + out.header.opcode = static_cast(octets[0]); + out.header.flags = octets[1]; + out.header.sequence = octets[2] | (octets[3] << 8); + out.body.assign(octets.begin() + header_size, octets.end()); + return true; +} + +namespace { +void put_area(Writer& w, const DestinationArea& a) { + w.u8(a.shape); w.i32(a.latitude); w.i32(a.longitude); w.u16(a.distance_a); w.u16(a.distance_b); w.u16(a.angle); +} +DestinationArea get_area(Reader& r) { + DestinationArea a; + a.shape = r.u8(); a.latitude = r.i32(); a.longitude = r.i32(); a.distance_a = r.u16(); a.distance_b = r.u16(); a.angle = r.u16(); + return a; +} +} + +// STATION_CONFIGURE +Bytes encode(const StationConfigure& c) { + Writer w; + w.u8(c.station_type); w.u8(c.security); w.u8(c.address_configuration); w.bytes(c.mid, 6); w.u8(c.beaconing); + w.u16(c.channel_number); w.u8(c.transmit_power_dbm); w.u8(c.radio); w.u8(c.default_traffic_class); w.u8(c.default_lifetime); + return w.out; +} +bool decode(const Bytes& body, StationConfigure& c) { + Reader r(body); + c.station_type = r.u8(); c.security = r.u8(); c.address_configuration = r.u8(); r.bytes(c.mid, 6); c.beaconing = r.u8(); + c.channel_number = r.u16(); c.transmit_power_dbm = r.u8(); c.radio = r.u8(); c.default_traffic_class = r.u8(); c.default_lifetime = r.u8(); + return r.done() && c.security <= 1 && c.address_configuration <= 1 && c.beaconing <= 1 && c.radio <= 2; +} + +// POTI_UPDATE +Bytes encode(const PotiUpdate& p) { + Writer w; + w.u64(p.timestamp_ms); w.i32(p.latitude); w.i32(p.longitude); w.u16(p.semi_major_cm); w.u16(p.semi_minor_cm); + w.u16(p.orientation_deci_degree); w.u8(p.flags); w.i32(p.altitude_cm); w.u16(p.speed_cm_s); w.u16(p.heading_deci_degree); + return w.out; +} +bool decode(const Bytes& body, PotiUpdate& p) { + Reader r(body); + p.timestamp_ms = r.u64(); p.latitude = r.i32(); p.longitude = r.i32(); p.semi_major_cm = r.u16(); p.semi_minor_cm = r.u16(); + p.orientation_deci_degree = r.u16(); p.flags = r.u8(); p.altitude_cm = r.i32(); p.speed_cm_s = r.u16(); p.heading_deci_degree = r.u16(); + return r.done(); +} + +// BTP_DATA_REQUEST +Bytes encode(const BtpDataRequest& q) { + Writer w; + w.u8(q.btp_type); w.u16(q.destination_port); w.u16(q.destination_port_info); w.u8(q.gn_packet_transport_type); + w.u8(q.gn_communication_profile); w.u8(q.gn_security_profile); w.u8(q.gn_traffic_class); w.u8(q.gn_maximum_packet_lifetime); + w.u8(q.gn_maximum_hop_limit); w.u16(q.gn_repetition_interval_ms); w.u16(q.gn_repetition_maximum_ms); w.u32(q.its_aid); + w.u8(static_cast(q.permissions.size())); w.bytes(q.permissions); + w.u8(static_cast(q.context.size())); w.bytes(q.context); + if (q.gn_packet_transport_type == 3) put_area(w, q.area.value_or(DestinationArea {})); + w.u16(static_cast(q.fl_sdu.size())); w.bytes(q.fl_sdu); + return w.out; +} +bool decode(const Bytes& body, BtpDataRequest& q) { + Reader r(body); + q.btp_type = r.u8(); q.destination_port = r.u16(); q.destination_port_info = r.u16(); q.gn_packet_transport_type = r.u8(); + q.gn_communication_profile = r.u8(); q.gn_security_profile = r.u8(); q.gn_traffic_class = r.u8(); q.gn_maximum_packet_lifetime = r.u8(); + q.gn_maximum_hop_limit = r.u8(); q.gn_repetition_interval_ms = r.u16(); q.gn_repetition_maximum_ms = r.u16(); q.its_aid = r.u32(); + q.permissions = r.bytes(r.u8()); + q.context = r.bytes(r.u8()); + q.area.reset(); + if (q.gn_packet_transport_type == 3) q.area = get_area(r); + q.fl_sdu = r.bytes(r.u16()); + return r.done() && q.btp_type <= 1 && q.permissions.size() <= 31; +} + +// BTP_DATA_INDICATION +Bytes encode(const BtpDataIndication& i) { + Writer w; + w.u8(i.btp_type); w.u16(i.destination_port); w.u16(i.destination_port_info); w.u8(i.gn_packet_transport_type); + w.u8(i.gn_traffic_class); w.u8(i.gn_remaining_packet_lifetime); w.u8(i.gn_remaining_hop_limit); + w.bytes(i.source_gn_address, 8); w.u32(i.source_timestamp); w.i32(i.source_latitude); w.i32(i.source_longitude); + w.u8(i.security_report); w.u32(i.its_aid); + w.u8(static_cast(i.permissions.size())); w.bytes(i.permissions); + w.u8(i.certificate_present ? 1 : 0); w.bytes(i.certificate_id, 8); + w.u8(i.area ? 1 : 0); + if (i.area) put_area(w, *i.area); + w.u16(static_cast(i.received_fl_sdu.size())); w.bytes(i.received_fl_sdu); + return w.out; +} +bool decode(const Bytes& body, BtpDataIndication& i) { + Reader r(body); + i.btp_type = r.u8(); i.destination_port = r.u16(); i.destination_port_info = r.u16(); i.gn_packet_transport_type = r.u8(); + i.gn_traffic_class = r.u8(); i.gn_remaining_packet_lifetime = r.u8(); i.gn_remaining_hop_limit = r.u8(); + r.bytes(i.source_gn_address, 8); i.source_timestamp = r.u32(); i.source_latitude = r.i32(); i.source_longitude = r.i32(); + i.security_report = r.u8(); i.its_aid = r.u32(); + i.permissions = r.bytes(r.u8()); + i.certificate_present = r.u8() != 0; r.bytes(i.certificate_id, 8); + i.area.reset(); + if (r.u8()) i.area = get_area(r); + i.received_fl_sdu = r.bytes(r.u16()); + return r.done(); +} + +// CREDENTIALS_PROVISION +Bytes encode(const CredentialsProvision& c) { + Writer w; + w.u16(c.total_length); w.u16(c.offset); w.u8(static_cast(c.segment.size())); w.bytes(c.segment); + return w.out; +} +bool decode(const Bytes& body, CredentialsProvision& c) { + Reader r(body); + c.total_length = r.u16(); c.offset = r.u16(); c.segment = r.bytes(r.u8()); + return r.done() && !c.segment.empty() && c.offset + c.segment.size() <= c.total_length; +} + +// SF_IDCHANGE_EVENT +Bytes encode(const IdChangeEvent& e) { + Writer w; + w.u64(e.subscription); w.u8(e.command); w.bytes(e.id, 8); + w.u8(static_cast(e.subscriber_data.size())); w.bytes(e.subscriber_data); + return w.out; +} +bool decode(const Bytes& body, IdChangeEvent& e) { + Reader r(body); + e.subscription = r.u64(); e.command = r.u8(); r.bytes(e.id, 8); e.subscriber_data = r.bytes(r.u8()); + return r.done() && e.command <= 3; +} + +Bytes encode(const IdChangeEventResponse& e) { Writer w; w.u64(e.subscription); w.u8(e.return_code); return w.out; } +bool decode(const Bytes& body, IdChangeEventResponse& e) { + Reader r(body); + e.subscription = r.u64(); e.return_code = r.u8(); + return r.done() && e.return_code <= 1; +} + +// STATUS +Bytes encode(const Status& s) { + Writer w; + w.u32(s.uptime_ms); w.u8(s.configured); w.bytes(s.gn_address, 8); w.bytes(s.identifier, 8); w.u8(s.change_pending); w.u8(s.tickets); + w.u32(s.signed_messages); w.u32(s.refused_no_ticket); w.u32(s.refused_change_pending); w.u32(s.refused_permission); + w.u32(s.sign_failed); w.u32(s.verified); w.u32(s.rejected); + w.u32(s.requests_accepted); w.u32(s.requests_refused); w.u32(s.indications); + w.u32(s.radio_submitted); w.u32(s.radio_failed); w.u32(s.radio_received); w.u32(s.radio_dropped); + w.u32(s.link_rx_frames); w.u32(s.link_crc_errors); w.u32(s.link_malformed); w.u32(s.poti_updates); + w.u64(s.its_time_ms); + return w.out; +} +bool decode(const Bytes& body, Status& s) { + Reader r(body); + s.uptime_ms = r.u32(); s.configured = r.u8(); r.bytes(s.gn_address, 8); r.bytes(s.identifier, 8); s.change_pending = r.u8(); s.tickets = r.u8(); + s.signed_messages = r.u32(); s.refused_no_ticket = r.u32(); s.refused_change_pending = r.u32(); s.refused_permission = r.u32(); + s.sign_failed = r.u32(); s.verified = r.u32(); s.rejected = r.u32(); + s.requests_accepted = r.u32(); s.requests_refused = r.u32(); s.indications = r.u32(); + s.radio_submitted = r.u32(); s.radio_failed = r.u32(); s.radio_received = r.u32(); s.radio_dropped = r.u32(); + s.link_rx_frames = r.u32(); s.link_crc_errors = r.u32(); s.link_malformed = r.u32(); s.poti_updates = r.u32(); + s.its_time_ms = r.u64(); + return r.done(); +} + +// RESULT +Bytes encode(const Result& res) { + Writer w; + w.u8(static_cast(res.code)); w.u8(static_cast(res.detail.size())); w.bytes(res.detail); + return w.out; +} +bool decode(const Bytes& body, Result& res) { + Reader r(body); + res.code = static_cast(r.u8()); res.detail = r.bytes(r.u8()); + return r.done(); +} + +} // namespace microbu::link diff --git a/obu-firmware/main/link_protocol.hpp b/obu-firmware/main/link_protocol.hpp new file mode 100644 index 0000000..177a4dd --- /dev/null +++ b/obu-firmware/main/link_protocol.hpp @@ -0,0 +1,363 @@ +#pragma once +// micrOBU station-internal link, message layer version 1 (implementation/station-link/README.md). +// Transport independent: the same octets are one GATT attribute value later and one serial +// frame payload today. Little-endian integers; ETSI payloads inside stay opaque. +#include +#include +#include +#include +#include + +namespace microbu::link { + +using Bytes = std::vector; + +constexpr std::size_t maximum_message = 512; +constexpr std::size_t header_size = 4; + +/// @brief The message opcode, which determines the body type. +enum class Opcode : std::uint8_t { + // phone -> ESP32-C5 + STATION_CONFIGURE = 0x01, + POTI_UPDATE = 0x02, + BTP_DATA_REQUEST = 0x03, + CREDENTIALS_PROVISION = 0x04, + CREDENTIALS_ERASE = 0x05, + SF_IDCHANGE_SUBSCRIBE = 0x06, + SF_IDCHANGE_UNSUBSCRIBE = 0x07, + SF_IDCHANGE_EVENT_RESPONSE = 0x08, + SF_IDCHANGE_TRIGGER = 0x09, + SF_ID_LOCK = 0x0A, + SF_ID_UNLOCK = 0x0B, + STATUS_REQUEST = 0x0C, + // ESP32-C5 -> phone + RESULT = 0x80, + BTP_DATA_INDICATION = 0x81, + SF_IDCHANGE_EVENT = 0x82, + STATUS = 0x84, + // MicrOBU extension, not in the colleague's station-link v1: every ITS message heard on air, + // unwrapped by gn_unwrap.c whether or not the security entity could verify it. Body is the + // previous firmware's SERIAL_MSG_V2X_RX payload unchanged: [u16 btp_dest_port][i8 rssi] + // [u8 flags: bit0 geo area valid, bit1 signed but not verified][i32 area_lat][i32 area_lon] + // [u16 area_distance_a], then the UPER bytes. See obu-firmware/NOTES.md. + V2X_RX = 0x85, +}; + +constexpr std::uint8_t flag_fragment_first = 0x01; +constexpr std::uint8_t flag_fragment_more = 0x02; + +// RESULT codes: vanetza_idf::Result values first (same numbering), then link codes. +enum class Code : std::uint8_t { + accepted = 0, invalid_argument = 1, unsupported = 2, wrong_entry_point = 3, security_unavailable = 4, + resource_limit = 5, rejected = 6, time_regression = 7, identity_change_pending = 8, + unknown_opcode = 0x10, malformed = 0x11, not_configured = 0x12, busy = 0x13, no_credentials = 0x14, +}; + +/// @brief The header of a link message. +struct Header { + Opcode opcode; + std::uint8_t flags = 0; + std::uint16_t sequence = 0; +}; + +/// @brief A complete link message. +struct Message { + Header header; + Bytes body; +}; + +/// @brief Serializes header + body. +/// @param message Message to serialize. +/// @param out Receives the serialized bytes. +/// @return false when the result would exceed maximum_message. +bool encode(const Message& message, Bytes& out); +/// @brief Parses header + body. +/// @param octets Raw bytes to parse. +/// @param out Receives the decoded message. +/// @return false when shorter than the header or longer than maximum_message. +bool decode(const Bytes& octets, Message& out); + +// ---- bodies ------------------------------------------------------------------------------ + +// security, address_configuration, default_traffic_class and default_lifetime are the GN protocol +// constants itsGnSecurity, itsGnLocalAddrConfMethod, itsGnDefaultTrafficClass and +// itsGnDefaultPacketLifetime of ETSI TS 103 836-4-1 (GeoNetworking) annex H; channel_number, +// transmit_power_dbm and radio are ITS-G5 access-layer parameters (ETSI EN 303 797). +struct StationConfigure { + std::uint8_t station_type = 2; // TS 102 894-2 StationType (2 = cyclist) + std::uint8_t security = 1; // itsGnSecurity + std::uint8_t address_configuration = 1; // 0 AUTO (mid below), 1 ANONYMOUS (ticket digest) + std::uint8_t mid[6] = {2, 0, 0, 0, 0, 1}; + std::uint8_t beaconing = 1; + std::uint16_t channel_number = 180; + std::uint8_t transmit_power_dbm = 10; + std::uint8_t radio = 0; // 0 off, 1 receive only, 2 transmit and receive + std::uint8_t default_traffic_class = 2; + std::uint8_t default_lifetime = 0x05; // 1 s (base One_Second, multiplier 1) +}; +/// @param body Raw message body bytes. +/// @param out Receives the decoded configuration. +/// @return false if malformed. +bool decode(const Bytes& body, StationConfigure& out); + +/// @param value Configuration to encode. +/// @return Encoded body bytes. +Bytes encode(const StationConfigure& value); + +// Position/confidence-ellipse/speed/heading fields are the ETSI TS 102 894-2 (Common Data +// Dictionary) ReferencePosition / PosConfidenceEllipse data types; pai() mirrors the Position +// Accuracy Indicator field of the GeoNetworking Long Position Vector (ETSI TS 103 836-4-1 +// clause 9.5.2.2, table 2). +struct PotiUpdate { + std::uint64_t timestamp_ms = 0; // TimestampIts + std::int32_t latitude = 0; // 1/10 microdegree + std::int32_t longitude = 0; + std::uint16_t semi_major_cm = 0; + std::uint16_t semi_minor_cm = 0; + std::uint16_t orientation_deci_degree = 0; + std::uint8_t flags = 0; // bit0 altitude, bit1 speed, bit2 heading, bit3 PAI + std::int32_t altitude_cm = 0; + std::uint16_t speed_cm_s = 0; + std::uint16_t heading_deci_degree = 0; + bool has_altitude() const { return flags & 1; } + bool has_speed() const { return flags & 2; } + bool has_heading() const { return flags & 4; } + bool pai() const { return flags & 8; } +}; + +/// @param body Raw message body bytes. +/// @param out Receives the decoded fix. +/// @return false if malformed. +bool decode(const Bytes& body, PotiUpdate& out); + +/// @param value Fix to encode. +/// @return Encoded body bytes. +Bytes encode(const PotiUpdate& value); + + +/// @brief The message body of a BTP_DATA_REQUEST (ETSI TS 103 836-4-1 annex J.2) request. +// GeoArea per ETSI TS 103 899 "Geographical Area Definition". shape follows the GEOBROADCAST_*/ +// GEOANYCAST_* header sub-type encoding of ETSI TS 103 836-4-1 (GeoNetworking) clause 9.7.4 table 9 +// (0 circle, 1 rectangle, 2 ellipse); position/distance_a/distance_b/angle are the GeoArea fields +// carried in the GBC/GAC extended header per clause 9.8.5 table 36. +struct DestinationArea { + std::uint8_t shape = 0; // 0 circle, 1 rectangle, 2 ellipse + std::int32_t latitude = 0; + std::int32_t longitude = 0; + std::uint16_t distance_a = 0; + std::uint16_t distance_b = 0; + std::uint16_t angle = 0; +}; + +// btp_type/destination_port(_info) are the BTP-A/BTP-B header fields of ETSI TS 103 836-5-1 (Basic +// Transport Protocol); the gn_* fields mirror the TRANSP_CORE.request service primitive parameters +// of ETSI TS 103 836-4-1 (GeoNetworking) annex J.2 (Packet transport type, Traffic class, Maximum +// hop limit, Repetition interval/maximum, Security profile); its_aid follows the ITS-AID registry +// of ETSI TS 102 965. +struct BtpDataRequest { + std::uint8_t btp_type = 1; + std::uint16_t destination_port = 0; + std::uint16_t destination_port_info = 0; // or source_port for BTP-A + std::uint8_t gn_packet_transport_type = 1; // 0 GUC, 1 SHB, 2 TSB, 3 GBC, 4 GAC (TS 103 836-4-1 annex J.2) + std::uint8_t gn_communication_profile = 1; + std::uint8_t gn_security_profile = 0; // 0 not given, 1 unsecured, 2 secured + std::uint8_t gn_traffic_class = 0xFF; // 0xFF = station default + std::uint8_t gn_maximum_packet_lifetime = 0xFF; // 0xFF = station default + std::uint8_t gn_maximum_hop_limit = 0; // 0 = station default + std::uint16_t gn_repetition_interval_ms = 0; + std::uint16_t gn_repetition_maximum_ms = 0; + std::uint32_t its_aid = 0; + Bytes permissions; + Bytes context; + std::optional area; // present for GBC + Bytes fl_sdu; +}; + +/// @param body Raw message body bytes. +/// @param out Receives the decoded request. +/// @return false if malformed. +bool decode(const Bytes& body, BtpDataRequest& out); + +/// @param value Request to encode. +/// @return Encoded body bytes. +Bytes encode(const BtpDataRequest& value); + + + +// Mirrors the TRANSP_CORE.indication service primitive parameters of ETSI TS 103 836-4-1 +// (GeoNetworking) annex J.4: source_gn_address/source_timestamp/source_latitude/source_longitude +// are the sender's Long Position Vector (clause 9.5.2), security_report/certificate_* the Security +// report/Certificate id parameters, its_aid/permissions the ITS-AID/Security permissions +// parameters. +struct BtpDataIndication { + std::uint8_t btp_type = 1; + std::uint16_t destination_port = 0; + std::uint16_t destination_port_info = 0; + std::uint8_t gn_packet_transport_type = 1; // 0 GUC, 1 SHB, 2 TSB, 3 GBC, 4 GAC (TS 103 836-4-1 annex J.4) + std::uint8_t gn_traffic_class = 0; + std::uint8_t gn_remaining_packet_lifetime = 0xFF; + std::uint8_t gn_remaining_hop_limit = 0xFF; + std::uint8_t source_gn_address[8] = {}; + std::uint32_t source_timestamp = 0; + std::int32_t source_latitude = 0; + std::int32_t source_longitude = 0; + std::uint8_t security_report = 0; // 0 unsecured, 1 + VerificationReport ordinal + std::uint32_t its_aid = 0; + Bytes permissions; + bool certificate_present = false; + std::uint8_t certificate_id[8] = {}; + std::optional area; + Bytes received_fl_sdu; +}; + +/// @param body Raw message body bytes. +/// @param out Receives the decoded indication. +/// @return false if malformed. +bool decode(const Bytes& body, BtpDataIndication& out); + +/// @param value Indication to encode. +/// @return Encoded body bytes. +Bytes encode(const BtpDataIndication& value); + + + +// Segmented upload of an ETSI TS 102 941 (Trust and Privacy Management) credential bundle +// (root CA / enrolment or authorization authority certificates, authorization tickets), whose +// certificate encoding is ETSI TS 103 097. +struct CredentialsProvision { + std::uint16_t total_length = 0; + std::uint16_t offset = 0; + Bytes segment; +}; + +/// @param body Raw message body bytes. +/// @param out Receives the decoded segment. +/// @return false if malformed. +/// @note One CredentialsProvision carries a single segment of a larger bundle; total_length +/// and offset let the caller reassemble the full bundle across several messages. +bool decode(const Bytes& body, CredentialsProvision& out); + +/// @param value Segment to encode. +/// @return Encoded body bytes. +Bytes encode(const CredentialsProvision& value); + +// Counts of ETSI TS 102 941 credentials (root CA / EA-AA certificates / authorization tickets) +// applied from a CredentialsProvision bundle. +struct ApplyReport { std::uint8_t roots = 0, authorities = 0, tickets = 0; }; + + + +// Mirrors the security entity's pseudonym-change handshake that the GN Core subscribes to via the +// SN-IDCHANGE-SUBSCRIBE/-EVENT/-UNSUBSCRIBE primitives at the CORE_SEC interface (ETSI +// TS 103 836-4-1 clause 10.2.1.4); the pseudonym/Authorization Ticket change itself is governed by +// ETSI TS 102 941. command is the link's own PREPARE/COMMIT/ABORT/DEREG handshake state, not an +// ETSI-defined field. +struct IdChangeEvent { + std::uint64_t subscription = 0; + std::uint8_t command = 0; // 0 PREPARE, 1 COMMIT, 2 ABORT, 3 DEREG + std::uint8_t id[8] = {}; + Bytes subscriber_data; +}; + +/// @param body Raw message body bytes. +/// @param out Receives the decoded event. +/// @return false if malformed. +bool decode(const Bytes& body, IdChangeEvent& out); + +/// @param value Event to encode. +/// @return Encoded body bytes. +Bytes encode(const IdChangeEvent& value); + +// The link's encoding of the SN-IDCHANGE-EVENT.response primitive (ETSI TS 103 836-4-1 +// clause 10.2.1.4), acknowledging an IdChangeEvent. +struct IdChangeEventResponse { std::uint64_t subscription = 0; std::uint8_t return_code = 0; }; +/// @param body Raw message body bytes. +/// @param out Receives the decoded response. +/// @return false if malformed. +bool decode(const Bytes& body, IdChangeEventResponse& out); +/// @param value Response to encode. +/// @return Encoded body bytes. +Bytes encode(const IdChangeEventResponse& value); + +struct Status { + std::uint32_t uptime_ms = 0; + std::uint8_t configured = 0; + std::uint8_t gn_address[8] = {}; + std::uint8_t identifier[8] = {}; + std::uint8_t change_pending = 0; + std::uint8_t tickets = 0; + std::uint32_t signed_messages = 0, refused_no_ticket = 0, refused_change_pending = 0, refused_permission = 0, + sign_failed = 0, verified = 0, rejected = 0; + std::uint32_t requests_accepted = 0, requests_refused = 0, indications = 0; + std::uint32_t radio_submitted = 0, radio_failed = 0, radio_received = 0, radio_dropped = 0; + std::uint32_t link_rx_frames = 0, link_crc_errors = 0, link_malformed = 0, poti_updates = 0; + std::uint64_t its_time_ms = 0; +}; +/// @param body Raw message body bytes. +/// @param out Receives the decoded status. +/// @return false if malformed. +bool decode(const Bytes& body, Status& out); +/// @param value Status to encode. +/// @return Encoded body bytes. +Bytes encode(const Status& value); + +struct Result { + Code code = Code::accepted; + Bytes detail; +}; +/// @param body Raw message body bytes. +/// @param out Receives the decoded result. +/// @return false if malformed. +bool decode(const Bytes& body, Result& out); +/// @param value Result to encode. +/// @return Encoded body bytes. +Bytes encode(const Result& value); + +// ---- little-endian helpers shared with the test channel ---------------------------------- +/// @note All multi-byte values are written little-endian. +class Writer { +public: + /// @brief Output buffer thats apppended to + Bytes out; + /// @brief Appends a single byte to the output. + void u8(std::uint8_t v) { out.push_back(v); } + /// @brief Appends a 16-bit unsigned integer to the output. + void u16(std::uint16_t v) { out.push_back(v & 0xFF); out.push_back(v >> 8); } + /// @brief Appends a 32-bit unsigned integer to the output. + void u32(std::uint32_t v) { for (int i = 0; i < 4; ++i) out.push_back((v >> (8 * i)) & 0xFF); } + /// @brief Appends a 64-bit unsigned integer to the output. + void u64(std::uint64_t v) { for (int i = 0; i < 8; ++i) out.push_back((v >> (8 * i)) & 0xFF); } + /// @brief Appends a 32-bit signed integer to the output. + void i32(std::int32_t v) { u32(static_cast(v)); } + /// @brief Appends a sequence of bytes to the output. + void bytes(const std::uint8_t* p, std::size_t n) { out.insert(out.end(), p, p + n); } + /// @brief Appends a sequence of bytes to the output. + void bytes(const Bytes& b) { out.insert(out.end(), b.begin(), b.end()); } +}; + +/// @note All multi-byte values are read little-endian. Once a read runs past the end of the +/// buffer, ok() becomes false and all further reads return zero/empty instead of throwing. +class Reader { +public: + /// @param b Buffer to read from; must outlive the Reader. + /// @param at Starting offset into b. + Reader(const Bytes& b, std::size_t at = 0) : b_(b), at_(at) {} + bool ok() const { return ok_; } + bool done() const { return ok_ && at_ == b_.size(); } + std::size_t remaining() const { return b_.size() - at_; } + std::uint8_t u8() { return need(1) ? b_[at_++] : 0; } + std::uint16_t u16() { if (!need(2)) return 0; std::uint16_t v = b_[at_] | (b_[at_ + 1] << 8); at_ += 2; return v; } + std::uint32_t u32() { if (!need(4)) return 0; std::uint32_t v = 0; for (int i = 3; i >= 0; --i) v = (v << 8) | b_[at_ + i]; at_ += 4; return v; } + std::uint64_t u64() { if (!need(8)) return 0; std::uint64_t v = 0; for (int i = 7; i >= 0; --i) v = (v << 8) | b_[at_ + i]; at_ += 8; return v; } + std::int32_t i32() { return static_cast(u32()); } + bool bytes(std::uint8_t* p, std::size_t n) { if (!need(n)) return false; for (std::size_t i = 0; i < n; ++i) p[i] = b_[at_ + i]; at_ += n; return true; } + Bytes bytes(std::size_t n) { Bytes r; if (need(n)) { r.assign(b_.begin() + at_, b_.begin() + at_ + n); at_ += n; } return r; } + Bytes rest() { Bytes r(b_.begin() + at_, b_.end()); at_ = b_.size(); return r; } +private: + bool need(std::size_t n) { if (!ok_ || at_ + n > b_.size()) { ok_ = false; return false; } return true; } + const Bytes& b_; + std::size_t at_; + bool ok_ = true; +}; + +} // namespace microbu::link diff --git a/obu-firmware/main/link_service.cpp b/obu-firmware/main/link_service.cpp new file mode 100644 index 0000000..697b697 --- /dev/null +++ b/obu-firmware/main/link_service.cpp @@ -0,0 +1,159 @@ +#include "link_service.hpp" +#include "simple_ble.hpp" +#include "serial_link.hpp" +#include +#include + +namespace microbu { +namespace { +const char* TAG = "link"; +} + +LinkService::LinkService(Station& station) : station_(station) { + station_.on_indication([this](const link::BtpDataIndication& indication) { + broadcast(link::Opcode::BTP_DATA_INDICATION, ++own_sequence_, link::encode(indication)); + }); + // MicrOBU: every ITS message heard on air, verified or not (Station::on_raw_its). + station_.on_raw_its([this](const link::Bytes& body) { + broadcast(link::Opcode::V2X_RX, ++own_sequence_, body); + }); + station_.on_id_event([this](const link::IdChangeEvent& event) { + // PREPARE/COMMIT expect SF_IDCHANGE_EVENT_RESPONSE with this sequence + broadcast(link::Opcode::SF_IDCHANGE_EVENT, ++own_sequence_, link::encode(event)); + }); +} + +void LinkService::send(LinkTransport transport, link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body) { + link::Bytes octets; + link::Message message {{opcode, 0, sequence}, body}; + if (!link::encode(message, octets)) { ESP_LOGE(TAG, "message 0x%02x too long (%u)", unsigned(opcode), unsigned(body.size())); return; } + if (transport == LinkTransport::serial) serial::write(serial::FrameType::LINK, octets); + else ble::write(octets); +} + +void LinkService::broadcast(link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body) { + send(LinkTransport::serial, opcode, sequence, body); + if (ble::connected()) send(LinkTransport::ble, opcode, sequence, body); +} + +void LinkService::reply(LinkTransport transport, std::uint16_t sequence, link::Code code, const link::Bytes& detail) { + send(transport, link::Opcode::RESULT, sequence, link::encode(link::Result {code, detail})); +} + +link::Status LinkService::status() { + auto value = station_.status(); + const auto serial_counters = serial::counters(); + const auto ble_counters = ble::counters(); + value.link_rx_frames = serial_counters.frames + ble_counters.rx_messages; + value.link_crc_errors = serial_counters.crc_errors; + value.link_malformed += ble_counters.malformed; + return value; +} + +void LinkService::handle(const link::Bytes& octets, LinkTransport origin) { + link::Message m; + if (!link::decode(octets, m)) return; + const auto seq = m.header.sequence; + using link::Opcode; using link::Code; + switch (m.header.opcode) { + case Opcode::STATION_CONFIGURE: { + link::StationConfigure c; + if (!link::decode(m.body, c)) return reply(origin, seq, Code::malformed); + link::Bytes detail; + const auto result = station_.configure(c, detail); + ESP_LOGI(TAG, "station configured: security %u, radio %u, result %d", unsigned(c.security), unsigned(c.radio), int(result)); + return reply(origin, seq, result, detail); + } + case Opcode::POTI_UPDATE: { + link::PotiUpdate p; + if (!link::decode(m.body, p)) return reply(origin, seq, Code::malformed); + const auto result = station_.poti(p); + if (result != Code::accepted) reply(origin, seq, result); // accepted updates are silent + return; + } + case Opcode::BTP_DATA_REQUEST: { + link::BtpDataRequest q; + if (!link::decode(m.body, q)) return reply(origin, seq, Code::malformed); + return reply(origin, seq, station_.btp_request(q)); + } + case Opcode::CREDENTIALS_PROVISION: { + link::CredentialsProvision c; + if (!link::decode(m.body, c)) return reply(origin, seq, Code::malformed); + if (c.offset == 0) { bundle_.clear(); bundle_total_ = c.total_length; } + if (c.total_length != bundle_total_ || c.offset != bundle_.size() || c.total_length > 8192) { bundle_.clear(); return reply(origin, seq, Code::malformed); } + bundle_.insert(bundle_.end(), c.segment.begin(), c.segment.end()); + if (bundle_.size() < bundle_total_) return reply(origin, seq, Code::accepted); + link::ApplyReport report; + link::Code result = Code::accepted; + try { + result = station_.provision(bundle_, report); + } catch (const std::exception& e) { + ESP_LOGE(TAG, "provision exception: %s", e.what()); + result = Code::invalid_argument; + } + bundle_.clear(); + link::Bytes detail; + detail.push_back(static_cast(report.roots)); + detail.push_back(static_cast(report.authorities)); + detail.push_back(static_cast(report.tickets)); + return reply(origin, seq, result, detail); + } + case Opcode::CREDENTIALS_ERASE: + return reply(origin, seq, station_.erase_credentials()); + case Opcode::SF_IDCHANGE_SUBSCRIBE: { + link::Reader r(m.body); + const auto data = r.bytes(r.u8()); + if (!r.done()) { + ESP_LOGW(TAG, "SF_IDCHANGE_SUBSCRIBE malformed (len=%u)", (unsigned)m.body.size()); + return reply(origin, seq, Code::malformed); + } + std::uint64_t handle = 0; + const auto result = station_.subscribe(data, handle); + ESP_LOGI(TAG, "SF_IDCHANGE_SUBSCRIBE handle=%llu result=%d", (unsigned long long)handle, int(result)); + link::Writer w; w.u64(handle); + return reply(origin, seq, result, result == Code::accepted ? w.out : link::Bytes {}); + } + case Opcode::SF_IDCHANGE_UNSUBSCRIBE: { + link::Reader r(m.body); + const auto handle = r.u64(); + if (!r.done()) return reply(origin, seq, Code::malformed); + return reply(origin, seq, station_.unsubscribe(handle)); + } + case Opcode::SF_IDCHANGE_EVENT_RESPONSE: { + link::IdChangeEventResponse e; + if (!link::decode(m.body, e)) return; // no reply defined for a response + station_.event_response(e.subscription, e.return_code != 0); + return; + } + case Opcode::SF_IDCHANGE_TRIGGER: + return reply(origin, seq, station_.trigger()); + case Opcode::SF_ID_LOCK: { + link::Reader r(m.body); + const auto seconds = r.u8(); + if (!r.done()) return reply(origin, seq, Code::malformed); + std::uint64_t handle = 0; + const auto result = station_.lock(seconds, handle); + link::Writer w; w.u64(handle); + return reply(origin, seq, result, result == Code::accepted ? w.out : link::Bytes {}); + } + case Opcode::SF_ID_UNLOCK: { + link::Reader r(m.body); + const auto handle = r.u64(); + if (!r.done()) return reply(origin, seq, Code::malformed); + return reply(origin, seq, station_.unlock(handle)); + } + case Opcode::STATUS_REQUEST: + return send(origin, link::Opcode::STATUS, seq, link::encode(status())); + default: + return reply(origin, seq, Code::unknown_opcode); + } +} + +void LinkService::tick() { + const auto now = esp_timer_get_time(); + if (now - last_status_us_ < 1000000) return; + last_status_us_ = now; + broadcast(link::Opcode::STATUS, ++own_sequence_, link::encode(status())); +} + +} // namespace microbu diff --git a/obu-firmware/main/link_service.hpp b/obu-firmware/main/link_service.hpp new file mode 100644 index 0000000..b5c898d --- /dev/null +++ b/obu-firmware/main/link_service.hpp @@ -0,0 +1,51 @@ +#pragma once +// Dispatches station-link messages (phone -> micrOBU) to the station and sends the +// micrOBU -> phone messages (results, indications, identifier-change events, status). +#include "link_protocol.hpp" +#include "station.hpp" + +namespace microbu { + +/// @brief Represents the transport mechanism for link messages. Either BLE or Serial +enum class LinkTransport : std::uint8_t { serial, ble }; + +class LinkService { +public: + /// @brief Binds the service to a station and subscribes to its indication/id-event callbacks. + /// @param station Station to bind to; must outlive the LinkService. + explicit LinkService(Station& station); + /// @brief Decodes and dispatches one received message; call from the station task. + /// @param message Raw message bytes as received from the transport. + /// @param origin Transport the message arrived on; replies go back on the same transport. + void handle(const link::Bytes& message, LinkTransport origin); + /// @brief Periodic work: broadcasts STATUS once a second. + void tick(); +private: + /// @brief Encodes and writes one message on the given transport. + /// @param transport Transport to write to. + /// @param opcode Message opcode. + /// @param sequence Sequence number to stamp on the header. + /// @param body Encoded message body. + void send(LinkTransport transport, link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body); + /// @brief Sends a message on serial, and also on BLE when a phone is connected. + /// @param opcode Message opcode. + /// @param sequence Sequence number to stamp on the header. + /// @param body Encoded message body. + void broadcast(link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body); + /// @brief Sends a RESULT reply to the message's origin transport. + /// @param origin Transport to reply on. + /// @param sequence Sequence number of the message being replied to. + /// @param code Result code to report. + /// @param detail Optional result detail bytes. + void reply(LinkTransport origin, std::uint16_t sequence, link::Code code, const link::Bytes& detail = {}); + /// @brief Builds a STATUS snapshot from the station and transport counters. + /// @return Current status. + link::Status status(); + Station& station_; + std::uint16_t own_sequence_ = 0; + link::Bytes bundle_; // credentials being provisioned + std::uint16_t bundle_total_ = 0; + std::int64_t last_status_us_ = 0; +}; + +} // namespace microbu diff --git a/obu-firmware/main/linker.lf b/obu-firmware/main/linker.lf new file mode 100644 index 0000000..c515ed0 --- /dev/null +++ b/obu-firmware/main/linker.lf @@ -0,0 +1,4 @@ +[mapping:newlib_memcmp] +archive: libnewlib.a +entries: + memcmp (noflash) diff --git a/obu-firmware/main/otm_tx_custom.c b/obu-firmware/main/otm_tx_custom.c new file mode 100644 index 0000000..16543a6 --- /dev/null +++ b/obu-firmware/main/otm_tx_custom.c @@ -0,0 +1,175 @@ +// Sourced from OpenTrafficMap's its-g5-receiver-firmware_txenabled, main/tx_custom.c +// (https://codeberg.org/opentrafficmap/its-g5-receiver-firmware_txenabled, pinned commit +// 674e34128279235ba34c9f8d778f43cf1d075397, no stated license). Reproduced here as generated by +// implementation/external/vanetza-idf/ports/esp_idf/radio_c5.cmake (which hashes the upstream file +// so this copy cannot silently drift) and checked in for readability. The struct layouts +// (x_eb_txdesc_t, x_middle_data_t, x_ebuf_t) and the bit-level manipulation of esp_wifi's private +// tx descriptor are OTM's own reverse engineering of the closed esp_wifi/libphy internals -- not +// documented or supported by Espressif, and not verified independently by this project. The one +// deliberate change from upstream is `result = ieee80211_post_hmac_tx(eb);` below: upstream +// discards that return value, but ESP_OK from this function means the driver accepted the frame +// for submission, not that it was independently observed on air (see +// experiments/evidence/c5-radio-characterization/phy-internals-investigation.md). +#include "esp_private/wifi_os_adapter.h" +#include "esp_wifi.h" +#include "otm_tx_custom.h" +#include +#include + +esp_err_t ieee80211_raw_frame_sanity_check(wifi_interface_t ifx, const void *buffer, int32_t len, bool en_sys_seq); +esp_err_t ieee80211_post_hmac_tx(void *ebuf); +void *ic_ebuf_alloc(const void *packet, uint32_t unknown, uint32_t len); +void *ic_get_default_sched(void); + +extern wifi_osi_funcs_t *g_osi_funcs_p; +extern void *g_wifi_global_lock; + +typedef struct x_eb_txdesc +{ + uint32_t flags; + uint32_t field_4; + uint32_t field_8; + uint8_t rate; + uint8_t field_d; + uint8_t field_e; + uint8_t field_f; + uint32_t field_10; + uint32_t field_14; + uint32_t timestamp; + void* sched; + uint32_t field_20; + uint32_t field_24; + uint32_t field_28; + union { + uint32_t field_2c_32; + struct { + uint8_t field_2c; + uint8_t field_2d; + uint8_t field_2e; + uint8_t field_2f; + }; + }; + union { + uint32_t field_30_32; + struct { + uint8_t field_30; + uint8_t field_31; + uint8_t field_32; + uint8_t field_33; + }; + }; + uint32_t field_34; + uint32_t field_38; + uint32_t field_3c; + uint32_t field_40; + uint32_t field_44; +} x_eb_txdesc_t; +_Static_assert(sizeof(x_eb_txdesc_t) == 0x48, "eb_txdesc size"); + +typedef struct x_middle_data +{ + uint32_t field_40; + uint8_t* buf; + uint32_t field_48; + uint32_t field_4c; +} x_middle_data_t; +_Static_assert(sizeof(x_middle_data_t) == 0x10, "middle_data size"); + +typedef struct x_ebuf +{ + uint32_t field_0; + x_middle_data_t* ds_head; + x_middle_data_t* ds_tail; + uint16_t field_c; + uint16_t field_e; + uint32_t extra_data_start; + uint16_t header_length; + uint32_t data_length; + uint16_t field_1c; + uint8_t alloc_type; + uint8_t field_1f; + uint32_t field_20; + uint8_t field_24; + uint8_t field_25; + uint8_t field_26; + uint8_t field_27; + uint32_t field_28; + uint8_t field_2c; + uint32_t field_30; + uint32_t next_free; + x_eb_txdesc_t* txdesc; + uint16_t field_3c; + uint8_t field_3e; + uint8_t field_3f; +} x_ebuf_t; +_Static_assert(sizeof(x_ebuf_t) == 0x40, "ebuf size"); +_Static_assert(offsetof(x_ebuf_t, txdesc) == 0x38, "ebuf txdesc offset"); +_Static_assert(offsetof(x_eb_txdesc_t, rate) == 0x0c, "txdesc rate offset"); + +esp_err_t esp_wifi_80211_tx_custom(wifi_interface_t ifx, const void *buffer, int32_t len, bool en_sys_seq, wifi_tx_rate_config_t *tx_rate_config, wifi_band_t band, wifi_bandwidth_t bw) +{ + esp_err_t result = 0; + + if (!result) + { + g_osi_funcs_p->_mutex_lock(g_wifi_global_lock); + x_ebuf_t* eb = ic_ebuf_alloc(buffer, 1, len); + + if (eb) + { + eb->data_length = 0; + x_eb_txdesc_t *txdesc_1 = eb->txdesc; + eb->header_length = len; + txdesc_1->flags |= 0x4000; + txdesc_1->sched = ic_get_default_sched(); + wifi_phy_rate_t rate = tx_rate_config->rate; + x_eb_txdesc_t *txdesc = eb->txdesc; + + if (rate) + txdesc->rate = (char)rate; + else if (band != WIFI_BAND_5G) + txdesc->rate = 0; + else + txdesc->rate = (char)WIFI_PHY_RATE_6M; + + wifi_phy_mode_t phymode = tx_rate_config->phymode; + + if (phymode == WIFI_PHY_MODE_HE20) + { + txdesc->flags |= 0x80000000; + txdesc->field_2f = + (char)((((uint32_t)tx_rate_config->ersu + 6) & 0xf) << 3) + | (txdesc->field_2f & 0x87); + + if ((uint32_t)tx_rate_config->dcm) + txdesc->field_31 |= 0x80; + } + else if (phymode == WIFI_PHY_MODE_VHT20) + txdesc->flags |= 0x1000000; + + // OTM's own comment on this line upstream: "No idea if this is correct, but this is + // what the original code does...". This project always passes WIFI_BW20 (see + // c5_radio.cpp), so bw_is_bw40 is always 0 here; see the investigation doc above for + // what is and isn't verified about ITS-G5's 10 MHz channel width on this path. + uint32_t bw_is_bw40 = bw == WIFI_BW40; + txdesc->field_8 = (bw_is_bw40 << 0xf) | (txdesc->field_8 & 0xffff7fff); + + if (en_sys_seq) + txdesc->flags |= 1; + + txdesc->field_10 = + (txdesc->field_10 & 0xfff3ffff) | ((ifx & WIFI_IF_MAX) << 0x12); + txdesc->field_14 = 0x100; + + result = ieee80211_post_hmac_tx(eb); + g_osi_funcs_p->_mutex_unlock(g_wifi_global_lock); + } + else + { + result = ESP_ERR_NO_MEM; + g_osi_funcs_p->_mutex_unlock(g_wifi_global_lock); + } + } + + return result; +} diff --git a/obu-firmware/main/otm_tx_custom.h b/obu-firmware/main/otm_tx_custom.h new file mode 100644 index 0000000..0cd35fd --- /dev/null +++ b/obu-firmware/main/otm_tx_custom.h @@ -0,0 +1,23 @@ +#pragma once + +// Sourced from OpenTrafficMap's its-g5-receiver-firmware_txenabled, main/tx_custom.h +// (https://codeberg.org/opentrafficmap/its-g5-receiver-firmware_txenabled, pinned commit +// 674e34128279235ba34c9f8d778f43cf1d075397, no stated license). That project reverse-engineered +// this ESP32-C5 ROM/libphy entry point -- it is not documented or supported by Espressif. +// See experiments/evidence/c5-radio-characterization/phy-internals-investigation.md for what +// this repository independently verified (by disassembling libphy.a) versus what is still an +// unverified upstream guess. + +#include "esp_wifi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t esp_wifi_80211_tx_custom(wifi_interface_t ifx, const void *buffer, int32_t len, + bool en_sys_seq, wifi_tx_rate_config_t *tx_rate_config, + wifi_band_t band, wifi_bandwidth_t bw); + +#ifdef __cplusplus +} +#endif diff --git a/obu-firmware/main/serial_link.cpp b/obu-firmware/main/serial_link.cpp new file mode 100644 index 0000000..30e5a79 --- /dev/null +++ b/obu-firmware/main/serial_link.cpp @@ -0,0 +1,160 @@ +#include "serial_link.hpp" +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace microbu::serial { +namespace { +std::uint16_t crc16_ccitt_false_step(std::uint16_t crc, std::uint8_t octet) { + crc ^= static_cast(octet) << 8; + for (int bit = 0; bit < 8; ++bit) crc = (crc & 0x8000) ? static_cast((crc << 1) ^ 0x1021) : static_cast(crc << 1); + return crc; +} +} + +std::uint16_t crc16_ccitt_false(const std::uint8_t* data, std::size_t length) { + std::uint16_t crc = 0xFFFF; + for (std::size_t i = 0; i < length; ++i) crc = crc16_ccitt_false_step(crc, data[i]); + return crc; +} + +Bytes encode_frame(FrameType type, const Bytes& payload) { + Bytes out; + out.reserve(payload.size() + 7); + out.push_back(0xAA); out.push_back(0x55); + out.push_back(static_cast(type)); + out.push_back(payload.size() & 0xFF); out.push_back(payload.size() >> 8); + out.insert(out.end(), payload.begin(), payload.end()); + const auto crc = crc16_ccitt_false(out.data() + 2, out.size() - 2); + out.push_back(crc & 0xFF); out.push_back(crc >> 8); + return out; +} + +void Decoder::feed(const std::uint8_t* data, std::size_t length, const Handler& handler) { + for (std::size_t i = 0; i < length; ++i) { + const std::uint8_t b = data[i]; + switch (state_) { + case State::SYNC0: state_ = (b == 0xAA) ? State::SYNC1 : State::SYNC0; break; + case State::SYNC1: state_ = (b == 0x55) ? State::TYPE : (b == 0xAA ? State::SYNC1 : State::SYNC0); break; + case State::TYPE: type_ = b; state_ = State::LEN_LO; break; + case State::LEN_LO: length_ = b; state_ = State::LEN_HI; break; + case State::LEN_HI: + length_ |= static_cast(b) << 8; + payload_.clear(); + if (length_ > maximum_payload) state_ = State::SYNC0; // untrustworthy boundary: resync + else state_ = length_ == 0 ? State::CRC_LO : State::PAYLOAD; + break; + case State::PAYLOAD: + payload_.push_back(b); + if (payload_.size() >= length_) state_ = State::CRC_LO; + break; + case State::CRC_LO: crc_ = b; state_ = State::CRC_HI; break; + case State::CRC_HI: { + crc_ |= static_cast(b) << 8; + // CRC over head then payload without concatenating them: same running value as encode_frame. + const std::uint8_t head[3] = {type_, static_cast(length_ & 0xFF), static_cast(length_ >> 8)}; + std::uint16_t crc = 0xFFFF; + for (auto octet : head) crc = crc16_ccitt_false_step(crc, octet); + for (auto octet : payload_) crc = crc16_ccitt_false_step(crc, octet); + if (crc == crc_) { ++frames_; handler(Frame {static_cast(type_), payload_}); } + else ++crc_errors_; + state_ = State::SYNC0; + break; + } + } + } +} + +namespace { +SemaphoreHandle_t writer_lock = nullptr; +Counters the_counters; +Decoder::Handler frame_handler; +std::atomic last_frame_at_ms {0}; // 32-bit: no libatomic needed on RV32 +#if CONFIG_MICROBU_LOG_OVER_LINK +vprintf_like_t previous_vprintf = nullptr; +#endif + +bool write_all(const Bytes& frame) { + std::size_t sent = 0; + while (sent < frame.size()) { + // MicrOBU: 50 ms, not the colleague's 500. A phone that is plugged in but not reading (app + // not running) fills the driver's buffer, and every write then waits out this timeout on + // the station task, which also serves BLE and the radio. + const int count = usb_serial_jtag_write_bytes(frame.data() + sent, frame.size() - sent, pdMS_TO_TICKS(50)); + if (count <= 0) return false; // the host detects an incomplete frame by CRC + sent += count; + } + return true; +} + +#if CONFIG_MICROBU_LOG_OVER_LINK +// ESP_LOG sink: one LOG frame per call, never a raw write into the frame stream. +int log_to_frame(const char* format, va_list args) { + char line[256]; + const int length = std::vsnprintf(line, sizeof line, format, args); + if (length <= 0) return 0; + std::size_t n = static_cast(length) < sizeof line ? length : sizeof line - 1; + while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r')) --n; + if (n == 0) return length; + // esp_log colour escapes are stripped by the emulator; keep the line verbatim otherwise. + write(FrameType::LOG, Bytes(line, line + n)); + return length; +} +#endif + +void reader_task(void*) { + Decoder decoder; + std::uint8_t buffer[512]; + for (;;) { + const int count = usb_serial_jtag_read_bytes(buffer, sizeof buffer, pdMS_TO_TICKS(20)); + if (count > 0) { + decoder.feed(buffer, static_cast(count), [](Frame frame) { + last_frame_at_ms = static_cast(esp_timer_get_time() / 1000); + frame_handler(std::move(frame)); + }); + the_counters.frames = decoder.frames(); + the_counters.crc_errors = decoder.crc_errors(); + } + } +} +} + +void start(const Decoder::Handler& on_frame) { + frame_handler = on_frame; + writer_lock = xSemaphoreCreateMutex(); + usb_serial_jtag_driver_config_t config {}; + config.rx_buffer_size = 8192; + config.tx_buffer_size = 8192; + ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&config)); +#if CONFIG_MICROBU_LOG_OVER_LINK + previous_vprintf = esp_log_set_vprintf(log_to_frame); +#endif + xTaskCreate(reader_task, "link_rx", 6144, nullptr, 12, nullptr); +} + +bool write(FrameType type, const Bytes& payload) { + if (payload.size() > maximum_payload || !writer_lock) return false; + // MicrOBU: nothing on the native port (phone on BLE, or unplugged). Without this check every + // write, the 1 Hz STATUS included, would block the station task until the timeout. + if (!usb_serial_jtag_is_connected()) { ++the_counters.not_connected; return false; } + const auto frame = encode_frame(type, payload); + if (xSemaphoreTake(writer_lock, pdMS_TO_TICKS(1000)) != pdTRUE) { ++the_counters.write_failures; return false; } + const bool ok = write_all(frame); + xSemaphoreGive(writer_lock); + if (!ok) ++the_counters.write_failures; + return ok; +} + +Counters counters() { return the_counters; } + +std::uint32_t last_frame_ms() { return last_frame_at_ms.load(); } + +} // namespace microbu::serial diff --git a/obu-firmware/main/serial_link.hpp b/obu-firmware/main/serial_link.hpp new file mode 100644 index 0000000..62511d9 --- /dev/null +++ b/obu-firmware/main/serial_link.hpp @@ -0,0 +1,75 @@ +#pragma once +// Serial transport of the station-internal link over the ESP32-C5 native USB Serial/JTAG port. +// Framing is the app's Phase 03 one: [AA][55][type][len LE][payload][crc16 LE] with +// CRC-16/CCITT-FALSE over type+len+payload (implementation/station-link/README.md, "Serial"). +// One writer serialises complete frames. The byte stream never carries plain text: ESP_LOG stays on +// UART0 here, or travels as LOG frames with CONFIG_MICROBU_LOG_OVER_LINK. +#include +#include +#include +#include + +namespace microbu::serial { + +/// @brief Byte buffer type for frame payloads. +using Bytes = std::vector; + +/// @brief The type of a frame. Either Link (link protocol), Test (test channel), or Log (ESP_LOG output). +enum class FrameType : std::uint8_t { LINK = 0x10, TEST = 0x11, LOG = 0x7F }; + +constexpr std::size_t maximum_payload = 1536; + +/// @brief A complete frame with a type and a payload. +struct Frame { FrameType type; Bytes payload; }; + +/// @brief Computes the CRC-16/CCITT-FALSE checksum over a byte range. +/// @param data Pointer to the first byte to checksum. +/// @param length Number of bytes to checksum. +/// @return Checksum value. +std::uint16_t crc16_ccitt_false(const std::uint8_t* data, std::size_t length); +/// @brief Frames a payload as [AA][55][type][len LE][payload][crc16 LE]. +/// @param type Frame type tag. +/// @param payload Payload bytes; must not exceed maximum_payload. +/// @return Encoded frame bytes. +Bytes encode_frame(FrameType type, const Bytes& payload); + +/// Byte-at-a-time decoder, same state machine as the app's SerialFrameDecoder. +class Decoder { +public: + using Handler = std::function; + /// @brief Feeds raw bytes through the frame state machine, invoking the handler per complete frame. + /// @param data Pointer to the first byte to feed. + /// @param length Number of bytes to feed. + /// @param handler Invoked once per complete, CRC-valid frame; may be called zero or more times. + void feed(const std::uint8_t* data, std::size_t length, const Handler& handler); + /// @return Number of frames rejected for a CRC mismatch so far. + std::uint32_t crc_errors() const { return crc_errors_; } + /// @return Number of frames decoded successfully so far. + std::uint32_t frames() const { return frames_; } +private: + enum class State { SYNC0, SYNC1, TYPE, LEN_LO, LEN_HI, PAYLOAD, CRC_LO, CRC_HI } state_ = State::SYNC0; + std::uint8_t type_ = 0; + std::uint16_t length_ = 0, crc_ = 0; + Bytes payload_; + std::uint32_t crc_errors_ = 0, frames_ = 0; +}; + +struct Counters { std::uint32_t frames = 0, crc_errors = 0, write_failures = 0, not_connected = 0; }; + +/// @brief Installs the USB Serial/JTAG driver and starts the reader task. ESP_LOG goes into LOG frames +/// only with CONFIG_MICROBU_LOG_OVER_LINK; on this board it stays on the UART0 console. +/// @param on_frame Invoked once per complete, CRC-valid frame. +/// @note The handler runs on the reader task; it must only enqueue, never block. +void start(const Decoder::Handler& on_frame); +/// @brief Writes one complete frame (blocking, mutex-protected). Safe from any task. +/// @param type Frame type tag. +/// @param payload Payload bytes; must not exceed maximum_payload. +/// @return false on a full payload, missing driver, lock timeout, or write failure. +bool write(FrameType type, const Bytes& payload); +/// @return Current frame/error counters. +Counters counters(); +/// @return esp_timer time in ms (wrapping) at which the last CRC-valid frame arrived from the host, +/// 0 if none yet. MicrOBU: app_main pauses BLE advertising while this is recent. +std::uint32_t last_frame_ms(); + +} // namespace microbu::serial diff --git a/obu-firmware/main/simple_ble.cpp b/obu-firmware/main/simple_ble.cpp new file mode 100644 index 0000000..a18b256 --- /dev/null +++ b/obu-firmware/main/simple_ble.cpp @@ -0,0 +1,493 @@ +#include "simple_ble.hpp" +#include "sdkconfig.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +extern "C" void ble_store_config_init(void); + +namespace microbu::ble { +namespace { + +const char* TAG = "cits_ble"; +constexpr uint32_t FIXED_PASSKEY = 123456; + +// Base UUID: 0000xxxx-ba5e-4c17-8000-00805f9b34fb +// Little-endian byte order for NimBLE (16 bytes, index 15 down to 0): +#define CITS_UUID_BASE(id_lo, id_hi) \ + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, \ + 0x17, 0x4c, 0x5e, 0xba, (id_lo), (id_hi), 0x00, 0x00 + +static const ble_uuid128_t s_svc_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x75, 0xc1)); +static const ble_uuid128_t s_chr_btp_req_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x76, 0xc1)); +static const ble_uuid128_t s_chr_btp_ind_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x77, 0xc1)); +static const ble_uuid128_t s_chr_poti_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x78, 0xc1)); +static const ble_uuid128_t s_chr_status_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x79, 0xc1)); +static const ble_uuid128_t s_chr_id_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x7a, 0xc1)); +static const ble_uuid128_t s_chr_config_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x7b, 0xc1)); +static const ble_uuid128_t s_chr_result_uuid = BLE_UUID128_INIT(CITS_UUID_BASE(0x7c, 0xc1)); + +enum class ChrId : uintptr_t { + BtpRequest = 1, + BtpIndication = 2, + Poti = 3, + Status = 4, + IdChange = 5, + Configure = 6, + Result = 7, +}; + +Receiver s_receiver; +Counters statistics; +char s_dev_name[32] = "micrOBU"; +uint16_t s_btp_ind_val_handle = 0; +uint16_t s_status_val_handle = 0; +uint16_t s_id_val_handle = 0; +uint16_t s_result_val_handle = 0; +volatile uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE; +int s_notify_count = 0; // number of characteristics with active CCCD subscription +bool s_subscribed = false; +uint8_t s_own_addr_type = 0; +volatile bool s_synced = false; +volatile bool s_adv_allowed = true; + +// GATT characteristic read/write callback, shared by all characteristics (arg identifies which). +int chr_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) { + uintptr_t id = reinterpret_cast(arg); + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + uint16_t total = OS_MBUF_PKTLEN(ctxt->om); + if (total == 0 || total > link::maximum_message) { + ESP_LOGW(TAG, "rx invalid length: %u", (unsigned)total); + ++statistics.malformed; + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + link::Bytes buf(total); + uint16_t copied = 0; + int rc = ble_hs_mbuf_to_flat(ctxt->om, buf.data(), total, &copied); + if (rc != 0 || copied != total) { + ++statistics.malformed; + return BLE_ATT_ERR_UNLIKELY; + } + ++statistics.rx_messages; + if (s_receiver) { + // Require valid link header (opcode + flags + seq). + // Host->station opcodes are 0x01..0x0F; station->host are 0x80+. + if (total >= link::header_size && buf[0] >= 0x01 && buf[0] <= 0x0F) { + ESP_LOGD(TAG, "rx chr %u op=0x%02x len=%u", (unsigned)id, buf[0], (unsigned)total); + s_receiver(std::move(buf)); + } else { + ESP_LOGW(TAG, "rx chr %u unframed or invalid op=0x%02x len=%u", (unsigned)id, buf[0], (unsigned)total); + ++statistics.malformed; + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + } + return 0; + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { + if (id == static_cast(ChrId::Status)) { + return 0; // empty read; status is pushed via notify + } + } + return BLE_ATT_ERR_READ_NOT_PERMITTED; +} + + +#define CHR_FLAG_RW_ENC (BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_READ_AUTHEN | \ + BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_WRITE_AUTHEN) +#define CHR_FLAG_NOTIFY_ENC (BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ + BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN) + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +static struct ble_gatt_chr_def s_chr_defs[] = { + { + // NF-SAP: BTP-DATA.request (Central -> Station Write) + .uuid = &s_chr_btp_req_uuid.u, + .access_cb = chr_access, + .arg = reinterpret_cast(ChrId::BtpRequest), + .flags = BLE_GATT_CHR_F_WRITE | CHR_FLAG_RW_ENC, + }, + { + // NF-SAP: BTP-DATA.indication (Station -> Central Notify) + .uuid = &s_chr_btp_ind_uuid.u, + .access_cb = chr_access, + .arg = reinterpret_cast(ChrId::BtpIndication), + .flags = BLE_GATT_CHR_F_NOTIFY | CHR_FLAG_NOTIFY_ENC, + .val_handle = &s_btp_ind_val_handle, + }, + { + // PoTi: Position & Time Fix Update (Central -> Station Write) + .uuid = &s_chr_poti_uuid.u, + .access_cb = chr_access, + .arg = reinterpret_cast(ChrId::Poti), + .flags = BLE_GATT_CHR_F_WRITE | CHR_FLAG_RW_ENC, + }, + { + // Station Status (Station -> Central Read/Notify) + .uuid = &s_chr_status_uuid.u, + .access_cb = chr_access, + .arg = reinterpret_cast(ChrId::Status), + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | CHR_FLAG_RW_ENC | CHR_FLAG_NOTIFY_ENC, + .val_handle = &s_status_val_handle, + }, + { + // SF-SAP: Identity Change Event (Station -> Central Notify) + .uuid = &s_chr_id_uuid.u, + .access_cb = chr_access, + .arg = reinterpret_cast(ChrId::IdChange), + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY | CHR_FLAG_RW_ENC | CHR_FLAG_NOTIFY_ENC, + .val_handle = &s_id_val_handle, + }, + { + // Station Configure / Credentials (Central -> Station Write) + .uuid = &s_chr_config_uuid.u, + .access_cb = chr_access, + .arg = reinterpret_cast(ChrId::Configure), + .flags = BLE_GATT_CHR_F_WRITE | CHR_FLAG_RW_ENC, + }, + { + // Result / Response (Station -> Central Notify) + .uuid = &s_chr_result_uuid.u, + .access_cb = chr_access, + .arg = reinterpret_cast(ChrId::Result), + .flags = BLE_GATT_CHR_F_NOTIFY | CHR_FLAG_NOTIFY_ENC, + .val_handle = &s_result_val_handle, + }, + {0} +}; + +static struct ble_gatt_svc_def s_svc_defs[] = { + { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &s_svc_uuid.u, + .characteristics = s_chr_defs, + }, + {0} +}; +#pragma GCC diagnostic pop + +int start_advertising(); + +// NimBLE GAP callback: connect/disconnect, pairing (fixed-passkey), and notify subscriptions. +int gap_event(struct ble_gap_event *event, void *arg) { + struct ble_gap_conn_desc desc; + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + ESP_LOGI(TAG, "connect status=%d handle=%d", event->connect.status, event->connect.conn_handle); + if (event->connect.status == 0) { + s_conn_handle = event->connect.conn_handle; + s_subscribed = false; + struct ble_gap_upd_params params = {}; + params.itvl_min = 12; // 15 ms + params.itvl_max = 16; // 20 ms + params.latency = 0; + params.supervision_timeout = 400; // 4 s + params.min_ce_len = 0; + params.max_ce_len = 0; + ble_gap_update_params(event->connect.conn_handle, ¶ms); + ble_gap_security_initiate(event->connect.conn_handle); + } else { + start_advertising(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "disconnect reason=%d", event->disconnect.reason); + s_conn_handle = BLE_HS_CONN_HANDLE_NONE; + s_subscribed = false; + s_notify_count = 0; + start_advertising(); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + // MicrOBU: logged, since a phone that pairs again while we hold its bond means one side + // lost the keys; the phone's side then usually needs "forget device" as well. + ESP_LOGW(TAG, "phone started pairing again although bonded: dropping the old bond"); + if (ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc) == 0) { + ble_store_util_delete_peer(&desc.peer_id_addr); + } + return BLE_GAP_REPEAT_PAIRING_RETRY; + + case BLE_GAP_EVENT_ENC_CHANGE: + // MicrOBU: the one place the board side says whether the link got secure. 0 = encrypted. + if (ble_gap_conn_find(event->enc_change.conn_handle, &desc) == 0) { + ESP_LOGI(TAG, "encryption change status=%d encrypted=%d authenticated=%d bonded=%d", + event->enc_change.status, desc.sec_state.encrypted, desc.sec_state.authenticated, + desc.sec_state.bonded); + } else { + ESP_LOGI(TAG, "encryption change status=%d", event->enc_change.status); + } + return 0; + + case BLE_GAP_EVENT_MTU: + ESP_LOGI(TAG, "ATT MTU %u", unsigned(event->mtu.value)); + return 0; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + struct ble_sm_io pkey = {}; + pkey.action = BLE_SM_IOACT_DISP; + pkey.passkey = FIXED_PASSKEY; + ESP_LOGW(TAG, "BLE PAIRING FIXED PASSKEY: %06lu", static_cast(FIXED_PASSKEY)); + int rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + if (rc != 0) { + ESP_LOGW(TAG, "ble_sm_inject_io rc=%d", rc); + } + } else { + ESP_LOGW(TAG, "unhandled passkey action %d", event->passkey.params.action); + } + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + if (event->subscribe.attr_handle == s_btp_ind_val_handle || + event->subscribe.attr_handle == s_status_val_handle || + event->subscribe.attr_handle == s_id_val_handle || + event->subscribe.attr_handle == s_result_val_handle) { + if (event->subscribe.cur_notify && !event->subscribe.prev_notify) + ++s_notify_count; + else if (!event->subscribe.cur_notify && event->subscribe.prev_notify) + --s_notify_count; + if (s_notify_count < 0) s_notify_count = 0; + s_subscribed = (s_notify_count > 0); + ESP_LOGI(TAG, "subscription handle %u changed: notify=%d count=%d", + event->subscribe.attr_handle, (int)event->subscribe.cur_notify, s_notify_count); + } + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + start_advertising(); + return 0; + + default: + return 0; + } +} + +// (Re)starts undirected advertising with the device name and service UUID, unless paused. +int start_advertising() { + if (!s_adv_allowed || !s_synced || s_conn_handle != BLE_HS_CONN_HANDLE_NONE || ble_gap_adv_active()) return 0; + const char* name = s_dev_name; + size_t name_len = strlen(name); + + struct ble_hs_adv_fields adv = {}; + adv.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + adv.tx_pwr_lvl_is_present = 1; + adv.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + adv.name = reinterpret_cast(name); + adv.name_len = static_cast(name_len); + adv.name_is_complete = 1; + + int rc = ble_gap_adv_set_fields(&adv); + if (rc != 0) { + ESP_LOGE(TAG, "adv_set_fields rc=%d", rc); + return rc; + } + + struct ble_hs_adv_fields rsp = {}; + rsp.uuids128 = &s_svc_uuid; + rsp.num_uuids128 = 1; + rsp.uuids128_is_complete = 1; + + rc = ble_gap_adv_rsp_set_fields(&rsp); + if (rc != 0) { + ESP_LOGE(TAG, "adv_rsp_set_fields rc=%d", rc); + return rc; + } + + struct ble_gap_adv_params params = {}; + params.conn_mode = BLE_GAP_CONN_MODE_UND; + params.disc_mode = BLE_GAP_DISC_MODE_GEN; + + rc = ble_gap_adv_start(s_own_addr_type, nullptr, BLE_HS_FOREVER, + ¶ms, gap_event, nullptr); + if (rc != 0) { + ESP_LOGE(TAG, "adv_start rc=%d", rc); + return rc; + } + ESP_LOGI(TAG, "BLE advertising started as '%s'", name); + return 0; +} + +// NimBLE host reset callback. +void on_reset(int reason) { + ESP_LOGE(TAG, "NimBLE reset, reason=%d", reason); +} + +// NimBLE host sync callback: resolves our address and starts advertising. +void on_sync() { + int rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + rc = ble_hs_id_infer_auto(0, &s_own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "ble_hs_id_infer_auto rc=%d", rc); + return; + } + s_synced = true; + // MicrOBU: a bond that did not persist (full NVS) looks like "the phone has to pair every time". + int bonds = 0; + if (ble_store_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC, &bonds) == 0) + ESP_LOGI(TAG, "%d bonded phone(s) in NVS", bonds); + start_advertising(); +} + +void nimble_host_task(void* param) { + ESP_LOGI(TAG, "NimBLE host task running"); + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +} // namespace + +bool start(Receiver receiver) { + s_receiver = std::move(receiver); + + uint8_t mac[6] = {}; + if (esp_read_mac(mac, ESP_MAC_BT) == ESP_OK) { + std::snprintf(s_dev_name, sizeof(s_dev_name), "micrOBU-%02X%02X", mac[4], mac[5]); + } + + esp_err_t err = nimble_port_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "nimble_port_init failed: %d", err); + return false; + } + + ble_hs_cfg.reset_cb = on_reset; + ble_hs_cfg.sync_cb = on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + // Fixed passkey LE Secure Connections + Bonding + ble_hs_cfg.sm_io_cap = BLE_HS_IO_DISPLAY_ONLY; + ble_hs_cfg.sm_sc = 1; + ble_hs_cfg.sm_bonding = 1; + ble_hs_cfg.sm_mitm = 1; + ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID; + ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID; + + ble_svc_gap_init(); + ble_svc_gatt_init(); + ble_svc_gap_device_name_set(s_dev_name); + + int rc = ble_gatts_count_cfg(s_svc_defs); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gatts_count_cfg failed: %d", rc); + nimble_port_deinit(); + return false; + } + rc = ble_gatts_add_svcs(s_svc_defs); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gatts_add_svcs failed: %d", rc); + nimble_port_deinit(); + return false; + } + + ble_store_config_init(); + nimble_port_freertos_init(nimble_host_task); + ESP_LOGI(TAG, "simple_ble initialized (fixed passkey %06lu)", static_cast(FIXED_PASSKEY)); + return true; +} + +bool write(const link::Bytes& message) { + uint16_t conn_handle = s_conn_handle; + if (conn_handle == BLE_HS_CONN_HANDLE_NONE) { + ++statistics.tx_failed; + return false; + } + if (message.empty() || message.size() > link::maximum_message) { + ++statistics.tx_failed; + return false; + } + uint16_t val_handle = s_result_val_handle; + if (message.size() >= link::header_size) { + link::Opcode op = static_cast(message[0]); + switch (op) { + case link::Opcode::V2X_RX: + case link::Opcode::BTP_DATA_INDICATION: + val_handle = s_btp_ind_val_handle; + break; + case link::Opcode::SF_IDCHANGE_EVENT: + val_handle = s_id_val_handle; + break; + case link::Opcode::STATUS: + // Route unsolicited or requested STATUS over the subscribed result/reply characteristic + val_handle = s_result_val_handle; + break; + case link::Opcode::RESULT: + default: + val_handle = s_result_val_handle; + break; + } + } + // MicrOBU: one notification is one whole message (no fragmentation on this GATT layout), and + // NimBLE cuts a value longer than ATT_MTU - 3 short without an error. The phone asks for an + // MTU of 517; until it has, drop rather than deliver a truncated message. + if (message.size() + 3 > ble_att_mtu(conn_handle)) { + ++statistics.tx_failed; + return false; + } + struct os_mbuf *om = ble_hs_mbuf_from_flat(message.data(), message.size()); + if (!om) { + ++statistics.tx_failed; + return false; + } + int rc = ble_gatts_notify_custom(conn_handle, val_handle, om); + if (rc != 0) { + ESP_LOGW(TAG, "notify failed handle=%u op=0x%02x rc=%d", (unsigned)val_handle, (unsigned)(message.empty() ? 0 : message[0]), rc); + if (rc == BLE_HS_ENOTSUP) os_mbuf_free_chain(om); + ++statistics.tx_failed; + return false; + } + ++statistics.tx_messages; + return true; +} + +bool connected() { + return s_conn_handle != BLE_HS_CONN_HANDLE_NONE && s_subscribed; +} + +void set_advertising_allowed(bool allowed) { + if (s_adv_allowed == allowed) return; + s_adv_allowed = allowed; + if (!s_synced) return; + if (allowed) { + start_advertising(); + ESP_LOGI(TAG, "USB link idle: BLE advertising resumed"); + } else if (ble_gap_adv_active()) { + ble_gap_adv_stop(); + ESP_LOGI(TAG, "USB link in use: BLE advertising paused"); + } +} + +bool advertising_allowed() { + return s_adv_allowed; +} + +bool is_subscribed() { + return s_subscribed; +} + +Counters counters() { + return statistics; +} + +} // namespace microbu::ble diff --git a/obu-firmware/main/simple_ble.hpp b/obu-firmware/main/simple_ble.hpp new file mode 100644 index 0000000..397352c --- /dev/null +++ b/obu-firmware/main/simple_ble.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "link_protocol.hpp" +#include +#include + +namespace microbu::ble { + +using Receiver = std::function; + +/// @brief A set of counters for tracking BLE message traffic. +struct Counters { + std::uint32_t rx_messages = 0; + std::uint32_t tx_messages = 0; + std::uint32_t tx_failed = 0; + std::uint32_t malformed = 0; +}; + +/** + * @brief Starts a simple, bonded BLE GATT peripheral on the NimBLE host. + * Uses structured ETSI C-ITS Station Service & Characteristics: + * Service: 0000C175-BA5E-4C17-8000-00805F9B34FB + * BTP-DATA.request: 0000C176-BA5E-4C17-8000-00805F9B34FB (Write, Authenticated/Encrypted) + * BTP-DATA.indication: 0000C177-BA5E-4C17-8000-00805F9B34FB (Notify, Authenticated/Encrypted) + * PoTi Fix Update: 0000C178-BA5E-4C17-8000-00805F9B34FB (Write, Authenticated/Encrypted) + * Station Status: 0000C179-BA5E-4C17-8000-00805F9B34FB (Read / Notify, Authenticated/Encrypted) + * SF-SAP Identity: 0000C17A-BA5E-4C17-8000-00805F9B34FB (Notify, Authenticated/Encrypted) + * Station Configure: 0000C17B-BA5E-4C17-8000-00805F9B34FB (Write, Authenticated/Encrypted) + * Result: 0000C17C-BA5E-4C17-8000-00805F9B34FB (Notify, Authenticated/Encrypted) + * + * Fixed passkey bonding: 123456. + * Single ATT message transmission/reception up to 512 bytes without fragmentation chunk delays. + * @param receiver Invoked with each decoded station-link message written by the phone. + * @return false if the NimBLE host or GATT service failed to start. + */ +bool start(Receiver receiver); + +/** + * @brief Sends one complete station-link message via GATT notification. + * Dispatches immediately without queuing delay. + * @param message Encoded station-link message to notify. + * @return false if not connected/subscribed or the notification could not be sent. + */ +bool write(const link::Bytes& message); + +/// @return true if a phone is connected. +bool connected(); +/// @brief MicrOBU: allows or stops advertising (an existing connection is never dropped). Called +/// with false while the phone uses the USB link, so BLE takes no airtime from ITS-G5. +void set_advertising_allowed(bool allowed); +/// @return true while advertising is allowed (see set_advertising_allowed). +bool advertising_allowed(); +/// @return true if the phone has subscribed to notifications. +bool is_subscribed(); +/// @return Current message/error counters. +Counters counters(); + +} // namespace microbu::ble diff --git a/obu-firmware/main/station.cpp b/obu-firmware/main/station.cpp new file mode 100644 index 0000000..19e174d --- /dev/null +++ b/obu-firmware/main/station.cpp @@ -0,0 +1,653 @@ +#include "station.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "c5_radio.hpp" +extern "C" { +#include "geonet.h" +#include "gn_unwrap.h" +} +#include +#include +#include +#include + +namespace microbu { +using namespace vanetza_idf; +using vanetza::ByteBuffer; +namespace gn = vanetza::geonet; +namespace { +const char* TAG = "station"; + +link::Code code(Result result) { return static_cast(result); } + +// Numbering of the link's "Packet transport type" field matches the TRANSP_CORE.request/.indication +// service primitive parameter of ETSI TS 103 836-4-1 annex J.2/J.4 (0 GUC, 1 SHB, 2 TSB, 3 GBC, +// 4 GAC); it does not reuse the HT wire encoding of clause 9.7.4 table 9, which numbers these +// differently (GEOUNICAST=2, GEOANYCAST=3, GEOBROADCAST=4, TSB=5, no separate SHB value there). +std::uint8_t transport_number(gn::TransportType t) { + switch (t) { + case gn::TransportType::GUC: return 0; + case gn::TransportType::SHB: return 1; + case gn::TransportType::TSB: return 2; + case gn::TransportType::GBC: return 3; + case gn::TransportType::GAC: return 4; + } + return 0; +} +std::optional transport_from(std::uint8_t n) { + switch (n) { + case 0: return gn::TransportType::GUC; + case 1: return gn::TransportType::SHB; + case 2: return gn::TransportType::TSB; + case 3: return gn::TransportType::GBC; + case 4: return gn::TransportType::GAC; + } + return std::nullopt; +} + +// Wire DestinationArea -> vanetza gn::Area (GBC destination). +gn::Area area_from(const link::DestinationArea& a) { + gn::Area area; + switch (a.shape) { + case 0: { gn::Circle c; c.r = double(a.distance_a) * vanetza::units::si::meter; area.shape = c; break; } + case 1: { gn::Rectangle r; r.a = double(a.distance_a) * vanetza::units::si::meter; r.b = double(a.distance_b) * vanetza::units::si::meter; area.shape = r; break; } + default: { gn::Ellipse e; e.a = double(a.distance_a) * vanetza::units::si::meter; e.b = double(a.distance_b) * vanetza::units::si::meter; area.shape = e; break; } + } + area.position = gn::GeodeticPosition(a.latitude / 1.0e7 * vanetza::units::degree, a.longitude / 1.0e7 * vanetza::units::degree); + area.angle = vanetza::units::Angle(double(a.angle) * vanetza::units::degree); + return area; +} + +// gn::Area -> wire DestinationArea (the inverse of area_from). +struct AreaToLink : boost::static_visitor { + const gn::Area& area; + explicit AreaToLink(const gn::Area& a) : area(a) {} + link::DestinationArea common() const { + link::DestinationArea out; + out.latitude = static_cast(area.position.latitude.value() * 1.0e7); + out.longitude = static_cast(area.position.longitude.value() * 1.0e7); + out.angle = static_cast(area.angle.value()); + return out; + } + link::DestinationArea operator()(const gn::Circle& c) const { auto o = common(); o.shape = 0; o.distance_a = c.r.value(); return o; } + link::DestinationArea operator()(const gn::Rectangle& r) const { auto o = common(); o.shape = 1; o.distance_a = r.a.value(); o.distance_b = r.b.value(); return o; } + link::DestinationArea operator()(const gn::Ellipse& e) const { auto o = common(); o.shape = 2; o.distance_a = e.a.value(); o.distance_b = e.b.value(); return o; } +}; + +// 0 unsecured (no security envelope), 1 + VerificationReport ordinal otherwise +struct ReportToLink : boost::static_visitor { + ReportToLink() = default; + std::uint8_t operator()(boost::blank) const { return 0; } + std::uint8_t operator()(vanetza::security::VerificationReport r) const { return 1 + static_cast(r); } +}; +} + +// Credentials, backend, trust configuration, ticket pool and the entity built on them. +struct Station::Security { + BackendMbedTls backend; + security::TrustConfiguration trust; + security::CertificatePool pool {backend}; + std::unique_ptr entity; + bool loaded = false; + security::ApplyReport report; +}; + +std::int64_t Station::Clock::now_us() const { + return base_its_us + (esp_timer_get_time() - base_esp_us); +} + +Station::Station() = default; +Station::~Station() { teardown(); } + +void Station::teardown() { + rebuilding_ = true; // the DEREG of TS 102 723-8 Figure 15 is ours, not the phone's + stack_.reset(); // router timers before the runtime + // AccessStack::Dcc's Limeric holds a reference to *runtime_ and calls into it from its + // destructor (Runtime::cancel): it must be destroyed before runtime_ is. + access_stack_.reset(); + security_.reset(); + runtime_.reset(); + dcc_cca_last_.reset(); + dcc_last_sample_its_us_ = 0; + pending_responders_.clear(); + rebuilding_ = false; +} + +// TS 102 894-2 station config -> the GeoNetworking MIB (TS 103 836-4-1). +void Station::apply_mib(StackConfig& config, const link::StationConfigure& c) const { + config.mib.itsGnLocalGnAddr.mid(vanetza::MacAddress {c.mid[0], c.mid[1], c.mid[2], c.mid[3], c.mid[4], c.mid[5]}); + config.mib.itsGnLocalGnAddr.station_type(static_cast(c.station_type & 0x1F)); + config.mib.itsGnLocalGnAddr.is_manually_configured(c.address_configuration == 0); + config.mib.itsGnLocalAddrConfMethod = c.address_configuration == 1 ? gn::AddrConfMethod::Anonymous : gn::AddrConfMethod::Auto; + config.mib.itsGnSecurity = c.security == 1; + config.mib.vanetzaDisableBeaconing = c.beaconing == 0; + config.mib.itsGnDefaultTrafficClass = gn::TrafficClass(c.default_traffic_class); + gn::Lifetime lifetime; lifetime.raw(c.default_lifetime); + config.mib.itsGnDefaultPacketLifetime = lifetime; + config.mib.itsGnMaxPacketLifetime = lifetime < config.mib.itsGnMaxPacketLifetime ? config.mib.itsGnMaxPacketLifetime : lifetime; + config.radio_parameters.channel_number = c.channel_number; + config.radio_parameters.transmit_power_dbm = c.transmit_power_dbm; +} + +// Carry the phone's SF-SAP pseudonym-change subscriptions (TS 102 723-8 clause 6.3) +// over to the identity manager of a freshly rebuilt security entity. +void Station::resubscribe_id_change() { + std::map renewed; + for (const auto& [handle, old_service] : link_subscriptions_) { + (void)old_service; + renewed[handle] = security_->entity->id_change().subscribe( + [this, handle](security::IdChangeCommand command, const security::Identifier& id, const ByteBuffer& data, + std::shared_ptr responder) { + link::IdChangeEvent event; + event.subscription = handle; + event.command = static_cast(command); + std::copy(id.begin(), id.end(), event.id); + event.subscriber_data = data; + if (responder) pending_responders_[handle] = responder; + if (id_event_ && !rebuilding_) id_event_(event); + }); + } + link_subscriptions_ = renewed; +} + +// (Re)build stack and security entity from config_, the stored credentials and the current clock. +link::Code Station::build() { + teardown(); + const auto& c = *config_; + runtime_ = std::make_unique(vanetza::Clock::time_point(std::chrono::microseconds(clock_.synchronised ? clock_.now_us() : 0))); + StackConfig config; + apply_mib(config, c); + radio_parameters_ = config.radio_parameters; + vanetza::security::SecurityEntity* entity = nullptr; + if (config.mib.itsGnSecurity) { + security_ = std::make_unique(); + security::Credentials credentials; + security::NvsCredentialStore store; + const auto load = store.load(credentials); + if (load == Result::accepted) { + security_->report = security::apply(credentials, security_->trust, security_->pool); + security_->loaded = security_->report.result == Result::accepted && !security_->pool.empty(); + ESP_LOGI(TAG, "credentials from NVS: %u roots, %u authorities, %u tickets (result %d)", + unsigned(security_->report.roots), unsigned(security_->report.authorities), unsigned(security_->report.tickets), + int(security_->report.result)); + } else if (load == Result::rejected) { + ESP_LOGW(TAG, "no credentials in NVS: secured requests will be refused until provisioned"); + } else { + ESP_LOGE(TAG, "stored credentials do not decode (result %d)", int(load)); + } + security_->entity = std::make_unique(*runtime_, *this, security_->backend, security_->pool, security_->trust); + entity = security_->entity.get(); + resubscribe_id_change(); + } + stack_ = std::make_unique(config, *runtime_, *this, entity); + stack_->on_receive([this](BtpIndication indication) { deliver(std::move(indication)); }); +#if CONFIG_MICROBU_TEST_CHANNEL + stack_->on_receive_gn([this](GnIndication indication) { record(3, std::move(indication.data)); }); +#endif + stack_->on_access_result([](Result) { /* counted in Station::request, the adapter itself */ }); + // DCC_ACC gate in front of whatever radio_ currently is (rebuilt here rather than in + // Station::configure() because it must reference the fresh runtime_, not a torn-down one -- + // see the destruction-order comment in Station::teardown()). + if (radio_) { + access_stack_ = std::make_unique(*radio_); + access_stack_->enable_dcc(*runtime_); + stack_->report_tx_power(static_cast(std::lround(radio_parameters_.transmit_power_dbm))); + } + dcc_cca_last_.reset(); + dcc_last_sample_its_us_ = clock_.synchronised ? clock_.now_us() : 0; + if (have_fix_) apply_position(); + return link::Code::accepted; +} + +link::Code Station::configure(const link::StationConfigure& c, link::Bytes& detail) { + const bool radio_changed = !config_ || config_->radio != c.radio || config_->channel_number != c.channel_number || + config_->transmit_power_dbm != c.transmit_power_dbm; + if (radio_changed) { + radio_.reset(); + if (c.radio != 0) { + C5RadioConfig rc; + rc.channel_number = c.channel_number; + rc.transmit_power_dbm = c.transmit_power_dbm; + rc.laboratory_transmission = c.radio == 2; + radio_ = std::make_unique(rc); + const auto error = radio_->start(); + if (error != ESP_OK) { + ESP_LOGE(TAG, "radio start failed: %s", esp_err_to_name(error)); + radio_.reset(); + return link::Code::rejected; + } + ESP_LOGI(TAG, "radio on channel %u, %s", unsigned(c.channel_number), c.radio == 2 ? "transmit and receive" : "receive only"); + } + } + config_ = c; + link_subscriptions_.clear(); // a configuration starts a phone session: earlier subscriptions are void + counters_ = link::Status {}; // and the session's counters start at zero + const auto result = build(); + if (result != link::Code::accepted) return result; + link::Writer w; + ByteBuffer address; + gn::serialize_into_buffer(stack_->address(), address); + w.bytes(address); + security::Identifier identifier {}; + if (security_ && security_->entity) identifier = security_->entity->id_change().current_identifier(); + w.bytes(identifier.data(), 8); + w.u8(security_ && security_->loaded ? 1 : 0); + w.u8(security_ ? static_cast(std::min(security_->pool.size(), 255)) : 0); + detail = w.out; + counters_.configured = 1; + return link::Code::accepted; +} + +void Station::apply_position() { + if (!stack_ || !have_fix_) return; + // never ahead of the station clock (Stack::update_position rejects that) + const auto now = runtime_->now(); + if (fix_.timestamp > now) fix_.timestamp = now; + const auto result = stack_->update_position(fix_); + if (result != Result::accepted) ESP_LOGW(TAG, "position rejected: %d", int(result)); +} + +link::Code Station::poti(const link::PotiUpdate& p) { + if (p.latitude < -900000000 || p.latitude > 900000000 || p.longitude < -1800000000 || p.longitude > 1800000000) + return link::Code::invalid_argument; + // ITS clock: the phone's PoTi time is the reference; the local esp_timer runs between updates. + const std::int64_t its_us = static_cast(p.timestamp_ms) * 1000; + const std::int64_t esp_us = esp_timer_get_time(); + link::Code result = link::Code::accepted; + if (!clock_.synchronised) { + clock_ = Clock {true, its_us, esp_us}; + if (config_) build(); // the runtime started at 0: restart it at real time + } else { + const std::int64_t drift = its_us - clock_.now_us(); + if (drift >= 0) { + clock_.base_its_us = its_us; clock_.base_esp_us = esp_us; // forward: step immediately + } else if (drift > -1000000) { + // small backward drift: hold the local clock, it catches up with the next updates + } else { + // the reference moved back by more than a second: restart the station at that time + ESP_LOGW(TAG, "ITS time moved back by %lld ms, restarting the station", static_cast(-drift / 1000)); + clock_ = Clock {true, its_us, esp_us}; + if (config_) build(); + result = link::Code::time_regression; + } + } + fix_ = vanetza::PositionFix {}; + fix_.timestamp = vanetza::Clock::time_point(std::chrono::microseconds(std::min(its_us, clock_.now_us()))); + fix_.latitude = p.latitude / 1.0e7 * vanetza::units::degree; + fix_.longitude = p.longitude / 1.0e7 * vanetza::units::degree; + fix_.confidence.semi_major = p.semi_major_cm / 100.0 * vanetza::units::si::meter; + fix_.confidence.semi_minor = p.semi_minor_cm / 100.0 * vanetza::units::si::meter; + fix_.confidence.orientation = p.orientation_deci_degree / 10.0 * vanetza::units::true_north_degrees; + fix_.speed = (p.has_speed() ? p.speed_cm_s / 100.0 : 0.0) * vanetza::units::si::meters_per_second; + fix_.course = (p.has_heading() ? p.heading_deci_degree / 10.0 : 0.0) * vanetza::units::true_north_degrees; + if (p.has_altitude()) fix_.altitude = vanetza::ConfidentQuantity(p.altitude_cm / 100.0 * vanetza::units::si::meter); + have_fix_ = true; + last_poti_ = p; + ++counters_.poti_updates; + tick(); + apply_position(); + return result; +} + +link::Code Station::btp_request(const link::BtpDataRequest& q) { + if (!stack_) return link::Code::not_configured; + if (!clock_.synchronised || !have_fix_) { ++counters_.requests_refused; return link::Code::rejected; } + NF_SAP::BTP_DATA_request request; + request.fl_sdu = q.fl_sdu; + request.length = q.fl_sdu.size(); + request.btp_type = q.btp_type == 0 ? BtpType::a : BtpType::b; + request.destination_port = q.destination_port; + if (request.btp_type == BtpType::a) request.source_port = q.destination_port_info; + else request.destination_port_info = q.destination_port_info; + const auto transport = transport_from(q.gn_packet_transport_type); + if (!transport) { ++counters_.requests_refused; return link::Code::invalid_argument; } + request.gn_packet_transport_type = *transport; + + switch (q.gn_communication_profile) { + case 0: request.gn_communication_profile = gn::CommunicationProfile::Unspecified; break; + case 1: request.gn_communication_profile = gn::CommunicationProfile::ITS_G5; break; + case 2: request.gn_communication_profile = gn::CommunicationProfile::LTE_V2X; break; + default: ++counters_.requests_refused; return link::Code::invalid_argument; + } + // MicrOBU: the colleague's firmware refuses unsecured requests outright. Here the phone's + // "Sign outgoing messages" setting decides, per request; 0 means the station's configuration. + if (q.gn_security_profile > 2) { + ++counters_.requests_refused; + return link::Code::invalid_argument; + } + const bool secured = q.gn_security_profile == 2 || (q.gn_security_profile == 0 && config_->security == 1); + if (!secured) return unsecured_request(q); + request.gn_security_profile = NF_SAP::SecurityProfile::SECURED; + request.gn_traffic_class = q.gn_traffic_class == 0xFF ? stack_->config().mib.itsGnDefaultTrafficClass : gn::TrafficClass(q.gn_traffic_class); + if (q.gn_maximum_packet_lifetime != 0xFF) { gn::Lifetime l; l.raw(q.gn_maximum_packet_lifetime); request.gn_maximum_packet_lifetime = l; } + if (q.gn_maximum_hop_limit) request.gn_maximum_hop_limit = q.gn_maximum_hop_limit; + if (q.gn_repetition_interval_ms) { + gn::DataRequest::Repetition repetition; + repetition.interval = (q.gn_repetition_interval_ms / 1000.0) * vanetza::units::si::seconds; + repetition.maximum = (q.gn_repetition_maximum_ms / 1000.0) * vanetza::units::si::seconds; + request.gn_repetition = repetition; + } + if (*transport == gn::TransportType::GBC) { + if (!q.area) { ++counters_.requests_refused; return link::Code::invalid_argument; } + request.gn_destination_address = area_from(*q.area); + } + request.its_aid = q.its_aid; + request.permissions = q.permissions; + request.context_information = q.context; + tick(); // the packet carries the current time and position + ESP_LOGI(TAG, "heap before sign: free=%lu min=%lu dma=%lu stack_hwm=%u", + (unsigned long)esp_get_free_heap_size(), + (unsigned long)esp_get_minimum_free_heap_size(), + (unsigned long)heap_caps_get_free_size(MALLOC_CAP_DMA), + (unsigned)uxTaskGetStackHighWaterMark(nullptr)); + Result result = Result::rejected; + try { + result = NF_SAP::BTP_DATA_request_submit(*stack_, std::move(request)); + if (result == Result::accepted) ++counters_.requests_accepted; else ++counters_.requests_refused; + } catch (const std::exception& e) { + ESP_LOGE(TAG, "BTP_DATA_request_submit exception: %s", e.what()); + ++counters_.requests_refused; + return link::Code::rejected; + } + return code(result); +} + +void Station::forward_raw(const ByteBuffer& frame, int rssi) { + if (!raw_its_) return; + // Most captured frames are not ITS traffic this handles; false is the common case, not an error. + gn_rx_t rx; + if (!gn_unwrap_its(frame.data(), static_cast(frame.size()), &rx)) return; + constexpr std::size_t prefix = 14; + if (rx.truncated || rx.payload_len <= 0 || + static_cast(rx.payload_len) + prefix + link::header_size > link::maximum_message) { + ++raw_oversize_; + static std::int64_t last_report = 0; // one line per 10 s at most, the counter has the rest + const auto now = esp_timer_get_time(); + if (now - last_report > 10000000) { + last_report = now; + ESP_LOGW(TAG, "V2X_RX: port %u message of %d bytes does not fit a link message (%lu dropped so far)", + unsigned(rx.btp_dest_port), rx.payload_len, (unsigned long)raw_oversize_); + } + return; + } + link::Writer w; + w.u16(rx.btp_dest_port); + w.u8(static_cast(static_cast(std::clamp(rssi, -128, 127)))); + w.u8((rx.has_geo_area ? 0x01 : 0) | (rx.signed_unverified ? 0x02 : 0)); + w.i32(rx.geo_area_lat_tenmicrodeg); + w.i32(rx.geo_area_lon_tenmicrodeg); + w.u16(rx.geo_area_distance_a_m); + w.bytes(rx.payload, static_cast(rx.payload_len)); + ++raw_forwarded_; + raw_its_(w.out); +} + +link::Code Station::unsecured_request(const link::BtpDataRequest& q) { + // geonet.c builds exactly one packet shape: BTP-B inside a GN single-hop broadcast. That is + // what CAM and VAM are; anything else still needs the stack, which here only signs. + if (q.btp_type != 1 || q.gn_packet_transport_type != 1) { + ++counters_.requests_refused; + return link::Code::unsupported; + } + gn_lpv_t lpv {}; + std::copy(std::begin(config_->mid), std::end(config_->mid), lpv.mac); + lpv.station_type = config_->station_type; + lpv.pai = last_poti_.pai(); + lpv.tst_ms = static_cast(last_poti_.timestamp_ms); // TimestampIts mod 2^32 + lpv.lat_tenmicrodeg = last_poti_.latitude; + lpv.lon_tenmicrodeg = last_poti_.longitude; + lpv.speed_cms = static_cast(last_poti_.has_speed() ? std::min(last_poti_.speed_cm_s, 16383) : 0); + lpv.heading_decideg = last_poti_.has_heading() ? last_poti_.heading_deci_degree : 0; + ByteBuffer pdu(q.fl_sdu.size() + 64); + const int length = geonet_wrap_shb(q.fl_sdu.data(), static_cast(q.fl_sdu.size()), &lpv, + q.destination_port, pdu.data(), pdu.size()); + if (length <= 0) { + ++counters_.requests_refused; + return link::Code::invalid_argument; + } + pdu.resize(static_cast(length)); + AlDataRequest frame = radio_parameters_; + frame.source = vanetza::MacAddress {lpv.mac[0], lpv.mac[1], lpv.mac[2], lpv.mac[3], lpv.mac[4], lpv.mac[5]}; + frame.destination = vanetza::cBroadcastMacAddress; + frame.data = std::move(pdu); + const auto result = request(std::move(frame)); + if (result == Result::accepted) ++counters_.requests_accepted; else ++counters_.requests_refused; + return code(result); +} + +void Station::deliver(BtpIndication received) { + auto indication = NF_SAP::BTP_DATA_indication_from(std::move(received)); + ++counters_.indications; +#if CONFIG_MICROBU_TEST_CHANNEL + // The test channel sees every BTP indication as well (kind 2, the HIL SUT layout). + if (mirror_ != Mirror::off) { + link::Writer w; + w.u8(indication.btp_type == BtpType::b ? 1 : 0); + const auto port = indication.destination_port; + const auto info = indication.source_port.value_or(indication.destination_port_info.value_or(0)); + w.out.push_back(port >> 8); w.out.push_back(port & 0xFF); // big-endian like hil_sut.cpp + w.out.push_back(info >> 8); w.out.push_back(info & 0xFF); + w.bytes(indication.received_fl_sdu); + record(2, w.out); + } +#endif + if (!indication_) return; + link::BtpDataIndication out; + out.btp_type = indication.btp_type == BtpType::b ? 1 : 0; + out.destination_port = indication.destination_port; + out.destination_port_info = indication.source_port.value_or(indication.destination_port_info.value_or(0)); + out.gn_packet_transport_type = transport_number(indication.gn.transport_type); + out.gn_traffic_class = indication.gn.traffic_class.raw(); + out.gn_remaining_packet_lifetime = indication.gn.remaining_packet_lifetime ? indication.gn.remaining_packet_lifetime->raw() : 0xFF; + out.gn_remaining_hop_limit = indication.gn.remaining_hop_limit ? static_cast(std::min(*indication.gn.remaining_hop_limit, 254u)) : 0xFF; + ByteBuffer address; + gn::serialize_into_buffer(indication.gn.source_position.gn_addr, address); + std::copy_n(address.begin(), std::min(address.size(), 8), out.source_gn_address); + out.source_timestamp = indication.gn.source_position.timestamp.raw(); + out.source_latitude = indication.gn.source_position.latitude.value(); + out.source_longitude = indication.gn.source_position.longitude.value(); + out.security_report = boost::apply_visitor(ReportToLink {}, indication.gn.security_report); + out.its_aid = indication.gn.its_aid.value_or(0); + if (indication.gn.permissions) out.permissions = *indication.gn.permissions; + if (indication.gn.certificate_id) { out.certificate_present = true; std::copy(indication.gn.certificate_id->begin(), indication.gn.certificate_id->end(), out.certificate_id); } + if (const auto* area = boost::get(&indication.gn.destination)) out.area = boost::apply_visitor(AreaToLink {*area}, area->shape); + out.received_fl_sdu = std::move(indication.received_fl_sdu); + indication_(out); +} + +link::Code Station::provision(const link::Bytes& bundle, link::ApplyReport& report) { + try { + security::Credentials credentials; + if (!security::decode(bundle, credentials) || credentials.roots.empty() || credentials.tickets.empty()) { + return link::Code::invalid_argument; + } + security::NvsCredentialStore store; + const auto saved = store.save(credentials); + if (saved != Result::accepted) return code(saved); + if (config_ && config_->security) { + const auto result = build(); + if (result != link::Code::accepted) return result; + if (security_) { + report.roots = security_->report.roots; + report.authorities = security_->report.authorities; + report.tickets = security_->report.tickets; + ESP_LOGI(TAG, "credentials provisioned: %u roots, %u authorities, %u tickets", + unsigned(report.roots), unsigned(report.authorities), unsigned(report.tickets)); + } + return link::Code::accepted; + } + report.roots = credentials.roots.size(); + report.authorities = credentials.authorities.size(); + report.tickets = credentials.tickets.size(); + return link::Code::accepted; + } catch (const std::bad_alloc&) { + ESP_LOGE(TAG, "out of memory provisioning credentials"); + return link::Code::resource_limit; + } catch (const std::exception& e) { + ESP_LOGE(TAG, "failed provisioning credentials: %s", e.what()); + return link::Code::invalid_argument; + } +} + +link::Code Station::erase_credentials() { + security::NvsCredentialStore store; + const auto erased = store.erase(); + if (erased != Result::accepted && erased != Result::rejected) return code(erased); + if (config_ && config_->security) return build(); + return link::Code::accepted; +} + +link::Code Station::subscribe(const link::Bytes& subscriber_data, std::uint64_t& subscription) { + if (!security_ || !security_->entity) return link::Code::security_unavailable; + static std::uint64_t next_handle = 1; + const auto handle = next_handle++; + const auto service = security_->entity->id_change().subscribe( + [this, handle](security::IdChangeCommand command, const security::Identifier& id, const ByteBuffer& data, + std::shared_ptr responder) { + link::IdChangeEvent event; + event.subscription = handle; + event.command = static_cast(command); + std::copy(id.begin(), id.end(), event.id); + event.subscriber_data = data; + if (responder) pending_responders_[handle] = responder; + if (id_event_ && !rebuilding_) id_event_(event); + }, subscriber_data); + link_subscriptions_[handle] = service; + subscription = handle; + return link::Code::accepted; +} + +link::Code Station::unsubscribe(std::uint64_t subscription) { + const auto it = link_subscriptions_.find(subscription); + if (it == link_subscriptions_.end()) return link::Code::invalid_argument; + Result result = Result::accepted; + if (security_ && security_->entity) result = security_->entity->id_change().unsubscribe(it->second); + link_subscriptions_.erase(it); + pending_responders_.erase(subscription); + return code(result); +} + +link::Code Station::event_response(std::uint64_t subscription, bool return_code) { + const auto it = pending_responders_.find(subscription); + if (it == pending_responders_.end()) return link::Code::invalid_argument; + it->second->respond(return_code); + pending_responders_.erase(it); + return link::Code::accepted; +} + +link::Code Station::trigger() { + if (!security_ || !security_->entity) return link::Code::security_unavailable; + return code(security_->entity->id_change().trigger()); +} + +link::Code Station::lock(std::uint8_t seconds, std::uint64_t& handle) { + if (!security_ || !security_->entity) return link::Code::security_unavailable; + handle = security_->entity->id_change().lock(seconds); + return link::Code::accepted; +} + +link::Code Station::unlock(std::uint64_t handle) { + if (!security_ || !security_->entity) return link::Code::security_unavailable; + return code(security_->entity->id_change().unlock(handle)); +} + +void Station::tick() { + // MicrOBU: the stack needs the ITS clock, which only the phone's first PoTi sets. The radio + // does not: its queue (16 frames) must be drained and raw frames forwarded from the moment the + // station is configured. The colleague's version returned early here, so a phone that had + // configured the station but not yet sent a PoTi (no trip recording, no pinger) received + // nothing and every frame on air overflowed the queue into radio_dropped. + const bool running = stack_ && clock_.synchronised; + if (running) { + const auto now = vanetza::Clock::time_point(std::chrono::microseconds(clock_.now_us())); + if (now > runtime_->now()) stack_->advance(now); + } + if (radio_) { + radio_->poll([this, running](AlDataIndication indication) { + ++counters_.radio_received; + if (received_) received_(); + if (running && stack_) stack_->indicate(std::move(indication)); + }, [this](const ByteBuffer& frame, int rssi, std::uint32_t) { forward_raw(frame, rssi); }); + counters_.radio_dropped = radio_->dropped_frames(); + } + if (running) sample_dcc_channel_load(); +} + +// Sample the hardware LCBR counters at the T_Cbr cadence (TS 102 687 clause 5.4 / +// TS 103 836-4-2 clause 5.2, both 100 ms) and feed both DCC entities. The first sample after +// (re)build only establishes the baseline counter snapshot -- a CBR delta needs two samples. +void Station::sample_dcc_channel_load() { + if (!radio_ || !access_stack_) return; + const auto its_us = clock_.now_us(); + if (its_us - dcc_last_sample_its_us_ < 100000) return; + const auto counters = radio_->read_cca_counters(); + if (dcc_cca_last_) { + const vanetza::dcc::ChannelLoad local_cbr(C5Radio::calculate_cbr(counters, *dcc_cca_last_)); + stack_->report_local_channel_load(local_cbr); + const auto global_cbr = stack_->global_channel_busy_ratio(); + // Consume Release-2 CBR_G for DCC_ACC when available, else LCBR. + access_stack_->report_channel_load(global_cbr ? *global_cbr : local_cbr); + } + dcc_cca_last_ = counters; + dcc_last_sample_its_us_ = its_us; +} + +link::Status Station::status() { + link::Status s = counters_; + s.uptime_ms = static_cast(esp_timer_get_time() / 1000); + s.configured = stack_ ? 1 : 0; + if (stack_) { + ByteBuffer address; + gn::serialize_into_buffer(stack_->address(), address); + std::copy_n(address.begin(), std::min(address.size(), 8), s.gn_address); + s.change_pending = stack_->identity_change_pending() ? 1 : 0; + } + if (security_ && security_->entity) { + const auto id = security_->entity->id_change().current_identifier(); + std::copy(id.begin(), id.end(), s.identifier); + s.tickets = static_cast(std::min(security_->pool.size(), 255)); + const auto& st = security_->entity->statistics(); + s.signed_messages = st.signed_messages; s.refused_no_ticket = st.refused_no_ticket; + s.refused_change_pending = st.refused_change_pending; s.refused_permission = st.refused_permission; + s.sign_failed = st.failed; s.verified = st.verified; + s.rejected = st.rejected_profile + st.rejected_signer + st.rejected_certificate + st.rejected_signature + st.rejected_time + st.replayed; + } + s.its_time_ms = clock_.synchronised ? static_cast(clock_.now_us() / 1000) : 0; + return s; +} + +// ---- access adapter: radio, mirrored or diverted to the software lower tester ---- +Result Station::request(AlDataRequest request) { +#if CONFIG_MICROBU_TEST_CHANNEL + if (mirror_ != Mirror::off) record(1, request.data); + if (mirror_ == Mirror::divert) { ++counters_.radio_submitted; return overflow_ ? Result::resource_limit : Result::accepted; } +#endif + if (radio_) { + // Adaptive DCC_ACC gates here when access_stack_ has it enabled (build()); it falls + // through to radio_->request() unchanged otherwise. + const auto result = access_stack_ ? access_stack_->request(std::move(request)) : radio_->request(std::move(request)); + if (result == Result::accepted) { + ++counters_.radio_submitted; + if (disseminated_) disseminated_(); + } + else { + ++counters_.radio_failed; + static std::int64_t last_report = 0; // one line per second, the counter has the rest + const auto now = esp_timer_get_time(); + if (now - last_report > 1000000) { last_report = now; ESP_LOGW(TAG, "radio refused a frame: result %d", int(result)); } + } + return result; + } + ++counters_.radio_failed; + return Result::unsupported; // no radio configured and nothing diverting: the request has nowhere to go +} + +} // namespace microbu diff --git a/obu-firmware/main/station.hpp b/obu-firmware/main/station.hpp new file mode 100644 index 0000000..f61c99b --- /dev/null +++ b/obu-firmware/main/station.hpp @@ -0,0 +1,266 @@ +#pragma once +// The ESP32-C5 half of the VRU ITS-S: BTP-B, GeoNetworking, the SN-SAP +// security entity with the station's provisioned credentials, and the ITS-G5 access adapter. +// Everything here runs on one task (the station task). +#include "c5_radio.hpp" +#include "link_protocol.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace microbu { + +#if CONFIG_MICROBU_TEST_CHANNEL +/// Records the software lower tester collects (test channel, test firmware only). +struct TestRecord { std::uint8_t kind; link::Bytes bytes; }; +#endif + +/// @note All public members except the on_*() callback setters must be called from the station task. +class Station final : public vanetza_idf::Access, public vanetza::PositionProvider { +public: + using Indication = std::function; + using IdEvent = std::function; + using Disseminated = std::function; + using Received = std::function; + /// MicrOBU: body of one link V2X_RX message (see link_protocol.hpp). + using RawIts = std::function; + + /// @brief Constructs an unconfigured station (call configure() before use). + Station(); + /// @brief Tears down the stack, security entity and radio in dependency order. + ~Station() override; + + // ---- link primitives (station task) ---- + /// @brief (Re)builds the stack and security entity for a new station configuration. + /// @param config Requested configuration. + /// @param detail Receives the assigned GN address, identifier, and ticket count. + /// @return Result code. + link::Code configure(const link::StationConfigure& config, link::Bytes& detail); + + /// @brief Applies a position/time fix from the phone and advances the ITS clock. + /// @param fix Position/time fix to apply. + /// @return Result code. + link::Code poti(const link::PotiUpdate& fix); + + /// @brief Submits a BTP data request to the stack. + /// @param request Request to submit. + /// @return Result code. + link::Code btp_request(const link::BtpDataRequest& request); + + /// @brief Decodes and stores a credential bundle, rebuilding the security entity if configured. + /// @param bundle Raw credential bundle bytes. + /// @param report Receives the applied root/authority/ticket counts. + /// @return Result code. + link::Code provision(const link::Bytes& bundle, link::ApplyReport& report); + + /// @brief Erases stored credentials and rebuilds if security is configured. + /// @return Result code. + link::Code erase_credentials(); + + /// @brief Subscribes to pseudonym-change events. + /// @param subscriber_data Opaque data echoed back with events for this subscription. + /// @param subscription Receives the assigned subscription handle. + /// @return Result code. + link::Code subscribe(const link::Bytes& subscriber_data, std::uint64_t& subscription); + + /// @brief Cancels a pseudonym-change subscription. + /// @param subscription Handle returned by subscribe(). + /// @return Result code. + link::Code unsubscribe(std::uint64_t subscription); + + /// @brief Resolves a pending PREPARE/COMMIT identity-change event. + /// @param subscription Handle the event was delivered for. + /// @param return_code Caller's response (accept/reject) to the pending event. + /// @return Result code. + link::Code event_response(std::uint64_t subscription, bool return_code); + + /// @brief Triggers an immediate pseudonym change. + /// @return Result code. + link::Code trigger(); + + /// @brief Locks the current pseudonym for the given duration. + /// @param seconds Lock duration in seconds. + /// @param handle Receives the assigned lock handle. + /// @return Result code. + link::Code lock(std::uint8_t seconds, std::uint64_t& handle); + + /// @brief Releases a pseudonym lock. + /// @param handle Handle returned by lock(). + /// @return Result code. + link::Code unlock(std::uint64_t handle); + + /// @return Current station status snapshot. + link::Status status(); + + /// @brief Advances the ITS clock, runs timers, polls the radio; call every few milliseconds. + void tick(); + + /// @brief Registers the callback invoked when a BTP data indication arrives. + /// @param f Callback to invoke; replaces any previously registered callback. + void on_indication(Indication f) { indication_ = std::move(f); } + + /// @brief Registers the callback invoked when a pseudonym-change event fires. + /// @param f Callback to invoke; replaces any previously registered callback. + void on_id_event(IdEvent f) { id_event_ = std::move(f); } + + /// @brief Registers the callback invoked after a frame is disseminated. + /// @param f Callback to invoke; replaces any previously registered callback. + void on_disseminated(Disseminated f) { disseminated_ = std::move(f); } + + /// @brief Registers the callback invoked after a frame is received. + /// @param f Callback to invoke; replaces any previously registered callback. + void on_received(Received f) { received_ = std::move(f); } + + /// @brief MicrOBU: registers the callback for every ITS message heard on air, unwrapped by + /// gn_unwrap.c before (and independently of) the stack's security checks. The stack drops + /// what it cannot verify (itsGnSnDecapResultHandling is STRICT in vanetza-idf): unsigned + /// traffic, and anything signed under a root other than the provisioned demo root, i.e. every + /// RSU. This path is how those still reach the phone, exactly as with the previous firmware. + /// @param f Callback to invoke with a V2X_RX body; replaces any previously registered callback. + void on_raw_its(RawIts f) { raw_its_ = std::move(f); } + +#if CONFIG_MICROBU_TEST_CHANNEL + // ---- test channel (software lower tester; not part of the phone interface -- see test_channel.hpp) ---- + enum class Mirror : std::uint8_t { off = 0, mirror = 1, divert = 2 }; + /// @brief Sets whether requests/indications are mirrored to, or diverted through, the test channel. + /// @param mode Off, mirror (copy) or divert (intercept) mode. + void test_mirror(Mirror mode); + + /// @brief Injects a raw GN PDU as if received over the air. + /// @param source Source MAC address to report for the injected frame. + /// @param destination Destination MAC address to report for the injected frame. + /// @param gnpdu Raw GeoNetworking PDU bytes. + /// @return Result code. + link::Code test_inject(const vanetza::MacAddress& source, const vanetza::MacAddress& destination, link::Bytes gnpdu); + /// @brief Submits a raw GeoNetworking request bypassing BTP. + /// @param traffic_class GeoNetworking traffic class to submit with. + /// @param payload Raw payload bytes. + /// @return Result code. + link::Code test_gn_request(std::uint8_t traffic_class, link::Bytes payload); + /// @brief Pops queued mirror/divert records fitting into budget octets (3 per record header); the rest stays queued. + /// @param overflow Receives true if records were dropped because the queue budget was exceeded. + /// @param budget Maximum number of octets of records to drain. + /// @return Drained records. + std::deque test_drain(bool& overflow, std::size_t budget); + /// @brief Clears mirror mode and any queued records. + void test_reset(); + /// @brief Starts the radio if needed and sends a burst of raw test frames. + /// @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 Result code. + link::Code test_radio_burst(std::uint16_t channel, double power_dbm, unsigned mcs, + unsigned count, unsigned interval_ms, std::size_t payload_len); + /// @brief Starts the radio if needed and samples the CCA state for duration_ms. + /// @param duration_ms Sampling window duration in milliseconds. + /// @param detail Receives the sampling statistics. + /// @return Result code. + link::Code test_cca_sample(unsigned duration_ms, link::Bytes& detail); +#endif + + // vanetza_idf::Access + /// @brief Submits a frame to the radio (through DCC_ACC when enabled), mirroring/diverting for the test channel. + /// @param request Frame and transmit parameters from the access layer. + /// @return Result code. + vanetza_idf::Result request(vanetza_idf::AlDataRequest request) override; + + // vanetza::PositionProvider + /// @return The most recently applied position fix. + const vanetza::PositionFix& position_fix() override { return fix_; } + +private: + struct Security; + struct Clock { + bool synchronised = false; + std::int64_t base_its_us = 0; // ITS time at base_esp_us + std::int64_t base_esp_us = 0; + std::int64_t now_us() const; + }; + + /// @brief (Re)builds the stack and security entity from config_, stored credentials and the clock. + /// @return Result code. + link::Code build(); + + /// @brief Destroys the stack, security entity and runtime in dependency order. + void teardown(); + + /// @brief Maps a station configuration onto the GeoNetworking MIB. + /// @param mib MIB to populate. + /// @param config Station configuration to map from. + void apply_mib(vanetza_idf::StackConfig& mib, const link::StationConfigure& config) const; + + /// @brief Re-subscribes existing pseudonym-change handles to a freshly built security entity. + void resubscribe_id_change(); + + /// @brief Pushes the current position fix into the stack, clamped to not precede the station clock. + void apply_position(); + + /// @brief Samples the hardware CCA counters at the DCC cadence and feeds both DCC entities. + void sample_dcc_channel_load(); + +#if CONFIG_MICROBU_TEST_CHANNEL + /// @brief Queues bytes for the test channel, subject to the mirror-buffer budget. + /// @param kind Record kind tag. + /// @param bytes Record payload bytes. + void record(std::uint8_t kind, link::Bytes bytes); +#endif + + /// @brief MicrOBU: unwraps one raw received frame with gn_unwrap.c and hands it to raw_its_. + /// @param frame Complete 802.11 frame as captured (FCS included; gn_unwrap reads declared lengths). + /// @param rssi Received signal strength, dBm. + void forward_raw(const vanetza::ByteBuffer& frame, int rssi); + + /// @brief MicrOBU: transmits an unsecured BTP-B/SHB request the way the previous firmware did, + /// with geonet.c's GN header and the Source Position Vector of the last PoTi, through request(). + /// @param request The phone's request (already checked: configured, clock and fix present). + /// @return Result code. + link::Code unsecured_request(const link::BtpDataRequest& request); + + /// @brief Translates and forwards a stack BTP indication to the link service and test channel. + /// @param indication Indication received from the stack. + void deliver(vanetza_idf::BtpIndication indication); + + link::Status counters_; + + std::optional config_; + Clock clock_; + vanetza::PositionFix fix_; + bool have_fix_ = false; + link::PotiUpdate last_poti_; // MicrOBU: the GN Source Position Vector of unsecured_request() + std::uint32_t raw_forwarded_ = 0, raw_oversize_ = 0; + std::unique_ptr runtime_; + std::unique_ptr security_; + std::unique_ptr stack_; + std::unique_ptr radio_; + std::unique_ptr access_stack_; // DCC_ACC gate in front of radio_ + vanetza_idf::AlDataRequest radio_parameters_; + std::optional dcc_cca_last_; // previous read_cca_counters() sample, for the 100 ms LCBR delta + std::int64_t dcc_last_sample_its_us_ = 0; + Indication indication_; + IdEvent id_event_; + Disseminated disseminated_; + Received received_; + RawIts raw_its_; + std::map> pending_responders_; + std::map link_subscriptions_; // subscription -> service handle (identity) + bool rebuilding_ = false; +#if CONFIG_MICROBU_TEST_CHANNEL + Mirror mirror_ = Mirror::off; + std::deque records_; + std::size_t record_bytes_ = 0; + bool overflow_ = false; +#endif +}; + +} // namespace microbu diff --git a/obu-firmware/main/station_test.cpp b/obu-firmware/main/station_test.cpp new file mode 100644 index 0000000..818562a --- /dev/null +++ b/obu-firmware/main/station_test.cpp @@ -0,0 +1,130 @@ +// Station's test-channel API (software lower tester; not part of the phone interface, see +// test_channel.hpp). Kept in its own translation unit so the productive station.cpp stays free +// of test-only code; compiles to nothing when CONFIG_MICROBU_TEST_CHANNEL is off. +#include "station.hpp" +#if CONFIG_MICROBU_TEST_CHANNEL +#include +#include +#include +#include "c5_radio.hpp" + +namespace microbu { +using namespace vanetza_idf; +namespace gn = vanetza::geonet; +namespace { +const char* TAG = "station"; +link::Code code(Result result) { return static_cast(result); } +} + +void Station::test_mirror(Mirror mode) { mirror_ = mode; } + +link::Code Station::test_inject(const vanetza::MacAddress& source, const vanetza::MacAddress& destination, link::Bytes gnpdu) { + if (!stack_) return link::Code::not_configured; + tick(); + AlDataIndication indication; + indication.source = source; + indication.destination = destination; + indication.channel_number = radio_parameters_.channel_number; + indication.data = std::move(gnpdu); + return code(stack_->indicate(std::move(indication))); +} + +link::Code Station::test_gn_request(std::uint8_t traffic_class, link::Bytes payload) { + if (!stack_) return link::Code::not_configured; + if (!clock_.synchronised || !have_fix_) return link::Code::rejected; + tick(); + GnRequest request; + request.traffic_class = gn::TrafficClass(traffic_class); + request.data = std::move(payload); + return code(stack_->request(std::move(request))); +} + +std::deque Station::test_drain(bool& overflow, std::size_t budget) { + std::deque out; + std::size_t used = 0; + while (!records_.empty() && used + records_.front().bytes.size() + 3 <= budget) { + used += records_.front().bytes.size() + 3; + record_bytes_ -= records_.front().bytes.size(); + out.push_back(std::move(records_.front())); + records_.pop_front(); + } + overflow = overflow_ && records_.empty(); + if (records_.empty()) overflow_ = false; + return out; +} + +void Station::test_reset() { + mirror_ = Mirror::off; + records_.clear(); + record_bytes_ = 0; + overflow_ = false; +} + +link::Code Station::test_radio_burst(std::uint16_t channel, double power_dbm, unsigned mcs, + unsigned count, unsigned interval_ms, std::size_t payload_len) { + if (!radio_) { + // Automatically start the radio for testing if not yet started + C5RadioConfig rc; + rc.channel_number = channel; + rc.transmit_power_dbm = power_dbm; + rc.laboratory_transmission = true; + radio_ = std::make_unique(rc); + const auto error = radio_->start(); + if (error != ESP_OK) { + ESP_LOGE(TAG, "radio start failed for test burst: %s", esp_err_to_name(error)); + radio_.reset(); + return link::Code::rejected; + } + } + const auto err = radio_->transmit_burst(channel, power_dbm, mcs, count, interval_ms, payload_len); + if (err == ESP_OK) { + counters_.radio_submitted += count; + if (disseminated_) disseminated_(); + return link::Code::accepted; + } + if (err == ESP_ERR_INVALID_ARG) return link::Code::invalid_argument; + if (err == ESP_ERR_NO_MEM) return link::Code::resource_limit; + return link::Code::rejected; +} + +link::Code Station::test_cca_sample(unsigned duration_ms, link::Bytes& detail) { + // Capped: this busy-loops with interrupts otherwise free, so keep it short enough that + // the task watchdog and the WiFi/BLE stack's own housekeeping are not starved for long. + duration_ms = std::min(duration_ms, 500u); + if (!radio_) { + C5RadioConfig rc; // default channel 180 / 10 dBm is fine: this probes PHY polling + rc.laboratory_transmission = true; // capability, not a specific channel's RF content + radio_ = std::make_unique(rc); + const auto error = radio_->start(); + if (error != ESP_OK) { + ESP_LOGE(TAG, "radio start failed for CCA sample: %s", esp_err_to_name(error)); + radio_.reset(); + return link::Code::rejected; + } + } + const auto result = radio_->sample_cca(duration_ms); + link::Writer w; + w.u32(result.samples); + w.u32(result.duration_us); + w.u32(result.min_delta_us); + w.u32(result.max_delta_us); + w.u32(result.busy_count); + w.i32(result.first_cca); + w.i32(result.last_cca); + w.i32(result.noise_floor_dbm); + w.i32(result.cca_total_cycles_delta); + w.i32(result.cca_busy_cycles_delta); + w.i32(result.cca_status); + detail = w.out; + return result.samples > 0 ? link::Code::accepted : link::Code::rejected; +} + +void Station::record(std::uint8_t kind, link::Bytes bytes) { + if (mirror_ == Mirror::off) return; + if (record_bytes_ + bytes.size() > 8192 || records_.size() >= 32) { overflow_ = true; return; } + record_bytes_ += bytes.size(); + records_.push_back(TestRecord {kind, std::move(bytes)}); +} + +} // namespace microbu +#endif // CONFIG_MICROBU_TEST_CHANNEL diff --git a/obu-firmware/main/test_channel.cpp b/obu-firmware/main/test_channel.cpp new file mode 100644 index 0000000..032c168 --- /dev/null +++ b/obu-firmware/main/test_channel.cpp @@ -0,0 +1,79 @@ +#include "test_channel.hpp" +#if CONFIG_MICROBU_TEST_CHANNEL +#include + +namespace microbu { + +link::Bytes test_channel_execute(Station& station, const link::Bytes& request) { + using link::Code; + Code result = Code::malformed; + std::deque records; + link::Bytes extra; + if (!request.empty()) { + link::Reader r(request, 1); + switch (request[0]) { + case 0x01: { // MIRROR mode + const auto mode = r.u8(); + if (r.done() && mode <= 2) { station.test_mirror(static_cast(mode)); result = Code::accepted; } + break; + } + case 0x02: { // INJECT source destination gnpdu + vanetza::MacAddress source, destination; + r.bytes(source.octets.data(), 6); + r.bytes(destination.octets.data(), 6); + auto gnpdu = r.rest(); + if (r.ok() && !gnpdu.empty()) result = station.test_inject(source, destination, std::move(gnpdu)); + break; + } + case 0x03: { // GN_REQUEST traffic class payload + const auto tc = r.u8(); + auto payload = r.rest(); + if (r.ok() && !payload.empty()) result = station.test_gn_request(tc, std::move(payload)); + break; + } + case 0x04: { // DRAIN + bool overflow = false; + records = station.test_drain(overflow, 1536 - 2); + result = overflow ? Code::resource_limit : Code::accepted; + break; + } + case 0x05: // RESET + station.test_reset(); + result = Code::accepted; + break; + case 0x06: { // RADIO_BURST [channel u16 LE][power_quarter_db u8][mcs u8][count u16 LE][interval_ms u16 LE][payload_len u16 LE] + const auto channel = r.u16(); + const auto power_quarter_db = r.u8(); + const auto mcs = r.u8(); + const auto count = r.u16(); + const auto interval_ms = r.u16(); + const auto payload_len = r.u16(); + if (r.done()) { + const double power_dbm = power_quarter_db / 4.0; + result = station.test_radio_burst(channel, power_dbm, mcs, count, interval_ms, payload_len); + } + break; + } + case 0x07: { // CCA_SAMPLE [duration_ms u16 LE] -- CCA polling feasibility probe + const auto duration_ms = r.u16(); + if (r.done()) result = station.test_cca_sample(duration_ms, extra); + break; + } + default: + result = Code::unknown_opcode; + } + } + link::Writer w; + w.u8(static_cast(result)); + w.u8(static_cast(std::min(records.size(), 255))); + for (const auto& record : records) { + w.u8(record.kind); + w.u16(static_cast(record.bytes.size())); + w.bytes(record.bytes); + } + w.bytes(extra); + return w.out; +} + +} // namespace microbu +#endif // CONFIG_MICROBU_TEST_CHANNEL diff --git a/obu-firmware/main/test_channel.hpp b/obu-firmware/main/test_channel.hpp new file mode 100644 index 0000000..eaed531 --- /dev/null +++ b/obu-firmware/main/test_channel.hpp @@ -0,0 +1,15 @@ +#pragma once +// Test channel (serial frame type 0x11, test firmware only): software lower tester and the +// diagnostic hooks the ETSI campaigns need. Not part of the phone interface, never on BLE. +#include "link_protocol.hpp" +#include "station.hpp" + +namespace microbu { + +/// @brief Executes one software-lower-tester request against the station. +/// @param station Station to execute the request against. +/// @param request Raw test-channel request bytes. +/// @return Reply bytes: [result][record count]{[kind][length u16 LE][bytes]}. +link::Bytes test_channel_execute(Station& station, const link::Bytes& request); + +} // namespace microbu diff --git a/obu-firmware/partitions.csv b/obu-firmware/partitions.csv new file mode 100644 index 0000000..ef89b94 --- /dev/null +++ b/obu-firmware/partitions.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +# MicrOBU: NVS 80 KB instead of the colleague's 24 KB (their board has 4 MB of flash, this one 16 MB). +# NVS holds the BLE bond, the credential bundle and PHY calibration; at 24 KB, with Wi-Fi settings the +# previous firmware had left in it, there was no room for the bond, so the board forgot the phone +# after every connection. The app therefore moves from 0x10000 to 0x20000 (64 KB aligned). +nvs, data, nvs, 0x9000, 0x14000, +phy_init, data, phy, 0x1D000, 0x1000, +factory, app, factory, 0x20000, 0x300000, diff --git a/obu-firmware/sdkconfig b/obu-firmware/sdkconfig index d0ac8f0..61fcfc6 100644 --- a/obu-firmware/sdkconfig +++ b/obu-firmware/sdkconfig @@ -1,6 +1,6 @@ # # Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 6.1.0 Project Configuration +# Espressif IoT Development Framework (ESP-IDF) 6.0.2 Project Configuration # # default: CONFIG_SOC_ADC_SUPPORTED=y @@ -103,7 +103,7 @@ CONFIG_SOC_PMU_PVT_SUPPORTED=y # default: CONFIG_SOC_PAU_SUPPORTED=y # default: -CONFIG_SOC_RTC_TIMER_SUPPORTED=y +CONFIG_SOC_RTC_TIMER_V2_SUPPORTED=y # default: CONFIG_SOC_LP_AON_SUPPORTED=y # default: @@ -149,8 +149,6 @@ CONFIG_SOC_PHY_SUPPORTED=y # default: CONFIG_SOC_BITSCRAMBLER_SUPPORTED=y # default: -CONFIG_SOC_SPI_EXTERNAL_NOR_FLASH_SUPPORTED=y -# default: CONFIG_SOC_XTAL_SUPPORT_40M=y # default: CONFIG_SOC_XTAL_SUPPORT_48M=y @@ -483,12 +481,32 @@ CONFIG_SOC_ECDSA_SUPPORT_CURVE_SPECIFIC_KEY_PURPOSES=y # default: CONFIG_SOC_SPI_PERIPH_NUM=2 # default: +CONFIG_SOC_SPI_MAX_CS_NUM=6 +# default: CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 # default: +CONFIG_SOC_SPI_SUPPORT_DDRCLK=y +# default: +CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y +# default: +CONFIG_SOC_SPI_SUPPORT_CD_SIG=y +# default: +CONFIG_SOC_SPI_SUPPORT_CONTINUOUS_TRANS=y +# default: CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y # default: CONFIG_SOC_SPI_SUPPORT_SLEEP_RETENTION=y # default: +CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y +# default: +CONFIG_SOC_SPI_SUPPORT_CLK_PLL_F160M=y +# default: +CONFIG_SOC_SPI_SUPPORT_CLK_RC_FAST=y +# default: +CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y +# default: +CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 +# default: CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y # default: CONFIG_SOC_PSRAM_DMA_CAPABLE=y @@ -513,22 +531,34 @@ CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y # default: CONFIG_SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR=y # default: -CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY=y -# default: -CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y -# default: CONFIG_SOC_SPI_MEM_FLASH_SUPPORT_HPM=y # default: +CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY=y +# default: CONFIG_SOC_MEMSPI_ENCRYPTION_ALIGNMENT=16 # default: +CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 +# default: +CONFIG_SOC_SYSTIMER_ALARM_NUM=3 +# default: +CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 +# default: +CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 +# default: +CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y +# default: +CONFIG_SOC_SYSTIMER_SUPPORT_RC_FAST=y +# default: +CONFIG_SOC_SYSTIMER_INT_LEVEL=y +# default: +CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y +# default: CONFIG_SOC_SYSTIMER_SUPPORT_ETM=y # default: CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 # default: CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 # default: -CONFIG_SOC_RTC_TIMER_V2=y -# default: CONFIG_SOC_TIMER_SUPPORT_ETM=y # default: CONFIG_SOC_TIMER_SUPPORT_SLEEP_RETENTION=y @@ -599,8 +629,14 @@ CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y # default: CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_SUPPORT_PSEUDO_ROUND=y # default: +CONFIG_SOC_FLASH_ENCRYPTION_PAGE_CONFIGURABLE=y +# default: CONFIG_SOC_PSRAM_ENCRYPTION_XTS_AES_128=y # default: +CONFIG_SOC_PSRAM_ENCRYPTION_SEPARATE_KEY=y +# default: +CONFIG_SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE=y +# default: CONFIG_SOC_RECOVERY_BOOTLOADER_SUPPORTED=y # default: CONFIG_SOC_BOOTLOADER_ANTI_ROLLBACK_SUPPORTED=y @@ -699,8 +735,6 @@ CONFIG_SOC_PM_SUPPORT_PMU_MODEM_STATE=y # default: CONFIG_SOC_PM_SUPPORT_PMU_CLK_ICG=y # default: -CONFIG_SOC_PM_SUPPORT_MODEM_CLOCK_DOMAIN_ICG=y -# default: CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y # default: CONFIG_SOC_PM_CPU_RETENTION_BY_SW=y @@ -717,6 +751,8 @@ CONFIG_SOC_PM_PAU_REGDMA_LINK_IDX_WIFIMAC=4 # default: CONFIG_SOC_PM_PAU_REGDMA_COMMON_PHY_LINK_ENTRY=y # default: +CONFIG_SOC_PM_PAU_REGDMA_MODEM_WIFIMAC_WORKAROUND=y +# default: CONFIG_SOC_PM_PMU_MIN_SLP_SLOW_CLK_CYCLE_FIXED=y # default: CONFIG_SOC_PM_RETENTION_MODULE_NUM=32 @@ -737,6 +773,8 @@ CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL_D2=y # default: CONFIG_SOC_RCC_IS_INDEPENDENT=y # default: +CONFIG_SOC_CLK_ROOT_CLK_SWITCH_PROTECT=y +# default: CONFIG_SOC_CLK_ANA_I2C_MST_DEPENDS_ON_MODEM_APB=y # default: CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_FAST_RC=y @@ -815,7 +853,7 @@ CONFIG_IDF_TARGET_ARCH="riscv" # default: CONFIG_IDF_TARGET="esp32c5" # default: -CONFIG_IDF_INIT_VERSION="6.1.0" +CONFIG_IDF_INIT_VERSION="6.0.2" # default: CONFIG_IDF_TARGET_ESP32C5=y # default: @@ -840,6 +878,11 @@ CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y # CONFIG_APP_NO_BLOBS is not set # end of Build type +# +# App Update config +# +# end of App Update config + # # Bootloader config # @@ -961,6 +1004,8 @@ CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y # default: CONFIG_SECURE_BOOT_V2_ECC_SUPPORTED=y # default: +CONFIG_SECURE_BOOT_V2_ECDSA_INSECURE=y +# default: CONFIG_SECURE_BOOT_V2_PREFERRED=y # default: # CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set @@ -1049,6 +1094,10 @@ CONFIG_ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY=y CONFIG_ESP_ROM_DELAY_US_PATCH=y # default: CONFIG_ESP_ROM_SUPPORT_SECURE_BOOT_FAST_WAKEUP=y +# default: +CONFIG_ESP_ROM_ECDSA_VERIFY_PATCH=y +# default: +CONFIG_ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP=y # # Boot ROM Behavior @@ -1063,15 +1112,6 @@ CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y # CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set # end of Boot ROM Behavior -# -# ESP-TEE (Trusted Execution Environment) -# -# default: -# CONFIG_SECURE_ENABLE_TEE is not set -# default: -CONFIG_SECURE_TEE_LOG_LEVEL=0 -# end of ESP-TEE (Trusted Execution Environment) - # # Serial flasher config # @@ -1090,8 +1130,6 @@ CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y # default: CONFIG_ESPTOOLPY_FLASHMODE="dio" # default: -CONFIG_ESPTOOLPY_FLASHMODE_VAL=3 -# default: # CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set # default: CONFIG_ESPTOOLPY_FLASHFREQ_80M=y @@ -1101,24 +1139,16 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y # CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set # default: CONFIG_ESPTOOLPY_FLASHFREQ="80m" -# default: # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set -# default: -CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y -# default: +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set -# default: # CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -# default: -# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set -# default: +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set -# default: # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set -# default: # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set # default: -CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE="16MB" # default: # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set # default: @@ -1135,30 +1165,19 @@ CONFIG_ESPTOOLPY_AFTER_RESET=y CONFIG_ESPTOOLPY_AFTER="hard-reset" # default: CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 - -# -# Fast Reflashing -# -# end of Fast Reflashing # end of Serial flasher config # # Partition Table # -# default: -CONFIG_PARTITION_TABLE_SINGLE_APP=y -# default: +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set -# default: # CONFIG_PARTITION_TABLE_TWO_OTA is not set -# default: # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -# default: -# CONFIG_PARTITION_TABLE_CUSTOM is not set -# default: +CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" # default: -CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" # default: CONFIG_PARTITION_TABLE_OFFSET=0x8000 # default: @@ -1166,15 +1185,28 @@ CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table # -# Compiler options +# micrOBU firmware # # default: -CONFIG_COMPILER_OPTIMIZATION_DEBUG=y +CONFIG_MICROBU_TEST_CHANNEL=y # default: -# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_MICROBU_LOG_OVER_LINK is not set # default: +CONFIG_MICROBU_BLE_USB_IDLE_MS=3000 +# default: +CONFIG_MICROBU_TX_LED_GPIO=-1 +# default: +CONFIG_MICROBU_TX_LED_BLINK_MS=100 +# default: +# CONFIG_MICROBU_LED_INVERTED_RX is not set +# end of micrOBU firmware + +# +# Compiler options +# +# CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set +CONFIG_COMPILER_OPTIMIZATION_SIZE=y # CONFIG_COMPILER_OPTIMIZATION_PERF is not set -# default: # CONFIG_COMPILER_OPTIMIZATION_NONE is not set # default: # CONFIG_COMPILER_ENABLE_RISCV_ZCMP is not set @@ -1196,10 +1228,10 @@ CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 # CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set # default: CONFIG_COMPILER_HIDE_PATHS_MACROS=y +CONFIG_COMPILER_CXX_EXCEPTIONS=y # default: -# CONFIG_COMPILER_CXX_EXCEPTIONS is not set -# default: -# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 +CONFIG_COMPILER_CXX_RTTI=y # default: CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y # default: @@ -1253,60 +1285,636 @@ CONFIG_COMPILER_CXX_GLIBCXX_CONSTEXPR_NO_CHANGE=y # # Bluetooth # +CONFIG_BT_ENABLED=y +# CONFIG_BT_BLUEDROID_ENABLED is not set +CONFIG_BT_NIMBLE_ENABLED=y +# CONFIG_BT_CONTROLLER_ONLY is not set # default: -# CONFIG_BT_ENABLED is not set +CONFIG_BT_CONTROLLER_ENABLED=y +# default: +# CONFIG_BT_CONTROLLER_DISABLED is not set # # Common Options # +# default: +CONFIG_BT_ALARM_MAX_NUM=50 +# default: +CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y +# default: +# CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS is not set # -# BLE Log +# BT Logs # + +# +# Log Sources +# +# default: +# CONFIG_BT_LE_CONTROLLER_LOG_ENABLED is not set +# end of Log Sources + +# default: +# CONFIG_BT_LOG_CRITICAL_ONLY is not set # default: # CONFIG_BLE_LOG_ENABLED is not set -# end of BLE Log +# +# Legacy SPI Log Output (Deprecated - use BT Log Async Output instead) +# # default: # CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set -# default: -# CONFIG_BT_BLE_LOG_UHCI_OUT_ENABLED is not set +# end of Legacy SPI Log Output (Deprecated - use BT Log Async Output instead) +# end of BT Logs + # default: # CONFIG_BT_LE_USED_MEM_STATISTICS_ENABLED is not set # end of Common Options + +# +# NimBLE Options +# + +# +# General +# +# default: +CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# default: +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +# default: +CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 +# default: +CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096 +# end of General + +# +# Roles and Profiles +# +# CONFIG_BT_NIMBLE_ROLE_CENTRAL is not set +# default: +CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y +# default: +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y +# CONFIG_BT_NIMBLE_ROLE_OBSERVER is not set +# default: +CONFIG_BT_NIMBLE_GATT_SERVER=y +# end of Roles and Profiles + +# +# Security (SMP) +# +# default: +CONFIG_BT_NIMBLE_SECURITY_ENABLE=y +# default: +CONFIG_BT_NIMBLE_SM_LEGACY=y +CONFIG_BT_NIMBLE_SM_SC=y +# default: +# CONFIG_BT_NIMBLE_SM_SC_DEBUG_KEYS is not set +# default: +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_ENCRYPTION=y +# default: +CONFIG_BT_NIMBLE_SM_LVL=0 +# default: +CONFIG_BT_NIMBLE_SM_SC_ONLY=0 +# default: +# CONFIG_BT_NIMBLE_SMP_ID_RESET is not set +CONFIG_BT_NIMBLE_NVS_PERSIST=y +CONFIG_BT_NIMBLE_MAX_BONDS=1 +# default: +# CONFIG_BT_NIMBLE_HANDLE_REPEAT_PAIRING_DELETION is not set +# end of Security (SMP) + +# +# GAP +# +# default: +CONFIG_BT_NIMBLE_RPA_TIMEOUT=900 +# default: +CONFIG_BT_NIMBLE_WHITELIST_SIZE=12 +# default: +CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=y +# default: +CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3 +# default: +CONFIG_BT_NIMBLE_HS_PVCY=y +# default: +# CONFIG_BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN is not set +# default: +# CONFIG_BT_NIMBLE_HOST_QUEUE_CONG_CHECK is not set +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MAX_CCCDS=8 +# default: +# CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS is not set +# default: +CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000 +# default: +CONFIG_BT_NIMBLE_USE_ESP_TIMER=y +# end of GAP + +# +# GATT / ATT +# +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=517 +# default: +CONFIG_BT_NIMBLE_ATT_MAX_PREP_ENTRIES=64 +# default: +CONFIG_BT_NIMBLE_GATT_MAX_PROCS=4 +# default: +# CONFIG_BT_NIMBLE_BLE_GATT_BLOB_TRANSFER is not set +# default: +# CONFIG_BT_NIMBLE_GATT_CACHING is not set +# default: +# CONFIG_BT_NIMBLE_INCL_SVC_DISCOVERY is not set +# end of GATT / ATT + +# +# L2CAP +# +# default: +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=0 +# end of L2CAP + +# +# Memory Settings +# +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +# default: +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=128 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=12 +# default: +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=320 +# default: +CONFIG_BT_NIMBLE_MSYS_BUF_FROM_HEAP=y +# default: +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_EVT_COUNT=30 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_EVT_DISCARD_COUNT=8 +# default: +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=1 +# default: +# CONFIG_BT_NIMBLE_MEMPOOL_RUNTIME_ALLOC is not set +# end of Memory Settings + +# +# BLE 5.x Features +# +# default: +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +# default: +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=y +# default: +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY=y +# default: +# CONFIG_BT_NIMBLE_EXT_ADV is not set +# default: +# CONFIG_BT_NIMBLE_EXT_SCAN is not set +# default: +# CONFIG_BT_NIMBLE_BLE_POWER_CONTROL is not set +# default: +# CONFIG_BT_NIMBLE_AOA_AOD is not set +# default: +# CONFIG_BT_NIMBLE_ISO is not set +# end of BLE 5.x Features + +# +# Services +# +# default: +CONFIG_BT_NIMBLE_PROX_SERVICE=y +# default: +CONFIG_BT_NIMBLE_ANS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_CTS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_HTP_SERVICE=y +# default: +CONFIG_BT_NIMBLE_IPSS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_TPS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_IAS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_LLS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_SPS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_HR_SERVICE=y +# default: +# CONFIG_BT_NIMBLE_HID_SERVICE is not set +# default: +CONFIG_BT_NIMBLE_BAS_SERVICE=y +# default: +# CONFIG_BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY is not set +# default: +CONFIG_BT_NIMBLE_DIS_SERVICE=y +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_SERIAL_NUMBER is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_HARDWARE_REVISION is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_SOFTWARE_REVISION is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_SYSTEM_ID is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_PNP_ID is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_INCLUDED is not set +# default: +CONFIG_BT_NIMBLE_GAP_SERVICE=y +# default: +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +# default: +CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR=0 +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL is not set +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_RPA_ONLY is not set +# default: +CONFIG_BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP=y +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_NOT_SUPP is not set +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_SUPP is not set +# default: +CONFIG_BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION=-1 + +# +# GAP Appearance write permissions +# +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE is not set +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR=0 +# end of GAP Appearance write permissions + +# +# GAP device name write permissions +# +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE is not set +# end of GAP device name write permissions + +# +# Peripheral Preferred Connection Parameters (PPCP) settings +# +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO=0 +# end of Peripheral Preferred Connection Parameters (PPCP) settings +# end of Services + +# +# Extra Features +# +# default: +# CONFIG_BT_NIMBLE_DYNAMIC_SERVICE is not set +# default: +# CONFIG_BT_NIMBLE_BLUFI_ENABLE is not set +# default: +# CONFIG_BT_NIMBLE_ENC_ADV_DATA is not set +# default: +# CONFIG_BT_NIMBLE_ADV_UUID_CONCAT is not set +# default: +# CONFIG_BT_NIMBLE_GATTC_PROC_PREEMPTION_PROTECT is not set +# default: +# CONFIG_BT_NIMBLE_GATTC_AUTO_PAIR is not set +# default: +CONFIG_BT_NIMBLE_EATT_CHAN_NUM=0 +# default: +# CONFIG_BT_NIMBLE_SUBRATE is not set +# default: +# CONFIG_BT_NIMBLE_STATIC_PASSKEY is not set +# default: +CONFIG_BT_NIMBLE_DTM_MODE_TEST=y +# default: +CONFIG_BT_NIMBLE_MEM_OPTIMIZATION=y +# default: +CONFIG_BT_NIMBLE_STATIC_TO_DYNAMIC=y +# default: +CONFIG_BT_NIMBLE_SM_SIGN_CNT=y +# default: +CONFIG_BT_NIMBLE_CPFD_CAFD=y +# default: +CONFIG_BT_NIMBLE_RECONFIG_MTU=y +# default: +# CONFIG_BT_NIMBLE_LOW_SPEED_MODE is not set +# end of Extra Features + +# +# NimBLE Mesh +# +# default: +# CONFIG_BT_NIMBLE_MESH is not set +# end of NimBLE Mesh + +# +# Host-controller Transport +# +# default: +CONFIG_UART_HW_FLOWCTRL_DISABLE=y +# default: +# CONFIG_UART_HW_FLOWCTRL_CTS_RTS is not set +# default: +CONFIG_BT_NIMBLE_HCI_UART_FLOW_CTRL=0 +# default: +CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=19 +# default: +CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport + +# +# Debugging/Testing +# +# default: +# CONFIG_BT_NIMBLE_MEM_DEBUG is not set +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_NONE is not set +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR is not set +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING is not set +# default: +CONFIG_BT_NIMBLE_LOG_LEVEL_INFO=y +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_DEBUG is not set +# default: +CONFIG_BT_NIMBLE_LOG_LEVEL=1 +# default: +CONFIG_BT_NIMBLE_PRINT_ERR_NAME=y +# default: +# CONFIG_BT_NIMBLE_DEBUG is not set +# default: +# CONFIG_BT_NIMBLE_TEST_THROUGHPUT_TEST is not set +# end of Debugging/Testing + +# +# Vendor / Optimization +# +# default: +# CONFIG_BT_NIMBLE_VS_SUPPORT is not set +# default: +# CONFIG_BT_NIMBLE_OPTIMIZE_MULTI_CONN is not set +# default: +# CONFIG_BT_NIMBLE_HIGH_DUTY_ADV_ITVL is not set +# default: +# CONFIG_BT_NIMBLE_ADV_SEND_CONSTANT_DID is not set +# default: +# CONFIG_BT_NIMBLE_SCAN_ALLOW_ENH_ADI_FILTER is not set +# end of Vendor / Optimization + +# +# Helper Utils +# +# default: +CONFIG_BT_NIMBLE_CHK_HOST_STATUS=y +# default: +CONFIG_BT_NIMBLE_UTIL_API=y +# default: +CONFIG_BT_NIMBLE_EXTRA_ADV_FIELDS=y +# end of Helper Utils +# end of NimBLE Options + +# +# Controller Options +# + +# +# HCI Config +# +# default: +CONFIG_BT_LE_HCI_INTERFACE_USE_RAM=y +# default: +# CONFIG_BT_LE_HCI_INTERFACE_USE_UART is not set +# end of HCI Config + +# default: +CONFIG_BT_LE_CONTROLLER_NPL_OS_PORTING_SUPPORT=y +# default: +CONFIG_BT_LE_CONTROLLER_TASK_STACK_SIZE=4096 + +# +# Controller debug features +# +# default: +# CONFIG_BT_LE_ERROR_SIM_ENABLED is not set +# default: +# CONFIG_BT_LE_ASSERT_WHEN_ABNORMAL_DISCONN_ENABLED is not set +# default: +# CONFIG_BT_LE_DEBUG_REMAIN_SCENE_ENABLED is not set +# default: +# CONFIG_BT_LE_PTR_CHECK_ENABLED is not set +# default: +# CONFIG_BT_LE_MEM_CHECK_ENABLED is not set +# end of Controller debug features + +# default: +CONFIG_BT_LE_LL_RESOLV_LIST_SIZE=4 +# default: +CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT=20 +# default: +CONFIG_BT_LE_LL_SCA=60 +# default: +# CONFIG_BT_LE_LL_PEER_SCA_SET_ENABLE is not set +# default: +# CONFIG_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +# default: +CONFIG_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y +# default: +CONFIG_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_EFF=0 +# default: +# CONFIG_BT_LE_SLEEP_ENABLE is not set +# default: +CONFIG_BT_LE_LP_CLK_SRC_MAIN_XTAL=y +# default: +# CONFIG_BT_LE_LP_CLK_SRC_DEFAULT is not set +# default: +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y +# default: +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 +# default: +CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 +# default: +CONFIG_BT_LE_SCAN_DUPL=y +# default: +CONFIG_BT_LE_SCAN_DUPL_TYPE_DEVICE=y +# default: +# CONFIG_BT_LE_SCAN_DUPL_TYPE_DATA is not set +# default: +# CONFIG_BT_LE_SCAN_DUPL_TYPE_DATA_DEVICE is not set +# default: +CONFIG_BT_LE_SCAN_DUPL_TYPE=0 +# default: +CONFIG_BT_LE_SCAN_DUPL_CACHE_REFRESH_PERIOD=0 +# default: +CONFIG_BT_LE_MSYS_INIT_IN_CONTROLLER=y +# default: +# CONFIG_BT_LE_TX_CCA_ENABLED is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N24 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N21 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N18 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N15 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N12 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N9 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N6 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N3 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_N0 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_P3 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_P6 is not set +# default: +CONFIG_BT_LE_DFT_TX_POWER_LEVEL_P9=y +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_P12 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_P15 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_P18 is not set +# default: +# CONFIG_BT_LE_DFT_TX_POWER_LEVEL_P20 is not set +# default: +CONFIG_BT_LE_DFT_TX_POWER_LEVEL_DBM_EFF=9 +# default: +# CONFIG_BT_LE_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set +# default: +# CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY is not set + +# +# BLE disconnects when Instant Passed (0x28) occurs +# +# default: +# CONFIG_BT_LE_CTRL_LLCP_CONN_UPDATE is not set +# default: +# CONFIG_BT_LE_CTRL_LLCP_CHAN_MAP_UPDATE is not set +# default: +# CONFIG_BT_LE_CTRL_LLCP_PHY_UPDATE is not set +# end of BLE disconnects when Instant Passed (0x28) occurs + +# default: +CONFIG_BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX=32 +# default: +# CONFIG_BT_LE_CTRL_CHAN_ASS_EN is not set +# default: +CONFIG_BT_LE_CTRL_ADV_DATA_LENGTH_ZERO_AUX=y +# default: +CONFIG_BT_LE_RXBUF_OPT_ENABLED=y +# default: +CONFIG_BT_LE_CTRL_FAST_CONN_DATA_TX_EN=y + +# +# Reserved Memory Config +# +# default: +CONFIG_BT_LE_EXT_ADV_RESERVED_MEMORY_COUNT=2 +# default: +CONFIG_BT_LE_CONN_RESERVED_MEMORY_COUNT=2 +# end of Reserved Memory Config + +# default: +# CONFIG_BT_LE_DTM_ENABLED is not set + +# +# Scheduling Priority Level Config +# +# default: +CONFIG_BT_LE_ADV_SCHED_PRIO_LOW_LEVEL=y +# default: +# CONFIG_BT_LE_ADV_SCHED_PRIO_MID_LEVEL is not set +# default: +# CONFIG_BT_LE_ADV_SCHED_PRIO_HIGH_LEVEL is not set +# default: +CONFIG_BT_LE_DFT_ADV_SCHED_PRIO_LEVEL=0 +# default: +# CONFIG_BT_LE_PERIODIC_ADV_SCHED_PRIO_LOW_LEVEL is not set +# default: +CONFIG_BT_LE_PERIODIC_ADV_SCHED_PRIO_MID_LEVEL=y +# default: +# CONFIG_BT_LE_PERIODIC_ADV_SCHED_PRIO_HIGH_LEVEL is not set +# default: +CONFIG_BT_LE_DFT_PERIODIC_ADV_SCHED_PRIO_LEVEL=1 +# default: +# CONFIG_BT_LE_SYNC_SCHED_PRIO_LOW_LEVEL is not set +# default: +CONFIG_BT_LE_SYNC_SCHED_PRIO_MID_LEVEL=y +# default: +# CONFIG_BT_LE_SYNC_SCHED_PRIO_HIGH_LEVEL is not set +# default: +CONFIG_BT_LE_DFT_SYNC_SCHED_PRIO_LEVEL=1 +# end of Scheduling Priority Level Config + +# default: +# CONFIG_BT_LE_CTRL_SLV_FAST_RX_CONN_DATA_EN is not set +# default: +# CONFIG_BT_LE_CTRL_DL_ITVL_PHY_SYNC_EN is not set +# default: +# CONFIG_BT_LE_CTRL_ADV_FAST_TX_EN is not set + +# +# Adv Tx Options +# +# default: +# CONFIG_BT_LE_CTRL_ADV_EVENT_INC_ON_FULL_TX_EN is not set +# default: +# CONFIG_BT_LE_CTRL_ADV_CH38_39_TX_AVOID_CONFLICT_EN is not set +# default: +# CONFIG_BT_LE_ADV_CH39_TXPWR_CONFIG_SEPARATELY_EN is not set +# default: +# CONFIG_BT_LE_CTRL_ADV_TX_CONTINUE_WHEN_EXCEED_ITVL_EN is not set +# default: +# CONFIG_BT_LE_CTRL_ADV_RAND_CH_IDX_EN is not set +# default: +CONFIG_BT_LE_DFT_CH39_TX_POWER_LEVEL_DBM_EFF=0 +# end of Adv Tx Options +# end of Controller Options + +# default: +# CONFIG_BT_HCI_LOG_DEBUG_EN is not set # end of Bluetooth -# -# Console Library -# # default: -# CONFIG_CONSOLE_SORTED_HELP is not set -# end of Console Library - -# -# Legacy Driver Configurations -# - -# -# Legacy TWAI Driver Configurations -# -# default: -# CONFIG_TWAI_ISR_IN_IRAM_LEGACY is not set -# default: -# CONFIG_TWAI_SUPPRESS_DEPRECATE_WARN is not set -# default: -# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy TWAI Driver Configurations - -# -# Legacy I2C Driver Configurations -# -# default: -# CONFIG_I2C_SUPPRESS_DEPRECATE_WARN is not set -# default: -# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy I2C Driver Configurations -# end of Legacy Driver Configurations +# CONFIG_BLE_MESH is not set # # eFuse Bit Manager @@ -1319,44 +1927,6 @@ CONFIG_COMPILER_CXX_GLIBCXX_CONSTEXPR_NO_CHANGE=y CONFIG_EFUSE_MAX_BLK_LEN=256 # end of eFuse Bit Manager -# -# ESP-TLS -# -# default: -CONFIG_ESP_TLS_USING_MBEDTLS=y -# default: -# CONFIG_ESP_TLS_CUSTOM_STACK is not set -# default: -# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set -# default: -CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y -# default: -# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set -# default: -# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set -# default: -# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set -# default: -# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set -# default: -# CONFIG_ESP_TLS_PSK_VERIFICATION is not set -# default: -# CONFIG_ESP_TLS_INSECURE is not set -# default: -CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y -# end of ESP-TLS - -# -# ADC and ADC Calibration -# -# default: -# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set -# default: -# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set -# default: -# CONFIG_ADC_ENABLE_DEBUG_LOG is not set -# end of ADC and ADC Calibration - # # Wireless Coexistence # @@ -1379,28 +1949,6 @@ CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y CONFIG_ESP_ERR_TO_NAME_LOOKUP=y # end of Common ESP-related -# -# ESP-Driver:Analog Comparator Configurations -# -# default: -CONFIG_ANA_CMPR_ISR_HANDLER_IN_IRAM=y -# default: -# CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM is not set -# default: -# CONFIG_ANA_CMPR_ISR_CACHE_SAFE is not set -# default: -CONFIG_ANA_CMPR_OBJ_CACHE_SAFE=y -# default: -# CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:Analog Comparator Configurations - -# -# ESP-Driver:BitScrambler Configurations -# -# default: -# CONFIG_BITSCRAMBLER_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:BitScrambler Configurations - # # GDMA Configurations # @@ -1421,138 +1969,6 @@ CONFIG_GDMA_OBJ_DRAM_SAFE=y # CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set # end of ESP-Driver:GPIO Configurations -# -# ESP-Driver:GPTimer Configurations -# -# default: -CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y -# default: -# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set -# default: -# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set -# default: -CONFIG_GPTIMER_OBJ_CACHE_SAFE=y -# default: -# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:GPTimer Configurations - -# -# ESP-Driver:I2C Configurations -# -# default: -# CONFIG_I2C_ISR_IRAM_SAFE is not set -# default: -# CONFIG_I2C_ENABLE_DEBUG_LOG is not set -# default: -CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y -# end of ESP-Driver:I2C Configurations - -# -# ESP-Driver:I2S Configurations -# -# default: -# CONFIG_I2S_ISR_IRAM_SAFE is not set -# default: -# CONFIG_I2S_CTRL_FUNC_IN_IRAM is not set -# default: -# CONFIG_I2S_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:I2S Configurations - -# -# ESP-Driver:I3C Master Configurations -# -# default: -# CONFIG_I3C_MASTER_ISR_CACHE_SAFE is not set -# default: -# CONFIG_I3C_MASTER_ENABLE_DEBUG_LOG is not set -# default: -# CONFIG_I3C_MASTER_ISR_HANDLER_IN_IRAM is not set -# end of ESP-Driver:I3C Master Configurations - -# -# ESP-Driver:LEDC Configurations -# -# default: -# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:LEDC Configurations - -# -# ESP-Driver:MCPWM Configurations -# -# default: -CONFIG_MCPWM_ISR_HANDLER_IN_IRAM=y -# default: -# CONFIG_MCPWM_ISR_CACHE_SAFE is not set -# default: -# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set -# default: -CONFIG_MCPWM_OBJ_CACHE_SAFE=y -# default: -# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:MCPWM Configurations - -# -# ESP-Driver:Parallel IO Configurations -# -# default: -CONFIG_PARLIO_TX_ISR_HANDLER_IN_IRAM=y -# default: -CONFIG_PARLIO_RX_ISR_HANDLER_IN_IRAM=y -# default: -# CONFIG_PARLIO_TX_ISR_CACHE_SAFE is not set -# default: -# CONFIG_PARLIO_RX_ISR_CACHE_SAFE is not set -# default: -CONFIG_PARLIO_OBJ_CACHE_SAFE=y -# default: -# CONFIG_PARLIO_ENABLE_DEBUG_LOG is not set -# default: -# CONFIG_PARLIO_ISR_IRAM_SAFE is not set -# end of ESP-Driver:Parallel IO Configurations - -# -# ESP-Driver:PCNT Configurations -# -# default: -# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set -# default: -# CONFIG_PCNT_ISR_IRAM_SAFE is not set -# default: -# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:PCNT Configurations - -# -# ESP-Driver:RMT Configurations -# -# default: -CONFIG_RMT_ENCODER_FUNC_IN_IRAM=y -# default: -CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM=y -# default: -CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM=y -# default: -# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set -# default: -# CONFIG_RMT_TX_ISR_CACHE_SAFE is not set -# default: -# CONFIG_RMT_RX_ISR_CACHE_SAFE is not set -# default: -CONFIG_RMT_OBJ_CACHE_SAFE=y -# default: -# CONFIG_RMT_ENABLE_DEBUG_LOG is not set -# default: -# CONFIG_RMT_ISR_IRAM_SAFE is not set -# end of ESP-Driver:RMT Configurations - -# -# ESP-Driver:Sigma Delta Modulator Configurations -# -# default: -# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set -# default: -# CONFIG_SDM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:Sigma Delta Modulator Configurations - # # ESP-Driver:SPI Configurations # @@ -1564,28 +1980,6 @@ CONFIG_SPI_MASTER_ISR_IN_IRAM=y CONFIG_SPI_SLAVE_ISR_IN_IRAM=y # end of ESP-Driver:SPI Configurations -# -# ESP-Driver:Temperature Sensor Configurations -# -# default: -# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set -# default: -# CONFIG_TEMP_SENSOR_ISR_IRAM_SAFE is not set -# end of ESP-Driver:Temperature Sensor Configurations - -# -# ESP-Driver:TWAI Configurations -# -# default: -# CONFIG_TWAI_ISR_IN_IRAM is not set -# default: -# CONFIG_TWAI_IO_FUNC_IN_IRAM is not set -# default: -# CONFIG_TWAI_ISR_CACHE_SAFE is not set -# default: -# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:TWAI Configurations - # # ESP-Driver:UART Configurations # @@ -1611,19 +2005,6 @@ CONFIG_SPI_SLAVE_ISR_IN_IRAM=y CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y # end of ESP-Driver:USB Serial/JTAG Configuration -# -# Ethernet -# -# default: -CONFIG_ETH_ENABLED=y -# default: -CONFIG_ETH_USE_SPI_ETHERNET=y -# default: -# CONFIG_ETH_USE_OPENETH is not set -# default: -# CONFIG_ETH_TRANSMIT_MUTEX is not set -# end of Ethernet - # # Event Loop Library # @@ -1632,8 +2013,6 @@ CONFIG_ETH_USE_SPI_ETHERNET=y # default: CONFIG_ESP_EVENT_POST_FROM_ISR=y # default: -CONFIG_ESP_EVENT_POST_FROM_ISR_SIZE=4 -# default: CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y # end of Event Loop Library @@ -1650,85 +2029,6 @@ CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y CONFIG_ESP_GDBSTUB_MAX_TASKS=32 # end of GDB Stub -# -# ESP HID -# -# default: -CONFIG_ESPHID_TASK_SIZE_BT=2048 -# default: -CONFIG_ESPHID_TASK_SIZE_BLE=4096 -# end of ESP HID - -# -# ESP HTTP client -# -# default: -CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -# default: -# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set -# default: -# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set -# default: -# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set -# default: -# CONFIG_ESP_HTTP_CLIENT_ENABLE_GET_CONTENT_RANGE is not set -# default: -CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 -# default: -CONFIG_ESP_HTTP_CLIENT_PSA_CRYPTO_MIGRATE=y -# default: -# CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS is not set -# end of ESP HTTP client - -# -# HTTP Server -# -# default: -CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 -# default: -CONFIG_HTTPD_MAX_URI_LEN=512 -# default: -CONFIG_HTTPD_ERR_RESP_NO_DELAY=y -# default: -CONFIG_HTTPD_PURGE_BUF_LEN=32 -# default: -# CONFIG_HTTPD_LOG_PURGE_DATA is not set -# default: -# CONFIG_HTTPD_WS_SUPPORT is not set -# default: -# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set -# default: -CONFIG_HTTPD_ENABLE_EVENTS=y -# default: -CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 -# default: -CONFIG_HTTPD_SERVER_PSA_CRYPTO_MIGRATE=y -# end of HTTP Server - -# -# ESP HTTPS OTA -# -# default: -# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set -# default: -# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set -# default: -CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 -# default: -# CONFIG_ESP_HTTPS_OTA_ENABLE_PARTIAL_DOWNLOAD is not set -# default: -CONFIG_ESP_HTTPS_OTA_VERIFY_SPI_MODE=y -# end of ESP HTTPS OTA - -# -# ESP HTTPS server -# -# default: -# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set -# default: -# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set -# end of ESP HTTPS server - # # Hardware Settings # @@ -1810,8 +2110,6 @@ CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=0 # CONFIG_ESP_SLEEP_DEBUG is not set # default: CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y -# default: -# CONFIG_ESP_SLEEP_SET_FLASH_DPD is not set # end of Sleep Config # @@ -1900,13 +2198,6 @@ CONFIG_ESP_ENABLE_PVT=y CONFIG_ESP_INTR_IN_IRAM=y # end of Hardware Settings -# -# ESP-Driver:LCD Controller Configurations -# -# default: -# CONFIG_LCD_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:LCD Controller Configurations - # # LibC # @@ -1994,7 +2285,6 @@ CONFIG_ESP_PHY_ENABLED=y CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y # default: # CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set -# default: CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 # default: CONFIG_ESP_PHY_MAX_TX_POWER=20 @@ -2020,6 +2310,8 @@ CONFIG_ESP_PHY_PLL_TRACK_PERIOD_MS=1000 CONFIG_ESP_PHY_IRAM_OPT=y # default: # CONFIG_ESP_PHY_DEBUG is not set +# default: +CONFIG_ESP_PHY_ENABLE_VERSION_PRINT=y # end of PHY # @@ -2096,20 +2388,12 @@ CONFIG_ESP_CRYPTO_DPA_PROTECTION_LEVEL=1 # # ESP-STDIO # -# default: CONFIG_ESP_CONSOLE_UART_DEFAULT=y -# default: # CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set -# default: # CONFIG_ESP_CONSOLE_UART_CUSTOM is not set -# default: # CONFIG_ESP_CONSOLE_NONE is not set -# default: -# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set -# default: -CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y -# default: -CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +# CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG is not set # default: CONFIG_ESP_CONSOLE_UART=y # default: @@ -2175,8 +2459,7 @@ CONFIG_ESP_SYSTEM_MEMPROT_PMP=y CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 # default: CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -# default: -CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 # default: CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y # default: @@ -2199,8 +2482,7 @@ CONFIG_ESP_TASK_WDT_INIT=y CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 # default: CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y -# default: -# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +CONFIG_ESP_PANIC_HANDLER_IRAM=y # default: # CONFIG_ESP_DEBUG_STUBS_ENABLE is not set # default: @@ -2249,40 +2531,20 @@ CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y CONFIG_ESP_TIMER_IMPL_SYSTIMER=y # end of ESP Timer (High Resolution Timer) -# -# ESP Trace Configuration -# -# default: -# CONFIG_ESP_TRACE_LIB_EXTERNAL is not set -# default: -CONFIG_ESP_TRACE_LIB_NONE=y -# default: -CONFIG_ESP_TRACE_LIB_NAME="none" -# default: -# CONFIG_ESP_TRACE_TRANSPORT_APPTRACE is not set -# default: -CONFIG_ESP_TRACE_TRANSPORT_NONE=y -# default: -CONFIG_ESP_TRACE_TRANSPORT_NAME="none" -# end of ESP Trace Configuration - # # Wi-Fi # # default: CONFIG_ESP_WIFI_ENABLED=y -# default: -CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 -# default: -CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=6 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=16 # default: # CONFIG_ESP_WIFI_STATIC_TX_BUFFER is not set # default: CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y # default: CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 -# default: -CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=16 # default: CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y # default: @@ -2305,8 +2567,7 @@ CONFIG_ESP_WIFI_RX_BA_WIN=6 CONFIG_ESP_WIFI_NVS_ENABLED=y # default: CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 -# default: -CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=16 # default: CONFIG_ESP_WIFI_IRAM_OPT=y # default: @@ -2401,290 +2662,10 @@ CONFIG_ESP_WIFI_TX_HETB_QUEUE_NUM=3 CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y # default: # CONFIG_ESP_WIFI_ENT_FREE_DYNAMIC_BUFFER is not set +# default: +# CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT is not set # end of Wi-Fi -# -# Wi-Fi Remote -# -# default: -CONFIG_ESP_WIFI_REMOTE_IN_IDF=y -# default: -CONFIG_ESP_WIFI_REMOTE_ENABLED=y -# default: -CONFIG_SLAVE_IDF_TARGET_ESP32=y -# default: -# CONFIG_SLAVE_IDF_TARGET_ESP32S2 is not set -# default: -# CONFIG_SLAVE_IDF_TARGET_ESP32C3 is not set -# default: -# CONFIG_SLAVE_IDF_TARGET_ESP32S3 is not set -# default: -# CONFIG_SLAVE_IDF_TARGET_ESP32C2 is not set -# default: -# CONFIG_SLAVE_IDF_TARGET_ESP32C6 is not set -# default: -# CONFIG_SLAVE_IDF_TARGET_ESP32C5 is not set -# default: -# CONFIG_SLAVE_IDF_TARGET_ESP32C61 is not set -# default: -CONFIG_SLAVE_SOC_WIFI_SUPPORTED=y -# default: -CONFIG_SLAVE_SOC_WIFI_WAPI_SUPPORT=y -# default: -CONFIG_SLAVE_SOC_WIFI_CSI_SUPPORT=y -# default: -CONFIG_SLAVE_SOC_WIFI_MESH_SUPPORT=y -# default: -CONFIG_SLAVE_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y -# default: -CONFIG_SLAVE_SOC_WIFI_NAN_SUPPORT=y -# default: -CONFIG_SLAVE_IDF_TARGET_ARCH_XTENSA=y - -# -# Wi-Fi configuration -# -# default: -CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=10 -# default: -CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=32 -# default: -# CONFIG_WIFI_RMT_STATIC_TX_BUFFER is not set -# default: -CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER=y -# default: -CONFIG_WIFI_RMT_TX_BUFFER_TYPE=1 -# default: -CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=32 -# default: -CONFIG_WIFI_RMT_STATIC_RX_MGMT_BUFFER=y -# default: -# CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUFFER is not set -# default: -CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUF=0 -# default: -CONFIG_WIFI_RMT_RX_MGMT_BUF_NUM_DEF=5 -# default: -# CONFIG_WIFI_RMT_CSI_ENABLED is not set -# default: -CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y -# default: -CONFIG_WIFI_RMT_TX_BA_WIN=6 -# default: -CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y -# default: -CONFIG_WIFI_RMT_RX_BA_WIN=6 -# default: -CONFIG_WIFI_RMT_NVS_ENABLED=y -# default: -CONFIG_WIFI_RMT_TASK_PINNED_TO_CORE_0=y -# default: -# CONFIG_WIFI_RMT_TASK_PINNED_TO_CORE_1 is not set -# default: -CONFIG_WIFI_RMT_SOFTAP_BEACON_MAX_LEN=752 -# default: -CONFIG_WIFI_RMT_MGMT_SBUF_NUM=32 -# default: -CONFIG_WIFI_RMT_IRAM_OPT=y -# default: -# CONFIG_WIFI_RMT_EXTRA_IRAM_OPT is not set -# default: -CONFIG_WIFI_RMT_RX_IRAM_OPT=y -# default: -CONFIG_WIFI_RMT_ENABLE_WPA3_SAE=y -# default: -CONFIG_WIFI_RMT_ENABLE_SAE_H2E=y -# default: -CONFIG_WIFI_RMT_ENABLE_SAE_PK=y -# default: -CONFIG_WIFI_RMT_SOFTAP_SAE_SUPPORT=y -# default: -CONFIG_WIFI_RMT_ENABLE_WPA3_OWE_STA=y -# default: -CONFIG_WIFI_RMT_WPA3_COMPATIBLE_SUPPORT=y -# default: -# CONFIG_WIFI_RMT_SLP_IRAM_OPT is not set -# default: -CONFIG_WIFI_RMT_SLP_DEFAULT_MIN_ACTIVE_TIME=50 -# default: -# CONFIG_WIFI_RMT_BSS_MAX_IDLE_SUPPORT is not set -# default: -CONFIG_WIFI_RMT_SLP_DEFAULT_MAX_ACTIVE_TIME=10 -# default: -CONFIG_WIFI_RMT_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 -# default: -CONFIG_WIFI_RMT_STA_DISCONNECTED_PM_ENABLE=y -# default: -CONFIG_WIFI_RMT_GMAC_SUPPORT=y -# default: -CONFIG_WIFI_RMT_SOFTAP_SUPPORT=y -# default: -# CONFIG_WIFI_RMT_SLP_BEACON_LOST_OPT is not set -# default: -CONFIG_WIFI_RMT_ESPNOW_MAX_ENCRYPT_NUM=7 -# default: -# CONFIG_WIFI_RMT_NAN_SYNC_ENABLE is not set -# default: -CONFIG_WIFI_RMT_MBEDTLS_CRYPTO=y -# default: -CONFIG_WIFI_RMT_MBEDTLS_TLS_CLIENT=y -# default: -# CONFIG_WIFI_RMT_WAPI_PSK is not set -# default: -# CONFIG_WIFI_RMT_11KV_SUPPORT is not set -# default: -# CONFIG_WIFI_RMT_MBO_SUPPORT is not set -# default: -# CONFIG_WIFI_RMT_DPP_SUPPORT is not set -# default: -# CONFIG_WIFI_RMT_11R_SUPPORT is not set -# default: -# CONFIG_WIFI_RMT_WPS_SOFTAP_REGISTRAR is not set - -# -# WPS Configuration Options -# -# default: -# CONFIG_WIFI_RMT_WPS_STRICT is not set -# default: -# CONFIG_WIFI_RMT_WPS_PASSPHRASE is not set -# default: -# CONFIG_WIFI_RMT_WPS_RECONNECT_ON_FAIL is not set -# end of WPS Configuration Options - -# default: -# CONFIG_WIFI_RMT_DEBUG_PRINT is not set -# default: -CONFIG_WIFI_RMT_ENTERPRISE_SUPPORT=y -# default: -# CONFIG_WIFI_RMT_ENT_FREE_DYNAMIC_BUFFER is not set -# end of Wi-Fi configuration - -# default: -CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y -# default: -# CONFIG_ESP_WIFI_REMOTE_LIBRARY_EPPP is not set -# default: -# CONFIG_ESP_WIFI_REMOTE_LIBRARY_CUSTOM is not set -# default: -CONFIG_ESP_WIFI_REMOTE_EAP_ENABLED=y -# end of Wi-Fi Remote - -# -# Core dump -# -# default: -# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set -# default: -# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set -# default: -CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y -# end of Core dump - -# -# FAT Filesystem support -# -# default: -CONFIG_FATFS_VOLUME_COUNT=2 -# default: -# CONFIG_FATFS_LFN_NONE is not set -# default: -CONFIG_FATFS_LFN_HEAP=y -# default: -# CONFIG_FATFS_LFN_STACK is not set -# default: -# CONFIG_FATFS_SECTOR_512 is not set -# default: -CONFIG_FATFS_SECTOR_4096=y -# default: -# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set -# default: -CONFIG_FATFS_CODEPAGE_437=y -# default: -# CONFIG_FATFS_CODEPAGE_720 is not set -# default: -# CONFIG_FATFS_CODEPAGE_737 is not set -# default: -# CONFIG_FATFS_CODEPAGE_771 is not set -# default: -# CONFIG_FATFS_CODEPAGE_775 is not set -# default: -# CONFIG_FATFS_CODEPAGE_850 is not set -# default: -# CONFIG_FATFS_CODEPAGE_852 is not set -# default: -# CONFIG_FATFS_CODEPAGE_855 is not set -# default: -# CONFIG_FATFS_CODEPAGE_857 is not set -# default: -# CONFIG_FATFS_CODEPAGE_860 is not set -# default: -# CONFIG_FATFS_CODEPAGE_861 is not set -# default: -# CONFIG_FATFS_CODEPAGE_862 is not set -# default: -# CONFIG_FATFS_CODEPAGE_863 is not set -# default: -# CONFIG_FATFS_CODEPAGE_864 is not set -# default: -# CONFIG_FATFS_CODEPAGE_865 is not set -# default: -# CONFIG_FATFS_CODEPAGE_866 is not set -# default: -# CONFIG_FATFS_CODEPAGE_869 is not set -# default: -# CONFIG_FATFS_CODEPAGE_932 is not set -# default: -# CONFIG_FATFS_CODEPAGE_936 is not set -# default: -# CONFIG_FATFS_CODEPAGE_949 is not set -# default: -# CONFIG_FATFS_CODEPAGE_950 is not set -# default: -CONFIG_FATFS_CODEPAGE=437 -# default: -CONFIG_FATFS_MAX_LFN=255 -# default: -CONFIG_FATFS_API_ENCODING_ANSI_OEM=y -# default: -# CONFIG_FATFS_API_ENCODING_UTF_8 is not set -# default: -CONFIG_FATFS_FS_LOCK=0 -# default: -CONFIG_FATFS_TIMEOUT_MS=10000 -# default: -CONFIG_FATFS_PER_FILE_CACHE=y -# default: -# CONFIG_FATFS_ALLOC_PREFER_ALIGNED_WORK_BUFFERS is not set -# default: -# CONFIG_FATFS_USE_FASTSEEK is not set -# default: -CONFIG_FATFS_USE_STRFUNC_NONE=y -# default: -# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set -# default: -# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set -# default: -CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 -# default: -# CONFIG_FATFS_IMMEDIATE_FSYNC is not set -# default: -# CONFIG_FATFS_USE_LABEL is not set -# default: -CONFIG_FATFS_LINK_LOCK=y -# default: -CONFIG_FATFS_USE_DYN_BUFFERS=y - -# -# File system free space calculation behavior -# -# default: -CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0 -# default: -CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 -# end of File system free space calculation behavior -# end of FAT Filesystem support - # # FreeRTOS # @@ -2693,6 +2674,8 @@ CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 # Kernel # # default: +# CONFIG_FREERTOS_SMP is not set +# default: CONFIG_FREERTOS_UNICORE=y # default: CONFIG_FREERTOS_HZ=100 @@ -2750,8 +2733,6 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 # Port # # default: -CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y -# default: # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set # default: CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y @@ -2808,10 +2789,10 @@ CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y # default: CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 # default: -CONFIG_HAL_WDT_USE_ROM_IMPL=y -# default: CONFIG_HAL_SYSTIMER_USE_ROM_IMPL=y # default: +CONFIG_HAL_WDT_USE_ROM_IMPL=y +# default: CONFIG_HAL_GPIO_USE_ROM_IMPL=y # end of Hardware Abstraction Layer (HAL) and Low Level (LL) @@ -2842,37 +2823,6 @@ CONFIG_HEAP_TLSF_USE_ROM_IMPL=y # CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set # end of Heap memory debugging -# -# IEEE 802.15.4 -# -# default: -CONFIG_IEEE802154_ENABLED=y -# default: -CONFIG_IEEE802154_RX_BUFFER_SIZE=20 -# default: -# CONFIG_IEEE802154_CCA_CARRIER is not set -# default: -CONFIG_IEEE802154_CCA_ED=y -# default: -# CONFIG_IEEE802154_CCA_CARRIER_OR_ED is not set -# default: -# CONFIG_IEEE802154_CCA_CARRIER_AND_ED is not set -# default: -CONFIG_IEEE802154_CCA_MODE=1 -# default: -CONFIG_IEEE802154_CCA_THRESHOLD=-75 -# default: -CONFIG_IEEE802154_PENDING_TABLE_SIZE=20 -# default: -# CONFIG_IEEE802154_MULTI_PAN_ENABLE is not set -# default: -CONFIG_IEEE802154_INTERFACE_NUM=1 -# default: -CONFIG_IEEE802154_TIMING_OPTIMIZATION=y -# default: -# CONFIG_IEEE802154_DEBUG is not set -# end of IEEE 802.15.4 - # # Log # @@ -2886,17 +2836,11 @@ CONFIG_LOG_VERSION=1 # # Log Level # -# default: # CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -# default: # CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set -# default: # CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -# default: CONFIG_LOG_DEFAULT_LEVEL_INFO=y -# default: # CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set -# default: # CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set # default: CONFIG_LOG_DEFAULT_LEVEL=3 @@ -2934,7 +2878,6 @@ CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 # # Format # -# default: # CONFIG_LOG_COLORS is not set # default: CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y @@ -3418,8 +3361,6 @@ CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y # default: # CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set # default: -# CONFIG_MBEDTLS_TLS_DISABLED is not set -# default: # CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE is not set # default: # CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set @@ -3505,8 +3446,6 @@ CONFIG_MBEDTLS_CMAC_C=y # Asymmetric Ciphers # # default: -CONFIG_MBEDTLS_BIGNUM_C=y -# default: CONFIG_MBEDTLS_RSA_C=y # default: CONFIG_MBEDTLS_ECP_C=y @@ -3586,15 +3525,12 @@ CONFIG_MBEDTLS_ROM_MD5=y # # Hardware Acceleration # -# default: CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY=y # default: # CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN is not set -# default: CONFIG_MBEDTLS_HARDWARE_ECC=y # default: CONFIG_MBEDTLS_ECC_OTHER_CURVES_SOFT_FALLBACK=y -# default: CONFIG_MBEDTLS_HARDWARE_SHA=y # default: CONFIG_MBEDTLS_HARDWARE_MPI=y @@ -3604,26 +3540,13 @@ CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI=y CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y # default: CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 -# default: -CONFIG_MBEDTLS_HARDWARE_AES=y -# default: -# CONFIG_MBEDTLS_AES_SOFT_FALLBACK is not set -# default: -CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -# default: -CONFIG_MBEDTLS_AES_USE_INTERRUPT=y -# default: -CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 -# default: -# CONFIG_MBEDTLS_AES_USE_PSEUDO_ROUND_FUNC is not set -# default: -CONFIG_MBEDTLS_AES_HW_SMALL_DATA_LEN_OPTIM=y +# CONFIG_MBEDTLS_HARDWARE_AES is not set # default: # CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set # default: # CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set # default: -CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL=y +# CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL is not set # end of Hardware Acceleration # @@ -3675,36 +3598,6 @@ CONFIG_MBEDTLS_PKCS1_V21=y # CONFIG_NVS_FLASH_VERIFY_ERASE is not set # end of NVS -# -# OpenThread -# -# default: -# CONFIG_OPENTHREAD_ENABLED is not set - -# -# OpenThread Spinel -# -# default: -# CONFIG_OPENTHREAD_SPINEL_ONLY is not set -# end of OpenThread Spinel - -# default: -# CONFIG_OPENTHREAD_DEBUG is not set -# end of OpenThread - -# -# Protocomm -# -# default: -# CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 is not set -# default: -# CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 is not set -# default: -CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y -# default: -CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y -# end of Protocomm - # # PThreads # @@ -3720,13 +3613,6 @@ CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" # end of PThreads -# -# SD Protocol Layer Configuration -# -# default: -CONFIG_SD_ENABLE_SDIO_SUPPORT=y -# end of SD Protocol Layer Configuration - # # MMU Config # @@ -3840,109 +3726,6 @@ CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ=y # end of SPI Flash driver -# -# SPIFFS Configuration -# -# default: -CONFIG_SPIFFS_MAX_PARTITIONS=3 - -# -# SPIFFS Cache Configuration -# -# default: -CONFIG_SPIFFS_CACHE=y -# default: -CONFIG_SPIFFS_CACHE_WR=y -# default: -# CONFIG_SPIFFS_CACHE_STATS is not set -# end of SPIFFS Cache Configuration - -# default: -CONFIG_SPIFFS_PAGE_CHECK=y -# default: -CONFIG_SPIFFS_GC_MAX_RUNS=10 -# default: -# CONFIG_SPIFFS_GC_STATS is not set -# default: -CONFIG_SPIFFS_PAGE_SIZE=256 -# default: -CONFIG_SPIFFS_OBJ_NAME_LEN=32 -# default: -# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set -# default: -CONFIG_SPIFFS_USE_MAGIC=y -# default: -CONFIG_SPIFFS_USE_MAGIC_LENGTH=y -# default: -CONFIG_SPIFFS_META_LENGTH=4 -# default: -CONFIG_SPIFFS_USE_MTIME=y - -# -# Debug Configuration -# -# default: -# CONFIG_SPIFFS_DBG is not set -# default: -# CONFIG_SPIFFS_API_DBG is not set -# default: -# CONFIG_SPIFFS_GC_DBG is not set -# default: -# CONFIG_SPIFFS_CACHE_DBG is not set -# default: -# CONFIG_SPIFFS_CHECK_DBG is not set -# default: -# CONFIG_SPIFFS_TEST_VISUALISATION is not set -# end of Debug Configuration -# end of SPIFFS Configuration - -# -# TCP Transport -# - -# -# Websocket -# -# default: -CONFIG_WS_TRANSPORT=y -# default: -CONFIG_WS_BUFFER_SIZE=1024 -# default: -# CONFIG_WS_DYNAMIC_BUFFER is not set -# end of Websocket -# end of TCP Transport - -# -# Ultra Low Power (ULP) Co-processor -# -# default: -# CONFIG_ULP_COPROC_ENABLED is not set - -# -# ULP Debugging Options -# -# end of ULP Debugging Options -# end of Ultra Low Power (ULP) Co-processor - -# -# Unity unit testing library -# -# default: -CONFIG_UNITY_ENABLE_FLOAT=y -# default: -CONFIG_UNITY_ENABLE_DOUBLE=y -# default: -# CONFIG_UNITY_ENABLE_64BIT is not set -# default: -# CONFIG_UNITY_ENABLE_COLOR is not set -# default: -CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y -# default: -# CONFIG_UNITY_ENABLE_FIXTURE is not set -# default: -# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set -# end of Unity unit testing library - # # Virtual file system # @@ -3960,6 +3743,8 @@ CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y # CONFIG_VFS_SUPPORT_TERMIOS is not set # default: CONFIG_VFS_MAX_COUNT=8 +# default: +# CONFIG_VFS_SUPPRESS_CTX_DEPRECATION is not set # # Host File System I/O (Semihosting) @@ -3973,15 +3758,60 @@ CONFIG_VFS_INITIALIZE_DEV_NULL=y # end of Virtual file system # -# Wear Levelling +# Vanetza-IDF +# +# CONFIG_VANETZA_IDF_PROFILE_ACCESS is not set +CONFIG_VANETZA_IDF_PROFILE_NETWORK=y +# CONFIG_VANETZA_IDF_PROFILE_FACILITIES is not set +# default: +CONFIG_VANETZA_IDF_NETWORK=y +# default: +# CONFIG_VANETZA_IDF_CAM is not set +# default: +# CONFIG_VANETZA_IDF_DENM is not set +# default: +# CONFIG_VANETZA_IDF_VAM is not set +CONFIG_VANETZA_IDF_SECURITY=y +CONFIG_VANETZA_IDF_SECURITY_VERIFY=y +CONFIG_VANETZA_IDF_NVS_CREDENTIALS=y +# CONFIG_VANETZA_IDF_PKI is not set +# CONFIG_VANETZA_IDF_HIL is not set +# CONFIG_VANETZA_IDF_RADIO_EXTERNAL is not set +CONFIG_VANETZA_IDF_RADIO_C5=y +# end of Vanetza-IDF + +# +# ESP Boost Configuration # # default: -# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_BOOST_CXX_FEATURE_ENABLED=y # default: -CONFIG_WL_SECTOR_SIZE_4096=y +# CONFIG_BOOST_ASIO_ENABLED is not set # default: -CONFIG_WL_SECTOR_SIZE=4096 -# end of Wear Levelling +CONFIG_BOOST_CHRONO_ENABLED=y +# default: +# CONFIG_BOOST_CONTAINER_ENABLED is not set +# default: +# CONFIG_BOOST_EXCEPTION_ENABLED is not set +# default: +# CONFIG_BOOST_GRAPH_ENABLED is not set +# default: +# CONFIG_BOOST_JSON_ENABLED is not set +# default: +# CONFIG_BOOST_MATH_ENABLED is not set +# default: +# CONFIG_BOOST_RANDOM_ENABLED is not set +# default: +# CONFIG_BOOST_REGEX_ENABLED is not set +# default: +# CONFIG_BOOST_SERIALIZATION_ENABLED is not set +# default: +CONFIG_BOOST_SYSTEM_ENABLED=y +# default: +# CONFIG_BOOST_TEST_ENABLED is not set +# default: +CONFIG_BOOST_THREAD_ENABLED=y +# end of ESP Boost Configuration # end of Component config # default: @@ -4005,35 +3835,68 @@ CONFIG_LOG_BOOTLOADER_LEVEL=3 CONFIG_FLASHMODE_DIO=y # CONFIG_FLASHMODE_DOUT is not set CONFIG_MONITOR_BAUD=115200 -CONFIG_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y -# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set -# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +CONFIG_OPTIMIZATION_LEVEL_RELEASE=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y # CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_CXX_EXCEPTIONS=y +CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 CONFIG_STACK_CHECK_NONE=y # CONFIG_STACK_CHECK_NORM is not set # CONFIG_STACK_CHECK_STRONG is not set # CONFIG_STACK_CHECK_ALL is not set # CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_BLUEDROID_ENABLED is not set +CONFIG_NIMBLE_ENABLED=y +CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +CONFIG_NIMBLE_PINNED_TO_CORE=0 +CONFIG_NIMBLE_TASK_STACK_SIZE=4096 +CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096 +# CONFIG_NIMBLE_ROLE_CENTRAL is not set +CONFIG_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_NIMBLE_ROLE_BROADCASTER=y +# CONFIG_NIMBLE_ROLE_OBSERVER is not set +CONFIG_NIMBLE_SM_LEGACY=y +CONFIG_NIMBLE_SM_SC=y +# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_BT_NIMBLE_SM_SC_LVL=0 +CONFIG_NIMBLE_NVS_PERSIST=y +CONFIG_NIMBLE_MAX_BONDS=1 +CONFIG_NIMBLE_RPA_TIMEOUT=900 +CONFIG_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_NIMBLE_MAX_CCCDS=8 +# CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS is not set +CONFIG_NIMBLE_ATT_PREFERRED_MTU=517 +CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0 +CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24 +CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255 +CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30 +CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8 +CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0x0 +# CONFIG_NIMBLE_MESH is not set +# CONFIG_NIMBLE_DEBUG is not set +# CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y CONFIG_SW_COEXIST_ENABLE=y CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y # CONFIG_EXTERNAL_COEX_ENABLE is not set # CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set -# CONFIG_ANA_CMPR_ISR_IRAM_SAFE is not set -# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set -# CONFIG_MCPWM_ISR_IRAM_SAFE is not set # CONFIG_EVENT_LOOP_PROFILING is not set CONFIG_POST_EVENTS_FROM_ISR=y CONFIG_POST_EVENTS_FROM_IRAM_ISR=y CONFIG_GDBSTUB_SUPPORT_TASKS=y CONFIG_GDBSTUB_MAX_TASKS=32 -# CONFIG_OTA_ALLOW_HTTP is not set # CONFIG_ESP_SYSTEM_PD_FLASH is not set CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y CONFIG_BROWNOUT_DET=y @@ -4075,7 +3938,7 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y # CONFIG_ESP_SYSTEM_PMP_LP_CORE_RESERVE_MEM_EXECUTABLE is not set CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -CONFIG_MAIN_TASK_STACK_SIZE=3584 +CONFIG_MAIN_TASK_STACK_SIZE=8192 CONFIG_INT_WDT=y CONFIG_INT_WDT_TIMEOUT_MS=300 CONFIG_TASK_WDT=y @@ -4086,14 +3949,13 @@ CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y # CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set CONFIG_IPC_TASK_STACK_SIZE=1024 CONFIG_TIMER_TASK_STACK_SIZE=3584 -# CONFIG_ESP32_APPTRACE_ENABLE is not set CONFIG_ESP32_WIFI_ENABLED=y -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=6 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=16 # CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=16 # CONFIG_ESP32_WIFI_CSI_ENABLED is not set CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y CONFIG_ESP32_WIFI_TX_BA_WIN=6 @@ -4101,7 +3963,7 @@ CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y CONFIG_ESP32_WIFI_RX_BA_WIN=6 CONFIG_ESP32_WIFI_NVS_ENABLED=y CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 -CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=16 CONFIG_ESP32_WIFI_IRAM_OPT=y CONFIG_ESP32_WIFI_RX_IRAM_OPT=y CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y @@ -4118,37 +3980,6 @@ CONFIG_WPA_MBEDTLS_TLS_CLIENT=y # CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set # CONFIG_WPA_WPS_STRICT is not set # CONFIG_WPA_DEBUG_PRINT is not set -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 -CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 -# CONFIG_ESP32_WIFI_CSI_ENABLED is not set -CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y -CONFIG_ESP32_WIFI_TX_BA_WIN=6 -CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP32_WIFI_RX_BA_WIN=6 -CONFIG_ESP32_WIFI_NVS_ENABLED=y -CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 -CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 -CONFIG_ESP32_WIFI_IRAM_OPT=y -CONFIG_ESP32_WIFI_RX_IRAM_OPT=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y -# CONFIG_ESP_WIFI_NAN_ENABLE is not set -CONFIG_WPA_MBEDTLS_CRYPTO=y -CONFIG_WPA_MBEDTLS_TLS_CLIENT=y -# CONFIG_WPA_WAPI_PSK is not set -# CONFIG_WPA_SUITE_B_192 is not set -# CONFIG_WPA_11KV_SUPPORT is not set -# CONFIG_WPA_MBO_SUPPORT is not set -# CONFIG_WPA_DPP_SUPPORT is not set -# CONFIG_WPA_11R_SUPPORT is not set -# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set -# CONFIG_WPA_WPS_STRICT is not set -# CONFIG_WPA_DEBUG_PRINT is not set -# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set -# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set -CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y CONFIG_TIMER_TASK_PRIORITY=1 CONFIG_TIMER_TASK_STACK_DEPTH=2048 CONFIG_TIMER_QUEUE_LENGTH=10 diff --git a/obu-firmware/sdkconfig.defaults b/obu-firmware/sdkconfig.defaults index 164b924..caf8c67 100644 --- a/obu-firmware/sdkconfig.defaults +++ b/obu-firmware/sdkconfig.defaults @@ -1 +1,54 @@ +# obu-firmware defaults (ESP32-C5, ESP-IDF 6.0.2). Based on microbu-esp32c5/firmware/sdkconfig.defaults; +# the differences for this board are marked "MicrOBU:". CONFIG_IDF_TARGET="esp32c5" +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_RTTI=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 +# MicrOBU: the production board has 16 MB of flash (esptool reports it); the partition table +# only uses the first 4 MB. +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +# Network profile: BTP + GeoNetworking, no facilities codecs (the phone builds CAM/VAM) +CONFIG_VANETZA_IDF_PROFILE_NETWORK=y +CONFIG_VANETZA_IDF_SECURITY=y +CONFIG_VANETZA_IDF_SECURITY_VERIFY=y +CONFIG_VANETZA_IDF_NVS_CREDENTIALS=y +CONFIG_VANETZA_IDF_PKI=n +CONFIG_VANETZA_IDF_HIL=n +CONFIG_VANETZA_IDF_RADIO_C5=y +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=6 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=16 +# MicrOBU: the console and ESP_LOG stay on UART0, i.e. the CH343 bridge port (COM3 on the bench), +# exactly as in the previous firmware. The native USB Serial/JTAG port belongs to the phone and +# carries only station-link frames. The colleague's board has only the native port and routes the +# console there instead. +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_ESP_PANIC_HANDLER_IRAM=y +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_LOG_COLORS=n +# MicrOBU: 20 dBm ceiling, as the previous firmware (the phone configures 20). +CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 +# Station-internal BLE GATT link: NimBLE, maximum ATT value, persistent bonds. +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=517 +CONFIG_BT_NIMBLE_SM_SC=y +CONFIG_BT_NIMBLE_NVS_PERSIST=y +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MAX_BONDS=1 +CONFIG_BT_NIMBLE_MAX_CCCDS=8 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n +# Hardware ECDSA/ECC/SHA +CONFIG_MBEDTLS_HARDWARE_ECC=y +CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY=y +CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_HARDWARE_AES=n