# OBU transmit firmware - Phase 2 (in progress: HLN-SV DENM beacon) Build and flash steps are under "Build and flash" below. (`docs/04-transmit-setup.md`, referenced here and in the sources, is not in the repo.) ## Toolchain: use a dedicated terminal (ESP-IDF 5.5.4) This project builds against the **global** ESP-IDF 5.5.4, NOT the 6.1 checkout that `obu-firmware` uses. Keep one terminal per toolchain and never export both in the same window - the second export inherits the first's `IDF_PYTHON_ENV_PATH` and then fails every dependency check (`click`, `esptool`, `cryptography`, ... "not met"). That is env-var bleed, not a broken install: do **not** run `install.bat` to "fix" it, that damages one of the two environments. | Terminal | Export | Project | |---|---|---| | Transmitter | `C:\Espressif\frameworks\esp-idf-v5.5.4\export.ps1` | this one | | OBU | `...\micrOBU_workspace\its-g5-receiver-firmware\esp-idf\export.ps1` | `obu-firmware` | If a terminal has already been used for the other IDF, clear the state first: ```powershell $env:IDF_PYTHON_ENV_PATH = $null; $env:IDF_PATH = $null ``` Also note `build/` here was regenerated from scratch (its CMake cache still referenced an older source path under `micrOBU_workspace/v2x-obu-esp32c5/`, which makes `idf.py fullclean` refuse to run). If that error reappears, delete `build/` manually rather than fighting it. ## Build and flash The firmware needs no button, phone or serial connection to start. On every power-up or reset, `app_main` sets up the radio and starts `tx_task`, which starts driving the simulated route and sending CAMs on 5900 MHz. A board flashed with this image starts beaconing on its own as soon as it gets power. In a fresh PowerShell window: ```powershell $env:IDF_PYTHON_ENV_PATH = $null; $env:IDF_PATH = $null . C:\Espressif\frameworks\esp-idf-v5.5.4\export.ps1 cd C:\Users\Ashin\AndroidStudioProjects\MicrOBU\obu-cam-transmistter ``` 1. **Check what the board is running before you flash it.** Both this project and `obu-firmware` name their image `obu_firmware.bin`, so the file name tells you nothing. Read the app descriptor instead (replace `COMx` with the board's port): ```powershell python -m esptool --chip esp32c5 -p COMx read_flash 0x10000 0x100 $env:TEMP\desc.bin $b = [IO.File]::ReadAllBytes("$env:TEMP\desc.bin") function S($o,$n){ [Text.Encoding]::ASCII.GetString($b,$o,$n).Trim([char]0) } "time=" + (S 0x70 16) + " date=" + (S 0x80 16) + " idf=" + (S 0x90 32) ``` `idf=v6.1...` means the board runs the production OBU (`obu-firmware`). Don't flash this beacon over it. `idf=v5.5.4` means this transmitter, or another bench image. 2. **Build:** ```powershell idf.py build ``` A rebuild after small changes takes about 1-2 minutes. The output is `build\obu_firmware.bin`. 3. **Flash** (the board must be on its UART bridge port or its native USB port): ```powershell idf.py -p COMx flash ``` The flash is good when esptool prints `Hash of data verified.`, then resets the board. 4. **Check that it's transmitting:** ```powershell idf.py -p COMx monitor ``` (Exit with `Ctrl+]`.) About 1.4 s after reset you should see: ``` W obu-tx: OCB @ 5900 MHz - CAM beacon armed, driving a 103-point street loop I obu-tx: CAM sent (119 bytes) @ 5900 MHz genDeltaT=1087 pos=53.5531770,10.0220980 50.0 km/h heading 77.0 pt1 ``` After that, a `CAM sent` line appears about 3 times a second, with the position, speed and heading changing. These lines only mean each frame was handed to the radio. To confirm the frames actually went out, capture them with a second ESP32-C5 running the receiver firmware. Don't open the console of the production OBU (COM3) while the phone is attached. Opening the port resets that board and drops the phone's USB link. The beacon boards have no phone attached, so this doesn't apply to them. ## Simulated drive The beacon pretends to be a car driving a loop through St. Georg / Berliner Tor in Hamburg, on the real streets. The route comes from six waypoints, which you set in `tools/make_route.py`. **Changing the route:** edit `WAYPOINTS` in `tools/make_route.py`, then run ```powershell py -3.11 tools/make_route.py ``` The script asks the OSRM demo server (router.project-osrm.org) for a legal driving route through the waypoints in order and back to the first, then writes three files: - `main/route_points.h`: the street geometry, thinned to points no more than 1.5 m off the line (currently 103 points). - `tools/route_osrm.json`: OSRM's raw answer. `--offline` rebuilds the header from it without the network. - `tools/route_map.html`: the route on an OpenStreetMap map. Open it in a browser and check it before building. Then build and flash as above. Route data (c) OpenStreetMap contributors, ODbL; routing by OSRM. **Things to know about the routing:** - OSRM follows one-way streets and turn bans, so the loop can be longer than the waypoints suggest. The current one is 5.2 km, with two turn-round detours: a loop via Borgfelder Straße and Anckelmannsplatz between wp2 and wp3, and one round Nagelsweg, Norderstraße and Repsoldstraße between wp5 and wp6. To avoid a detour, move the waypoint on either side of it. - Each waypoint is sent with the direction towards the next one. Without it, points on divided roads (Beim Strohhause, for example) snap to the carriageway going the other way, and the loop grows to 8.4 km of U-turns. **How the car drives** (`main/route.c`): - **Speed:** it cruises at 50 km/h (`CRUISE_MPS` in `main/main.c`). Each bend gets a speed limit from its radius, keeping sideways acceleration at 2 m/s², so a 90° junction turn is taken at about 15 km/h and a gentle curve barely slows the car. It never drops below 10 km/h (`MIN_CORNER_MPS`). It brakes at 2 m/s² and accelerates at 1.5 m/s², planning braking across as many points as a bend needs. A lap takes about 7.7 min, averaging 40 km/h. - **Heading:** the compass bearing of the current straight piece. It changes gradually through curves but jumps at sharp junction turns. - **When CAMs are sent:** following ETSI EN 302 637-2, the state is checked every 100 ms. A CAM goes out when the heading changed by more than 4°, the position by more than 4 m, or the speed by more than 0.5 m/s since the last one, and at least once a second. That's about 3 CAMs a second at 50 km/h. - **What's filled in:** position, speed and heading go into both the CAM and the GeoNetworking source position vector. `genDeltaT` is milliseconds since boot. - **Testing:** `route.c` only uses standard headers, so you can compile it on the PC with MSYS2 gcc and simulate a lap. ## CAM encoding `main/cam.c` IS compiled here (unlike `obu-firmware`'s copy, which is a reference only). It must stay bit-identical to `obu-firmware/main/cam.c` and the app's `CamUperCodec.kt` - all three encode the same wire format, and a one-bit divergence in any of them is invisible on the bench but wrong against real equipment. See the `CurvatureCalculationMode` comment in that file. Implements one profile so far: **HLN-SV** (aftermarket stationary recovery vehicle), causeCode 94 (stationaryVehicle), subCauseCode 0, active while the hazard-light GPIO is grounded. No location/alacarte containers. - `main/main.c` - entry point, the `phy_11p_set`/`phy_change_channel(5900,...)` register hack, GPIO polling, TX loop - `main/denm.c` / `.h` - ASN.1 UPER encoding of a minimal DENM - `main/route.c` / `.h` - simulated drive round the route loop - `main/route_points.h` - the route, generated by `tools/make_route.py` - `main/geonet.c` / `.h` - GeoNetworking Basic/Common/SHB headers + BTP-B - `main/dot11p.c` / `.h` - 802.11 OCB (QoS Data, broadcast) frame + LLC/SNAP Known gaps, tracked as TODOs in the source: no real GNSS (the CAM position comes from the simulated drive above), no real time source (detectionTime/referenceTime hardcoded 0, decodes as 2004-01-01), fixed (non-rotating) pseudonym MAC, SHB instead of GeoBroadcast (no multi-hop forwarding), unsecured (no IEEE 1609.2 signing).