Keep the colleague's microbu-esp32c5 tree in this repository

obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but
that tree was gitignored, so a clone of this repository could not build the
firmware it ships. It is now committed here as ordinary files in its own
folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP
bridge's signature verification (--trust) used on the bench. Nothing is
fetched from or pushed to the colleague's repository; this repository and
its remotes carry everything. The folder's own .gitignore keeps build output,
downloaded components and private key material out, as it did there; the
committed file set is identical to that repository's tracked files.

The ESP32-C5 is still flashed from obu-firmware/, which only takes
vanetza-idf from microbu-esp32c5/, so the two stay separate folders.
FLASHING.md says how to take a newer version of the colleague's tree (copy
it over the folder, rebuild, test, commit).
This commit is contained in:
Ashin Walpola
2026-09-23 17:46:40 +02:00
parent 2f60623e18
commit 0e9525162d
9881 changed files with 1582523 additions and 17 deletions
@@ -0,0 +1,46 @@
// TEST-V2X2MAP-VAM-002: exercise the dashboard's actual rendering functions.
const fs = require('node:fs');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const html = fs.readFileSync(require('node:path').join(__dirname, '../dashboard.html'), 'utf8');
const script = html.split('<script>')[1].split('</script>')[0];
new vm.Script(script); // Syntax-check the entire dashboard, not only VAM code.
const slice = (a, b) => script.slice(script.indexOf(a), script.indexOf(b));
const context = vm.createContext({assert, console});
vm.runInContext(slice('const MSG =', '// ─── Map') +
slice('const vehicles ', 'setInterval(()=>{') +
slice('const filterActive=', 'document.querySelectorAll(\'.fchip\')') +
slice('function buildSummary(', 'function etCls('), context);
vm.runInContext(`
const map = {setView() {}};
const L = {
divIcon: x => x,
marker: () => ({addTo() {return this}, bindPopup() {}, on() {},
setLatLng() {}, setIcon() {}, isPopupOpen: () => false})
};
const frame = {msg_type:'VAM', station_id:'1234', lat:53.9, lon:10,
speed_mps:5, heading_deg:90, station_type_label:'cyclist',
vam:{station_id:42, station_type:2, generation_delta_time:65535,
speed_mps:5, altitude_m:12.3, longitudinal_acceleration_mps2:-0.5,
containers:{test:'<img src=x onerror=alert(1)>'}}};
onPositionFrame(frame);
onPositionFrame({...frame, lat:54});
assert.equal(vehicles.size, 1);
const marker = [...vehicles.values()][0];
assert.equal(marker.count, 2);
const popup = popupHtml(marker);
for (const text of ['Vulnerable road user (VAM)', '65535', '18.0 km/h', '12.3 m', '-0.5 m/s²', 'VAM containers'])
assert.ok(popup.includes(text), text);
assert.ok(!popup.includes('<img'));
assert.ok(popup.includes('&lt;img'));
const noValues = popupHtml({lastFrame:{...frame, vam:{station_id:0,station_type:2,generation_delta_time:0}},count:1,lastMs:0});
assert.ok(!noValues.includes('undefined'));
assert.ok(!noValues.includes('NaN'));
assert.ok(isVisible('VAM'));
filterActive.delete('VAM');
assert.ok(!isVisible('VAM'));
assert.ok(isVisible('CAM'));
assert.ok(buildSummary({lastFrame:frame,msgType:'VAM',frameCount:1}).includes('65535'));
assert.ok(buildSummary({lastFrame:{vam_status:'invalid VAM'},msgType:'VAM',frameCount:1}).includes('invalid VAM'));
console.log('PASS: VAM map updates, popups, unavailable fields, escaping, filters and log');
`, context);
@@ -0,0 +1,46 @@
[
{
"fields": [
1,
0,
0,
0,
0,
0
],
"uper": "03100000000100000009ad2748035a4e9007ffffff08eddd0f8000007e0003f50730"
},
{
"fields": [
4294967295,
539000000,
100000000,
65535,
500,
900
],
"uper": "0310ffffffffffff000aae2b2e0389fd9807ffffff08eddd0f8001c27e07d3f50730"
},
{
"fields": [
42,
-900000000,
-1800000000,
1,
16383,
3601
],
"uper": "03100000002a000100080000000000000007ffffff08eddd0f800708fefffff50730"
},
{
"fields": [
9,
900000000,
1800000000,
32768,
1,
3599
],
"uper": "0310000000098000000b5a4e9006b49d2007ffffff08eddd0f800707fe0007f50730"
}
]
@@ -0,0 +1,75 @@
2026-09-09 host verification
COMMAND: C:\bachelorthesis_micrOBU\.venv\Scripts\python.exe -W ignore::DeprecationWarning -m unittest discover -s C:\bachelorthesis_micrOBU\implementation\v2x2map-0.3.0\bridge -p test_vam.py -v
test_all_truncations_and_trailing_data (test_vam.VamTests.test_all_truncations_and_trailing_data) ... ok
test_independent_asn1c_vectors (test_vam.VamTests.test_independent_asn1c_vectors) ... ok
test_malformed_never_falls_back_to_gn_position (test_vam.VamTests.test_malformed_never_falls_back_to_gn_position) ... ok
test_optional_containers_and_json (test_vam.VamTests.test_optional_containers_and_json) ... ok
test_secured_header_is_not_guessed (test_vam.VamTests.test_secured_header_is_not_guessed) ... ok
test_transport_and_map_fields (test_vam.VamTests.test_transport_and_map_fields) ... ok
test_unavailable_coordinates_and_real_zero (test_vam.VamTests.test_unavailable_coordinates_and_real_zero) ... ok
----------------------------------------------------------------------
Ran 7 tests in 0.709s
OK
COMMAND: node C:\bachelorthesis_micrOBU\implementation\v2x2map-0.3.0\bridge\tests\dashboard-vam.cjs
PASS: VAM map updates, popups, unavailable fields, escaping, filters and log
COMMAND: C:\bachelorthesis_micrOBU\.venv\Scripts\python.exe C:\bachelorthesis_micrOBU\implementation\v2x2map-0.3.0\bridge\its_g5_bridge.py --help
usage: its_g5_bridge.py [-h] [--port PORT] [--list-ports] [--baud BAUD]
[--node-id NODE_ID] [--broker URL] [--no-mqtt]
[--reset-on-start] [--reconnect-delay RECONNECT_DELAY]
[--exit-after EXIT_AFTER]
[--dashboard-port DASHBOARD_PORT] [--open-browser]
[--no-browser] [--pcap-out PCAP_OUT] [--verbose]
ITS-G5 USB->MQTT bridge (V2X2MAP)
Config: C:\bachelorthesis_micrOBU\implementation\v2x2map-0.3.0\bridge\config\v2x2map.cfg
options:
-h, --help show this help message and exit
--port PORT serial port (e.g. COM7); omit to auto-detect or use
config
--list-ports print available serial ports with VID/PID and exit
--baud BAUD
--node-id NODE_ID device ID, e.g. V2X2MAP:d2cf13ed6293 (read from config
if omitted)
--broker URL MQTT broker URL (mqtts://host:port or
mqtt://host:port). Can be specified multiple times.
Default from config or
mqtts://cits1.opentrafficmap.org:8883
--no-mqtt parse only, don't publish (for testing)
--reset-on-start pulse RTS to reboot the device on first connect
--reconnect-delay RECONNECT_DELAY
seconds to wait before reconnecting after USB
disconnect (0 = exit)
--exit-after EXIT_AFTER
exit after N seconds (0 = run forever)
--dashboard-port DASHBOARD_PORT
local HTTP port for the live dashboard (0 to disable)
--open-browser open the dashboard URL in the default browser on start
(default: on when running as .exe)
--no-browser suppress automatic browser opening
--pcap-out PCAP_OUT write all captured frames to a .pcap file
(DLT_IEEE802_11)
--verbose, -v
PASS: Python/spec syntax, bridge CLI, Debian archive includes decoder, schemas, requirements and notices. Debian install and Windows EXE build not run.
2026-09-14 host verification (signed VAMs)
Change: bridge/secured.py unwraps TS 103 097 signed GeoNetworking packets (no signature
verification); its_g5_bridge.decode_itsg5_full continues on the unsecured payload and reports
security = {psid, hash, signer, signer_id, generation_time_ms, verified: false}.
COMMAND: C:\bachelorthesis_micrOBU\.venv\Scripts\python.exe -W ignore::DeprecationWarning -m unittest -v test_vam
Ran 8 tests ... OK (new: test_signed_vam_is_unwrapped_but_not_verified, vector = frame 1 of
recordings/its5_20260914_202525.pcap, signer HashedId8 ba1862d0e89fe341 = the micrOBU's ticket)
COMMAND: node tests/dashboard-vam.cjs
PASS
Live: bridge on COM20 (V2X2MAP receiver firmware) with the micrOBU transmitting signed VAMs on
COM11: /events carries msg_type VAM, vam_status decoded, security.signer certificate/digest.