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
|