Files
MicrOBU/microbu-esp32c5/tools/v2x2map-0.3.0/bridge/vam.py
T
Ashin Walpola 0e9525162d 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).
2026-09-23 17:46:40 +02:00

75 lines
2.9 KiB
Python

"""VAM v3 ASN.1 UPER decoding (ETSI TS 103 300-3 V2.3.1 / CDD V2.4.1).
Pinned schemas and notices are bundled in asn1/. See ../docs/vam-support.md.
"""
from functools import lru_cache
from pathlib import Path
@lru_cache(maxsize=1)
def codec():
try:
import asn1tools
except ImportError as exc:
raise ValueError("VAM decoder unavailable: install bridge/requirements.txt") from exc
directory = Path(__file__).resolve().parent / "asn1"
return asn1tools.compile_files([
str(directory / "TS102894-2v241-CDD.asn"),
str(directory / "VAM-PDU-Descriptions.asn"),
], codec="uper")
def json_value(value):
"""Preserve ASN.1 CHOICE/bit-string values in JSON-safe form."""
if isinstance(value, bytes):
return value.hex()
if isinstance(value, dict):
return {k: json_value(v) for k, v in value.items()}
if isinstance(value, (tuple, list)):
return [json_value(v) for v in value]
return value
def decode_vam(data):
if not data or len(data) > 4096:
raise ValueError("empty/oversized VAM")
decoder = codec()
import asn1tools
try:
message = decoder.decode("VAM", data, check_constraints=True)
# Detect trailing bytes, nonzero padding and unknown extensions that the
# library may skip. Do not silently call partially understood data valid.
if decoder.encode("VAM", message, check_constraints=True) != data:
raise ValueError("noncanonical VAM or unsupported extension/trailing data")
except (asn1tools.DecodeError, asn1tools.EncodeError, asn1tools.ConstraintsError) as exc:
raise ValueError("invalid VAM UPER: " + str(exc)) from exc
header = message["header"]
if header["protocolVersion"] != 3 or header["messageId"] != 16:
raise ValueError("unsupported VAM protocol version/message ID")
body = message["vam"]
containers = body["vamParameters"]
basic = containers["basicContainer"]
pos = basic["referencePosition"]
hf = containers["vruHighFrequencyContainer"]
result = dict(station_id=header["stationId"],
generation_delta_time=body["generationDeltaTime"],
station_type=basic["stationType"],
containers=json_value(containers))
lat, lon = pos["latitude"], pos["longitude"]
if lat != 900000001 and lon != 1800000001:
result.update(lat=lat / 1e7, lon=lon / 1e7)
altitude = pos["altitude"]["altitudeValue"]
if altitude != 800001:
result["altitude_m"] = altitude / 100
heading = hf["heading"]["value"]
if heading != 3601:
result["heading_deg"] = heading / 10
speed = hf["speed"]["speedValue"]
if speed != 16383:
result["speed_mps"] = speed / 100
acceleration = hf["longitudinalAcceleration"]["longitudinalAccelerationValue"]
if acceleration != 161:
result["longitudinal_acceleration_mps2"] = acceleration / 10
return result