2026-08-11 14:50:35 +02:00
|
|
|
# OBU transmit firmware - Phase 2 (in progress: HLN-SV DENM beacon)
|
|
|
|
|
|
2026-09-16 14:21:44 +02:00
|
|
|
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.)
|
2026-08-11 14:50:35 +02:00
|
|
|
|
|
|
|
|
## 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.
|
|
|
|
|
|
2026-09-16 14:21:44 +02:00
|
|
|
## 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.
|
|
|
|
|
|
2026-08-11 14:50:35 +02:00
|
|
|
## 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
|
2026-09-16 14:21:44 +02:00
|
|
|
- `main/route.c` / `.h` - simulated drive round the route loop
|
|
|
|
|
- `main/route_points.h` - the route, generated by `tools/make_route.py`
|
2026-08-11 14:50:35 +02:00
|
|
|
- `main/geonet.c` / `.h` - GeoNetworking Basic/Common/SHB headers + BTP-B
|
|
|
|
|
- `main/dot11p.c` / `.h` - 802.11 OCB (QoS Data, broadcast) frame + LLC/SNAP
|
|
|
|
|
|
2026-09-16 14:21:44 +02:00
|
|
|
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
|
2026-08-11 14:50:35 +02:00
|
|
|
2004-01-01), fixed (non-rotating) pseudonym MAC, SHB instead of GeoBroadcast
|
|
|
|
|
(no multi-hop forwarding), unsecured (no IEEE 1609.2 signing).
|