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).
75 lines
2.9 KiB
Python
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
|