From fcf4d75c765ad2f4ebc6be9510503652f635249e Mon Sep 17 00:00:00 2001 From: JamesL787 Date: Tue, 25 Aug 2026 18:09:20 -0400 Subject: [PATCH] honda: port Bosch-A 16-slot radar decoder, enable for full HONDA_BOSCH_A harness Ports the 16-slot Bosch-A object-bank radar decoder (16 slots x 4 main frames + 1 aux frame each) and enables it behind a HondaBoschARadar toggle (default on) for the full HONDA_BOSCH_A harness category: N-Box, Accord, Civic (incl. Diesel), CR-V, CR-V Hybrid, Acura RDX, Insight, and Honda e -- 10 platforms total, derived as HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR. RX-only: never transmits on the radar bus, so it takes no CAN authority. Factory AEB/CMBS/FCW stay live as long as stock longitudinal remains in control; enabling openpilot longitudinal (alpha) disables them regardless of this toggle, via the existing UDS CommunicationControl call in CarInterface.init() -- documented consistently now via a new HondaCarDocs Footnote.EXP_LONG and matching comments on all 10 platform configs. 86 new tests for the decoder (gate-open/closed coverage for every affected platform) plus the existing 19 in test_honda.py all pass. Co-Authored-By: Claude Sonnet 5 (cherry picked from commit b13dd7d75e29b79293fb5f23fd2255769c59d482) --- common/params_keys.h | 1 + opendbc_repo/opendbc/car/honda/interface.py | 13 +- .../opendbc/car/honda/radar_interface.py | 599 ++++++++- .../car/honda/tests/test_bosch_a_radar.py | 1132 +++++++++++++++++ opendbc_repo/opendbc/car/honda/values.py | 80 +- .../opendbc/dbc/honda_bosch_a_radar.dbc | 1014 +++++++++++++++ .../common/assets/device_settings_layout.json | 9 + 7 files changed, 2827 insertions(+), 21 deletions(-) create mode 100644 opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py create mode 100644 opendbc_repo/opendbc/dbc/honda_bosch_a_radar.dbc diff --git a/common/params_keys.h b/common/params_keys.h index 0dcc3d5f6..a87f4de9c 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -62,6 +62,7 @@ inline static std::unordered_map keys = { {"GsmRoaming", {PERSISTENT, BOOL}}, {"HardwareSerial", {PERSISTENT, STRING}}, {"HasAcceptedTerms", {PERSISTENT, STRING, "0"}}, + {"HondaBoschARadar", {PERSISTENT, BOOL, "1"}}, {"HondaGasFactorParams", {PERSISTENT, FLOAT}}, {"HondaLateralPidKiScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}}, {"HondaLateralPidKpScale", {PERSISTENT, FLOAT, "1.0", "1.0", 3}}, diff --git a/opendbc_repo/opendbc/car/honda/interface.py b/opendbc_repo/opendbc/car/honda/interface.py index b9ee522d2..b774459e4 100644 --- a/opendbc_repo/opendbc/car/honda/interface.py +++ b/opendbc_repo/opendbc/car/honda/interface.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 import numpy as np +from openpilot.common.params import Params, UnknownKeyName from opendbc.car import get_safety_config, structs, uds from opendbc.car.common.conversions import Conversions as CV from opendbc.car.disable_ecu import disable_ecu from opendbc.car.honda.hondacan import CanBus -from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_CANFD, \ +from opendbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_A, HONDA_BOSCH_CANFD, \ HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, HondaSafetyFlags from opendbc.car.honda.carcontroller import CarController from opendbc.car.honda.carstate import CarState @@ -44,7 +45,15 @@ class CarInterface(CarInterfaceBase): cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput)) ret.safetyConfigs = cfgs - ret.radarUnavailable = True + # HONDA_BOSCH_A platforms (plain bosch_a harness: not CANFD, not radarless, not alt-radar) have + # a firmware-correct RadarInterface (16-slot Bosch-A object bank, RX-only) and use it by + # default via HondaBoschARadar: every other Bosch platform still has no parsed radar DBC and + # keeps radarUnavailable=True regardless. + try: + bosch_a_radar_tryout = not docs and Params().get_bool("HondaBoschARadar") + except UnknownKeyName: + bosch_a_radar_tryout = False + ret.radarUnavailable = not (candidate in HONDA_BOSCH_A and bosch_a_radar_tryout) # Disable the radar and let openpilot control longitudinal # WARNING: THIS DISABLES AEB! # If Bosch radarless, this blocks ACC messages from the camera diff --git a/opendbc_repo/opendbc/car/honda/radar_interface.py b/opendbc_repo/opendbc/car/honda/radar_interface.py index 34a743d19..bf28d7247 100755 --- a/opendbc_repo/opendbc/car/honda/radar_interface.py +++ b/opendbc_repo/opendbc/car/honda/radar_interface.py @@ -1,8 +1,13 @@ #!/usr/bin/env python3 +import math +from collections import deque +from dataclasses import dataclass, field + from opendbc.can import CANParser from opendbc.car import Bus, structs -from opendbc.car.interfaces import RadarInterfaceBase +from opendbc.car.honda.hondacan import CanBus from opendbc.car.honda.values import DBC +from opendbc.car.interfaces import RadarInterfaceBase def _create_nidec_can_parser(car_fingerprint): @@ -11,39 +16,615 @@ def _create_nidec_can_parser(car_fingerprint): return CANParser(DBC[car_fingerprint][Bus.radar], messages, 1) +# ============================================================================ +# Honda Bosch-A 16-slot object bank +# ============================================================================ +# 16 CAN-visible object slots. Each slot has 4 main frames (f0..f3, one CAN ID each -- NOT sub-frames +# muxed onto a shared ID) plus one synchronized auxiliary 5th frame. This supersedes any prior model of +# 0x280/0x284/0x288/0x28C as pieces of one object, or of 0x2C8/0x2C9 as a separate "coarse" list: it is +# ONE 16-slot bank with one auxiliary frame per slot. See honda_bosch_a_radar.dbc for the bit geometry. +BOSCH_A_DBC_NAME = 'honda_bosch_a_radar' +BOSCH_A_NUM_SLOTS = 16 + + +def _bosch_a_main_base(slot: int) -> int: + return 0x280 + 4 * slot if slot < 4 else 0x2D0 + 4 * (slot - 4) + + +def _bosch_a_aux_id(slot: int) -> int: + return 0x2C8 + slot if slot < 8 else 0x290 + (slot - 8) + + +BOSCH_A_MAIN_IDS = [[_bosch_a_main_base(s) + i for i in range(4)] for s in range(BOSCH_A_NUM_SLOTS)] +BOSCH_A_AUX_IDS = [_bosch_a_aux_id(s) for s in range(BOSCH_A_NUM_SLOTS)] +BOSCH_A_ALL_IDS = [addr for ids in BOSCH_A_MAIN_IDS for addr in ids] + BOSCH_A_AUX_IDS + +# Publish RadarPoints when the last MAIN object frame arrives. Passive captures prove that slot 15's +# companion frame, 0x297, follows 0x2FF and is the final observed object-family frame in a full sweep: +# +# ... 0x2FC, 0x2FD, 0x2FE, 0x2FF, 0x297 +# +# Keep 0x2FF as the RadarPoint trigger while the companion data remains optional/debug-only. Making +# 0x297 the sole trigger would allow one dropped auxiliary frame to suppress an otherwise-valid point +# update, contrary to the parser's "aux never gates validity" contract. +BOSCH_A_TRIGGER_MSG = BOSCH_A_MAIN_IDS[BOSCH_A_NUM_SLOTS - 1][3] +BOSCH_A_SWEEP_END_MSG = BOSCH_A_AUX_IDS[BOSCH_A_NUM_SLOTS - 1] # 0x297 + +# Observed coherent Bosch-A sweep cadence is ~14.5-16 Hz in the available captures. +BOSCH_A_FREQ_HZ = 15 + +# Range: f0 raw_range (12-bit, B2:B3 high nibble) -> meters. Firmware q16 = 8*raw_range; physical +# calibration keeps slope/offset as named, replay-refinable constants (do not add a second radar/camera +# longitudinal offset on top of this). +BOSCH_A_RANGE_SCALE_M = 0.05712 +BOSCH_A_RANGE_OFFSET_M = -3.0 + +# Azimuth: f0 raw_angle (11-bit, B4:B5 high 3 bits), offset-binary about 1024. +# +# The f3 angular-edge pair independently closes the exact center-angle scale: +# azimuth_rad = (raw_angle - 1024) / 2048 +# +# This supersedes the older empirical 0.032 deg/count fit. +BOSCH_A_AZIMUTH_SCALE_RAD = 1.0 / 2048.0 +BOSCH_A_AZIMUTH_CENTER = 1024 + +# Invalid sentinels (section 3/4/5/6 of the spec). +BOSCH_A_STATUS_INVALID = 0xF +BOSCH_A_RANGE_RAW_INVALID = 0xFFF +BOSCH_A_ANGLE_RAW_INVALID = 0x7FF +BOSCH_A_LIFE_INVALID = 0xFFF +BOSCH_A_TRACK_ID_MIN = 1 +BOSCH_A_TRACK_ID_MAX = 0x3F +# AUX logical 0x00CA has an explicit 0x3FF invalid sentinel. Firmware proves the normalization below; +# captures strongly support interpreting the active value as previous/current range ratio. +BOSCH_A_RANGE_RATIO_INVALID = 0x3FF +BOSCH_A_LOGICAL_00CA_INVALID = BOSCH_A_RANGE_RATIO_INVALID # compatibility/debug alias +BOSCH_A_RANGE_RATIO_SCALE = 0.001 +BOSCH_A_RANGE_RATIO_OFFSET = 0.5 + +# The synchronized AUX frame contains an 11-bit offset-binary velocity-like field and a 10-bit +# companion quality/uncertainty field. The byte locations are now decoded in the DBC. The exact +# Bosch descriptor name is still being verified, so keep the conversion constants isolated here. +# Capture validation shows active values in [0, 1728], with 0x7FE used as the inactive sentinel. +BOSCH_A_DIRECT_VREL_INVALID = 0x7FE +BOSCH_A_DIRECT_VREL_MIN_RAW = 0 +BOSCH_A_DIRECT_VREL_MAX_RAW = 1728 +BOSCH_A_DIRECT_VREL_CENTER_RAW = 864 +BOSCH_A_DIRECT_VREL_SCALE_MPS = 1.0 / 64.0 +# Empirical raw-quality policy. U11 error against an independent range-derivative reference rises +# with u10 in a graded way, not a cliff: replay evidence bins it roughly as 0-255 clean, 256-511 only +# mildly degraded, 512-767 clearly degraded, 768+ worst. 511 is set at that mild/clear boundary so +# U11 is still trusted directly through the mildly-degraded band; only u10 above this triggers the +# high-u10 coast path below. This is deliberately not presented as a Bosch physical unit or +# descriptor constant; it keeps saturated/high-uncertainty AUX values from becoming authoritative +# velocity measurements. +BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW = 511 + +# Measurement-authority policy. These are replay-derived safety/tuning gates, not recovered Bosch +# constants. Range innovation is measured from the previous accepted observation so a reset cannot +# become the baseline for following sweeps. +BOSCH_A_RANGE_SIGMA_DEGRADED_RAW = 4 +BOSCH_A_RANGE_INNOVATION_MAX_M = 2.0 +BOSCH_A_RANGE_INNOVATION_HARD_MAX_M = 5.0 +BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS = 50.0 +BOSCH_A_USE_TAN_LATERAL_PROJECTION = True + +# Accepted-range history is retained for continuity and the adjacent two-point fallback. Rejected +# range resets never enter it, and it is not used for a multi-sample OLS velocity fit. +BOSCH_A_VREL_MAX_SAMPLES = 8 + +# Staleness gate -- TUNING constant, reused plumbing pattern (not a firmware fact). At the observed +# ~15 Hz cadence, 0.20 s is approximately three missed sweeps. +BOSCH_A_STALE_S = 0.20 + + +@dataclass +class _BoschASlotState: + last_seen_nanos: int | None = None + + # Firmware logical-ID companion data -- telemetry/debug only for now, never a RadarPoint validity + # gate. 0x00C9 has a firmware transform but no proven physical meaning; 0x00CA has an explicit + # invalid sentinel and its physical meaning remains unresolved. + logical_00c9_raw: float = float('nan') + logical_00ca_raw: float = float('nan') + direct_vrel_raw: int | None = None + direct_vrel_uncertainty_raw: int | None = None + + def reset(self): + self.last_seen_nanos = None + self.logical_00c9_raw = float('nan') + self.logical_00ca_raw = float('nan') + self.direct_vrel_raw = None + self.direct_vrel_uncertainty_raw = None + + +@dataclass +class _BoschATrackState: + """Persistent state for one Bosch CAN object identity, independent of wire slot.""" + track_id: int + prev_frame_idx: int | None = None + prev_life: int | None = None + last_seen_nanos: int | None = None + wire_slot: int | None = None + samples: deque = field(default_factory=lambda: deque(maxlen=BOSCH_A_VREL_MAX_SAMPLES)) + last_trusted_vrel: float | None = None + last_trusted_vrel_nanos: int | None = None + + +def _bosch_a_direct_vrel(raw_value: int | float | None, + uncertainty_raw: int | float | None = None) -> float | None: + """Decode the capture-validated AUX relative-velocity candidate. + + None means that AUX was absent/invalid and the caller should use the existing fallback policy. The [0, 1728] + active domain is deliberately enforced here because values above the observed +13.5 m/s rail have + not appeared on active objects; 0x7FE is the observed inactive sentinel. + """ + if raw_value is None: + return None + raw = int(raw_value) + if raw == BOSCH_A_DIRECT_VREL_INVALID: + return None + if not BOSCH_A_DIRECT_VREL_MIN_RAW <= raw <= BOSCH_A_DIRECT_VREL_MAX_RAW: + return None + # u10 is retained as a raw quality indicator because its physical units remain unresolved. The + # conservative threshold is evidence-backed tuning, not a recovered firmware validity rule. + if uncertainty_raw is not None and int(uncertainty_raw) > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW: + return None + return (raw - BOSCH_A_DIRECT_VREL_CENTER_RAW) * BOSCH_A_DIRECT_VREL_SCALE_MPS + + +def _bosch_a_range_ratio(raw_value: int | float | None) -> float | None: + if raw_value is None: + return None + raw = int(raw_value) + if raw == BOSCH_A_RANGE_RATIO_INVALID or not 0 <= raw < BOSCH_A_RANGE_RATIO_INVALID: + return None + return BOSCH_A_RANGE_RATIO_OFFSET + BOSCH_A_RANGE_RATIO_SCALE * raw + + +def _bosch_a_range_ratio_vrel(raw_value: int | float | None, d_rel: float, dt: float) -> float | None: + """Return the range rate implied by the empirical previous/current range ratio.""" + ratio = _bosch_a_range_ratio(raw_value) + if ratio is None or dt <= 0.0 or not math.isfinite(d_rel): + return None + return d_rel * (1.0 - ratio) / dt + + +def _bosch_a_measurement_degraded(range_sigma_raw: int, existence_raw: int, + direct_vrel_uncertainty_raw: int | None) -> bool: + range_quality_bad = range_sigma_raw >= BOSCH_A_RANGE_SIGMA_DEGRADED_RAW or existence_raw in (0, 0x7F) + velocity_quality_bad = (direct_vrel_uncertainty_raw is not None and + direct_vrel_uncertainty_raw > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW) + return range_quality_bad or velocity_quality_bad + + +def _create_bosch_a_can_parser(CP): + messages = [(addr, BOSCH_A_FREQ_HZ) for addr in BOSCH_A_ALL_IDS] + # Bus.radar selects the Bosch-A DBC; the object/fusion feed itself is + # physically on the camera-side ACC-CAN. + return CANParser(DBC[CP.carFingerprint][Bus.radar], messages, CanBus(CP).camera) + + class RadarInterface(RadarInterfaceBase): def __init__(self, CP): super().__init__(CP) - self.track_id = 0 - self.radar_fault = False - self.radar_wrong_config = False self.radar_off_can = CP.radarUnavailable + self.bosch_a_radar = (not self.radar_off_can and Bus.radar in DBC[CP.carFingerprint] and + DBC[CP.carFingerprint][Bus.radar] == BOSCH_A_DBC_NAME) - # Nidec if self.radar_off_can: self.rcp = None + self.trigger_msg = 0x445 + elif self.bosch_a_radar: + self.rcp = _create_bosch_a_can_parser(CP) + self.trigger_msg = BOSCH_A_TRIGGER_MSG + self._slots = [_BoschASlotState() for _ in range(BOSCH_A_NUM_SLOTS)] + self._tracks: dict[int, _BoschATrackState] = {} + self._slot_track_ids: list[int | None] = [None] * BOSCH_A_NUM_SLOTS + self._last_trigger_nanos = -1 else: + # Nidec self.rcp = _create_nidec_can_parser(CP.carFingerprint) - self.trigger_msg = 0x445 + self.trigger_msg = 0x445 + self.track_id = 0 + self.radar_fault = False + self.radar_wrong_config = False + self.updated_messages = set() def update(self, can_strings): - # in Bosch radar and we are only steering for now, so sleep 0.05s to keep - # radard at 20Hz and return no points - if self.radar_off_can: + if self.radar_off_can or self.rcp is None: return super().update(None) vls = self.rcp.update(can_strings) self.updated_messages.update(vls) if self.trigger_msg not in self.updated_messages: + if self.bosch_a_radar and self._last_trigger_nanos >= 0: + now = self.rcp._last_update_nanos + if (now - self._last_trigger_nanos) * 1e-9 > BOSCH_A_STALE_S: + return self._bosch_a_stale_radardata() return None rr = self._update(self.updated_messages) self.updated_messages.clear() return rr + def _bosch_a_stale_radardata(self): + # Whole-bus silence: clear every live point/history and emit an EMPTY RadarData (not None) so + # radard drops any lead within a cycle instead of freezing a phantom. The next observation starts + # a fresh incarnation for its CAN identity. + self.pts.clear() + self._tracks.clear() + self._slot_track_ids = [None] * BOSCH_A_NUM_SLOTS + for slot_state in self._slots: + slot_state.reset() + self._last_trigger_nanos = -1 + stale = structs.RadarData() + if not self.rcp.can_valid: + stale.errors.canError = True + stale.errors.radarUnavailableTemporary = True + return stale + + def _bosch_a_retire_track(self, track_id: int): + self._tracks.pop(track_id, None) + self.pts.pop(track_id, None) + for slot, slot_track_id in enumerate(self._slot_track_ids): + if slot_track_id == track_id: + self._slot_track_ids[slot] = None + + def _bosch_a_retire_stale_tracks(self, now: int): + for track_id, track in list(self._tracks.items()): + if track.last_seen_nanos is not None and (now - track.last_seen_nanos) * 1e-9 > BOSCH_A_STALE_S: + self._bosch_a_retire_track(track_id) + def _update(self, updated_messages): + if self.bosch_a_radar: + return self._update_bosch_a(updated_messages) + return self._update_nidec(updated_messages) + + def _update_bosch_a(self, updated_messages): + ret = structs.RadarData() + if not self.rcp.can_valid: + ret.errors.canError = True + + now = self.rcp._last_update_nanos + self._last_trigger_nanos = now + self._bosch_a_retire_stale_tracks(now) + + observations = [] + + for slot in range(BOSCH_A_NUM_SLOTS): + f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[slot] + aux = BOSCH_A_AUX_IDS[slot] + st = self._slots[slot] + + if not (f0 in updated_messages and f1 in updated_messages and + f2 in updated_messages and f3 in updated_messages): + # Incomplete main-frame set: a missing CAN frame must not be treated as a lifecycle mismatch or + # death/replacement. Logical tracks are retired independently by their global staleness gate. + continue + + v0 = self.rcp.vl[f0] + v1 = self.rcp.vl[f1] + v2 = self.rcp.vl[f2] + v3 = self.rcp.vl[f3] + + idx0 = int(v0['FRAME_IDX']) + if not (idx0 == int(v1['FRAME_IDX']) == int(v2['FRAME_IDX']) == int(v3['FRAME_IDX'])): + # The four main frames don't share a common cycle index -- not a coherent observation this + # window. Only combine main-frame data from a coherent common frame index (section 2). + continue + + status = int(v0['STATUS']) + range_raw = int(v0['RANGE_RAW']) + angle_raw = int(v0['AZIMUTH_RAW']) + range_sigma_raw = int(v0['RANGE_SIGMA_RAW']) + existence_raw = int(v1['OBJECT_EXISTENCE_PROBABILITY_RAW']) + life = int(v2['LIFECYCLE_RAW']) + track_id = int(v3['TRACK_ID']) + track_id_valid = BOSCH_A_TRACK_ID_MIN <= track_id <= BOSCH_A_TRACK_ID_MAX + st.last_seen_nanos = now + direct_vrel_raw = None + direct_vrel_uncertainty_raw = None + range_ratio_raw = None + + # Attach synchronized companion data only when its cycle matches. Missing AUX never invalidates + # an otherwise coherent F0-F3 object; it only removes independent motion/quality evidence. + if aux in updated_messages: + av = self.rcp.vl[aux] + if int(av['FRAME_IDX']) == idx0: + direct_vrel_raw = int(av['REL_VELOCITY_RAW']) + direct_vrel_uncertainty_raw = int(av['REL_VELOCITY_UNCERTAINTY_RAW']) + range_ratio_raw = int(av['RANGE_RATIO_RAW']) + logical_00c9_raw = av['FW_LID_00C9_RAW'] + logical_00ca_raw = av['FW_LID_00CA_RAW'] + st.logical_00c9_raw = logical_00c9_raw + st.logical_00ca_raw = ( + logical_00ca_raw if logical_00ca_raw != BOSCH_A_LOGICAL_00CA_INVALID else float('nan') + ) + st.direct_vrel_raw = direct_vrel_raw + st.direct_vrel_uncertainty_raw = direct_vrel_uncertainty_raw + + object_valid = (status != BOSCH_A_STATUS_INVALID and range_raw != BOSCH_A_RANGE_RAW_INVALID and + angle_raw != BOSCH_A_ANGLE_RAW_INVALID and life != BOSCH_A_LIFE_INVALID) + + observations.append({ + 'slot': slot, + 'frame_idx': idx0, + 'life': life, + 'track_id': track_id, + 'track_id_valid': track_id_valid, + 'object_valid': object_valid, + 'range_sigma_raw': range_sigma_raw, + 'existence_raw': existence_raw, + 'direct_vrel_raw': direct_vrel_raw, + 'direct_vrel_uncertainty_raw': direct_vrel_uncertainty_raw, + 'range_ratio_raw': range_ratio_raw, + }) + + # First collapse duplicate wire observations of one CAN identity. The dictionary is also the + # output uniqueness boundary: one valid Bosch identity can never create two RadarPoints. + valid_by_id = {} + for observation in observations: + if not (observation['object_valid'] and observation['track_id_valid']): + # An invalid observation ends publication for the object currently occupying this wire slot, + # but it does not immediately destroy the persistent state. The object may be multiplexed to + # another slot or may return before the per-identity stale deadline; a later lifecycle break + # or staleness expiry will clear its derivative history. + ids_to_hide = {self._slot_track_ids[observation['slot']]} + if observation['track_id_valid']: + ids_to_hide.add(observation['track_id']) + for invalid_id in ids_to_hide - {None}: + self.pts.pop(invalid_id, None) + continue + track_id = observation['track_id'] + current = valid_by_id.get(track_id) + if current is None: + valid_by_id[track_id] = observation + continue + + # Prefer the wire slot currently associated with the persistent state. If neither candidate is + # preferred, retain the lower slot for deterministic handling of a malformed duplicate frame. + track = self._tracks.get(track_id) + current_score = (0 if track is not None and track.wire_slot == current['slot'] else 1, current['slot']) + candidate_score = (0 if track is not None and track.wire_slot == observation['slot'] else 1, + observation['slot']) + if candidate_score < current_score: + valid_by_id[track_id] = observation + + valid_ids = set(valid_by_id) + for track_id, observation in sorted(valid_by_id.items(), key=lambda item: item[1]['slot']): + slot = observation['slot'] + idx0 = observation['frame_idx'] + life = observation['life'] + + # A wire-slot replacement ends publication for the old occupant, but not its persistent + # identity/history. If the old ID is still valid in another slot this sweep, keep its point; + # otherwise hide it until that ID returns or reaches its own stale deadline. + old_id = self._slot_track_ids[slot] + if old_id is not None and old_id != track_id and old_id not in valid_ids: + self.pts.pop(old_id, None) + + track = self._tracks.get(track_id) + if track is None: + track = _BoschATrackState(track_id=track_id) + self._tracks[track_id] = track + + same_incarnation = False + if track.prev_frame_idx is not None and track.prev_life is not None: + frame_delta = (idx0 - track.prev_frame_idx) & 0xF + life_delta = (life - track.prev_life) & 0xFFF + same_incarnation = life_delta == 2 * frame_delta + + if not same_incarnation: + # The CAN identity remains the externally-visible key, but a lifecycle discontinuity starts a + # new incarnation and must not inherit the previous object's range-rate history. + track.samples.clear() + track.last_trusted_vrel = None + track.last_trusted_vrel_nanos = None + self.pts.pop(track_id, None) + + v0 = self.rcp.vl[BOSCH_A_MAIN_IDS[slot][0]] + range_raw = int(v0['RANGE_RAW']) + angle_raw = int(v0['AZIMUTH_RAW']) + dRel = BOSCH_A_RANGE_SCALE_M * range_raw + BOSCH_A_RANGE_OFFSET_M + azimuth_rad = BOSCH_A_AZIMUTH_SCALE_RAD * (angle_raw - BOSCH_A_AZIMUTH_CENTER) + # The firmware's internal geometry path uses tan(angle) for a forward-axis distance. The + # Bosch object range is consumed as that forward-axis quantity here, so use the same projection + # rather than treating it as radial/slant range. Keep the switch explicit while the final + # output bridge remains under static review. + # + # Sign: AZIMUTH_RAW > center (positive azimuth_rad) is a LEFT-of-center detection in car frame's + # y axis (left is positive), matching the Nidec RadarPoint.yRel contract. Confirmed against real + # captures 2026-08-22: a stationary cluster of queued vehicles visible on the left in the road + # camera was rendering on the right in both the Qt and raylib radar-track overlays with the + # previously-flipped sign. + lateral_projection = math.tan if BOSCH_A_USE_TAN_LATERAL_PROJECTION else math.sin + yRel = dRel * lateral_projection(azimuth_rad) + + now_s = now * 1e-9 + direct_vrel_raw = observation['direct_vrel_raw'] + direct_vrel_uncertainty_raw = observation['direct_vrel_uncertainty_raw'] + direct_vrel = _bosch_a_direct_vrel(direct_vrel_raw, direct_vrel_uncertainty_raw) + live_direct_vrel = _bosch_a_direct_vrel(direct_vrel_raw) + range_ratio_raw = observation['range_ratio_raw'] + + # A live U11 rejected solely by the conservative U10 qualification threshold is neither an + # unavailable velocity nor permission to synthesize a one-sweep range derivative. It is also + # NOT permission to skip range-innovation checking below: u10 correlates with range_sigma in + # replay data, so a high-u10 sweep is exactly the condition where a bad/discontinuous range + # (slot migration, reset) is most likely, not less likely. Range acceptance is therefore + # decided on the same terms as every other sweep first; only once the range clears that gate + # does a high-u10 U11 fall back to coasting instead of publishing a synthesized derivative. + high_u10_live_vrel = (direct_vrel is None and live_direct_vrel is not None and + direct_vrel_uncertainty_raw is not None and + direct_vrel_uncertainty_raw > BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW) + + # Validate against the previous accepted range. Qualified U11 and the range-ratio field are + # independent corroboration paths; high-U10 U11 is deliberately excluded from this decision. + previous_sample = track.samples[-1] if track.samples else None + fallback_vrel = 0.0 + ratio_vrel = None + range_rejected = False + degraded = _bosch_a_measurement_degraded( + observation['range_sigma_raw'], observation['existence_raw'], direct_vrel_uncertainty_raw, + ) + if previous_sample is not None: + previous_time, previous_range = previous_sample + dt = now_s - previous_time + if dt <= 0.0: + range_rejected = True + else: + fallback_vrel = (dRel - previous_range) / dt + ratio = _bosch_a_range_ratio(range_ratio_raw) + ratio_vrel = _bosch_a_range_ratio_vrel(range_ratio_raw, dRel, dt) + + residuals_m = [] + if direct_vrel is not None: + residuals_m.append(abs(dRel - (previous_range + direct_vrel * dt))) + if ratio is not None: + residuals_m.append(abs(previous_range - dRel * ratio)) + + if residuals_m: + innovation_m = min(residuals_m) + range_rejected = ( + innovation_m > BOSCH_A_RANGE_INNOVATION_HARD_MAX_M or + (degraded and innovation_m > BOSCH_A_RANGE_INNOVATION_MAX_M) + ) + else: + range_rejected = abs(fallback_vrel) > BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS + + if range_rejected: + # Keep the last accepted point briefly as an unmeasured coast. The rejected geometry is not + # published and never becomes the baseline for a later derivative. + accepted_fresh = previous_sample is not None and now_s - previous_sample[0] <= BOSCH_A_STALE_S + point = self.pts.get(track_id) + if accepted_fresh and point is not None: + point.measured = False + else: + self.pts.pop(track_id, None) + track.prev_frame_idx = idx0 + track.prev_life = life + track.last_seen_nanos = now + track.wire_slot = slot + for old_slot, old_id in enumerate(self._slot_track_ids): + if old_slot != slot and old_id == track_id: + self._slot_track_ids[old_slot] = None + self._slot_track_ids[slot] = track_id + continue + + if high_u10_live_vrel: + # The range cleared innovation checking above, so geometry here is trustworthy; only vRel is + # in question. Preserve current geometry but coast only a recent authoritative motion + # estimate without a KF update, rather than publishing a one-sweep-derivative synthesis. + trusted_fresh = (track.last_trusted_vrel is not None and track.last_trusted_vrel_nanos is not None and + (now - track.last_trusted_vrel_nanos) * 1e-9 <= BOSCH_A_STALE_S) + if trusted_fresh: + point = self.pts.get(track_id) + if point is not None: + point.dRel = dRel + point.yRel = yRel + point.vRel = track.last_trusted_vrel + point.measured = False + else: + track.last_trusted_vrel = None + track.last_trusted_vrel_nanos = None + self.pts.pop(track_id, None) + + # Do not let a coast-only range observation become a future derivative baseline. + track.prev_frame_idx = idx0 + track.prev_life = life + track.last_seen_nanos = now + track.wire_slot = slot + for old_slot, old_id in enumerate(self._slot_track_ids): + if old_slot != slot and old_id == track_id: + self._slot_track_ids[old_slot] = None + self._slot_track_ids[slot] = track_id + continue + + # Native U11 is unavailable here (sentinel/out-of-range -- the high-u10-but-live case above + # already returned before this point) and the range-ratio field is unusable or degraded too. + # The remaining option is the raw one-sweep (dRel-previous_range)/dt derivative, which is + # structurally the same hazard the high-u10 case guards against: a synthesized rate becoming an + # authoritative measurement. Coast a recent trusted velocity instead, on the same terms as above, + # rather than publish it. + # A true birth observation (no previous accepted range yet) can never mature into a published + # point this cycle regardless of vRel source -- `matured` below requires a second sample -- so + # only intercept once a fallback derivative would actually have something to poison. + u11_and_ratio_unavailable = (direct_vrel is None and (ratio_vrel is None or degraded) and + previous_sample is not None) + if u11_and_ratio_unavailable: + trusted_fresh = (track.last_trusted_vrel is not None and track.last_trusted_vrel_nanos is not None and + (now - track.last_trusted_vrel_nanos) * 1e-9 <= BOSCH_A_STALE_S) + if trusted_fresh: + point = self.pts.get(track_id) + if point is not None: + point.dRel = dRel + point.yRel = yRel + point.vRel = track.last_trusted_vrel + point.measured = False + else: + track.last_trusted_vrel = None + track.last_trusted_vrel_nanos = None + self.pts.pop(track_id, None) + + # Do not let a coast-only range observation become a future derivative baseline. + track.prev_frame_idx = idx0 + track.prev_life = life + track.last_seen_nanos = now + track.wire_slot = slot + for old_slot, old_id in enumerate(self._slot_track_ids): + if old_slot != slot and old_id == track_id: + self._slot_track_ids[old_slot] = None + self._slot_track_ids[slot] = track_id + continue + + track.samples.append((now_s, dRel)) + sample_count = len(track.samples) + + # Prefer qualified native U11; otherwise the range-ratio field. The raw one-sweep derivative is + # never published as a measurement -- see the coast/drop branch above, which intercepts before + # this point whenever neither native U11 nor the ratio field is usable. + if direct_vrel is not None: + vRel = direct_vrel + else: + vRel = ratio_vrel + trustworthy_vrel = True + + # A birth observation has no range-rate yet. Keep it as history, but do not publish a RadarPoint + # until a second coherent observation of the same CAN identity supplies a finite derivative. + matured = sample_count >= 2 and math.isfinite(vRel) + if trustworthy_vrel: + track.last_trusted_vrel = vRel + track.last_trusted_vrel_nanos = now + if matured and track_id not in self.pts: + self.pts[track_id] = structs.RadarData.RadarPoint() + self.pts[track_id].trackId = track_id + self.pts[track_id].aRel = float('nan') + self.pts[track_id].yvRel = float('nan') + + if matured: + self.pts[track_id].dRel = dRel + self.pts[track_id].yRel = yRel + self.pts[track_id].vRel = vRel + self.pts[track_id].measured = True + else: + self.pts.pop(track_id, None) + + track.prev_frame_idx = idx0 + track.prev_life = life + track.last_seen_nanos = now + track.wire_slot = slot + for old_slot, old_id in enumerate(self._slot_track_ids): + if old_slot != slot and old_id == track_id: + self._slot_track_ids[old_slot] = None + self._slot_track_ids[slot] = track_id + + ret.points = [self.pts[track_id] for track_id in sorted(self.pts)] + return ret + + def _update_nidec(self, updated_messages): ret = structs.RadarData() for ii in sorted(updated_messages): diff --git a/opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py b/opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py new file mode 100644 index 000000000..e3efb8370 --- /dev/null +++ b/opendbc_repo/opendbc/car/honda/tests/test_bosch_a_radar.py @@ -0,0 +1,1132 @@ +import math +import random + +import pytest + +from opendbc.can.dbc import DBC as DbcFile +from opendbc.can.parser import get_raw_value +from opendbc.car.can_definitions import CanData +from opendbc.car.honda.hondacan import CanBus +from opendbc.car.honda.interface import CarInterface +from opendbc.car.honda.radar_interface import ( + BOSCH_A_AZIMUTH_SCALE_RAD, + BOSCH_A_AUX_IDS, + BOSCH_A_DBC_NAME, + BOSCH_A_DIRECT_VREL_INVALID, + BOSCH_A_DIRECT_VREL_MAX_RAW, + BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW, + BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS, + BOSCH_A_FREQ_HZ, + BOSCH_A_MAIN_IDS, + BOSCH_A_NUM_SLOTS, + BOSCH_A_RANGE_RATIO_INVALID, + BOSCH_A_RANGE_SCALE_M, + BOSCH_A_STALE_S, + BOSCH_A_SWEEP_END_MSG, + BOSCH_A_TRIGGER_MSG, + _bosch_a_aux_id, + _bosch_a_direct_vrel, + _bosch_a_main_base, + _bosch_a_range_ratio, + _bosch_a_range_ratio_vrel, +) +from opendbc.car.honda.values import CAR +from openpilot.common.params import Params + +# Tester toggle: CP is computed once at import time below (many helpers in this module close over +# it), which runs before any pytest fixture could -- so this has to be plain top-level code, not a +# fixture. teardown_module() restores it once every test in this file has run (mirrors +# gm/tests/test_gm.py's put_bool/finally pattern for params-gated _get_params behavior). +Params().put_bool("HondaBoschARadar", True) + + +def teardown_module(module): + Params().remove("HondaBoschARadar") + + +CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH) +BUS = CanBus(CP).camera + + +# --- synthetic frame builders (inverse of the spec's raw-byte formulas) ----------------------------- + +def make_f0(frame_idx=0, status=0x7, range_raw=0, angle_raw=0, range_sigma_raw=1): + # inverse of raw_angle = (B4 << 3) | (B5 >> 5): B4 carries the top 8 bits, B5's top 3 bits carry the + # bottom 3 bits of the 11-bit angle (the decoder ignores B5's low 5 bits entirely). + B0 = (range_sigma_raw & 0x7F) << 1 + B3 = (frame_idx & 0xF) | ((range_raw & 0xF) << 4) + B2 = (range_raw >> 4) & 0xFF + B1 = (status & 0xF) << 4 + B5 = (angle_raw & 0x7) << 5 + B4 = (angle_raw >> 3) & 0xFF + return bytes([B0, B1, B2, B3, B4, B5, 0, 0]) + + +def make_f1(frame_idx=0, existence_raw=126): + return bytes([0, 0, 0, (frame_idx & 0xF) << 1, 0, existence_raw & 0x7F, 0, 0]) + + +def make_f2(frame_idx=0, life=0): + B1 = (frame_idx & 0xF) | ((life & 0xF) << 4) + B0 = (life >> 4) & 0xFF + return bytes([B0, B1, 0, 0, 0, 0, 0, 0]) + + +def make_f3(frame_idx=0, edge_a_raw=0, edge_b_raw=0, sigma_a_raw=0, track_id=0xFF): + B0 = (edge_a_raw >> 3) & 0xFF + B1 = ((edge_a_raw & 0x7) << 5) | ((frame_idx & 0xF) << 1) + B2 = (edge_b_raw >> 3) & 0xFF + B3 = (edge_b_raw & 0x7) << 5 + B4 = (sigma_a_raw >> 2) & 0xFF + B5 = (sigma_a_raw & 0x3) << 6 + return bytes([B0, B1, B2, B3, B4, B5, track_id & 0xFF, 0]) + + +def make_aux(frame_idx=0, rawc9=0, rawca=0, direct_vrel_raw=BOSCH_A_DIRECT_VREL_INVALID, + direct_vrel_uncertainty_raw=0x3FF): + B0 = (direct_vrel_raw >> 3) & 0xFF + B1 = ((direct_vrel_raw & 0x7) << 5) | ((frame_idx & 0xF) << 1) + B2 = (direct_vrel_uncertainty_raw >> 2) & 0xFF + B3 = (direct_vrel_uncertainty_raw & 0x3) << 6 + B5 = (rawc9 & 0x3) << 6 + B4 = (rawc9 >> 2) & 0xFF + B7 = (rawca & 0x3) << 6 + B6 = (rawca >> 2) & 0xFF + return bytes([B0, B1, B2, B3, B4, B5, B6, B7]) + + +def make_main_frames(slot, frame_idx, status, range_raw, angle_raw, life, track_id=1, + range_sigma_raw=1, existence_raw=126): + f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[slot] + return [ + CanData(f0, make_f0(frame_idx, status, range_raw, angle_raw, range_sigma_raw), BUS), + CanData(f1, make_f1(frame_idx, existence_raw), BUS), + CanData(f2, make_f2(frame_idx, life), BUS), + CanData(f3, make_f3(frame_idx, track_id=track_id), BUS), + ] + + +def make_radar_interface(): + return CarInterface.RadarInterface(CP) + + +def sweep(slot, frame_idx, status, range_raw, angle_raw, life, t_nanos, with_aux=False, aux_frame_idx=None, + rawc9=0, rawca=BOSCH_A_RANGE_RATIO_INVALID, extra_slots=(), track_id=1, direct_vrel_raw=BOSCH_A_DIRECT_VREL_INVALID, + direct_vrel_uncertainty_raw=0x3FF, range_sigma_raw=1, existence_raw=126): + """Build one update() input: a full main-frame set for `slot` (+ optional aux), plus the trigger + frame (slot 15's f3) so update() always processes the cycle unless the caller is testing slot 15 + itself or an incomplete-frame scenario via extra_slots.""" + f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[slot] + aux = BOSCH_A_AUX_IDS[slot] + frames = make_main_frames( + slot, frame_idx, status, range_raw, angle_raw, life, track_id, + range_sigma_raw=range_sigma_raw, existence_raw=existence_raw, + ) + if with_aux: + frames.append(CanData(aux, make_aux(aux_frame_idx if aux_frame_idx is not None else frame_idx, rawc9, rawca, + direct_vrel_raw, direct_vrel_uncertainty_raw), BUS)) + if slot != BOSCH_A_NUM_SLOTS - 1: + _, _, _, trig_f3 = BOSCH_A_MAIN_IDS[BOSCH_A_NUM_SLOTS - 1] + frames.append(CanData(trig_f3, make_f3(frame_idx), BUS)) + for extra in extra_slots: + frames.append(extra) + return [(t_nanos, frames)] + + +# --- 1. all 16 slot/frame ID mappings --------------------------------------------------------------- + +def test_all_16_slot_main_ids(): + for slot in range(16): + base = 0x280 + 4 * slot if slot < 4 else 0x2D0 + 4 * (slot - 4) + assert _bosch_a_main_base(slot) == base + assert BOSCH_A_MAIN_IDS[slot] == [base, base + 1, base + 2, base + 3] + + +def test_all_16_slot_aux_ids(): + expected = { + 0: 0x2C8, 1: 0x2C9, 2: 0x2CA, 3: 0x2CB, 4: 0x2CC, 5: 0x2CD, 6: 0x2CE, 7: 0x2CF, + 8: 0x290, 9: 0x291, 10: 0x292, 11: 0x293, 12: 0x294, 13: 0x295, 14: 0x296, 15: 0x297, + } + for slot, addr in expected.items(): + assert _bosch_a_aux_id(slot) == addr + assert BOSCH_A_AUX_IDS[slot] == addr + + +def test_no_duplicate_can_ids_across_80_messages(): + all_ids = [addr for ids in BOSCH_A_MAIN_IDS for addr in ids] + BOSCH_A_AUX_IDS + assert len(all_ids) == 80 + assert len(set(all_ids)) == 80 + + +# --- 2. DBC bit geometry matches the spec's raw-byte formulas exactly (section 16) ------------------- + +class TestDbcBitGeometry: + dbc = DbcFile(BOSCH_A_DBC_NAME) + + def test_f0_fields(self): + msg = self.dbc.msgs[0x280] + rng = random.Random(0) + for _ in range(500): + b = [rng.randint(0, 255) for _ in range(8)] + dat = bytes(b) + assert get_raw_value(dat, msg.sigs['STATUS']) == (b[1] >> 4) & 0x0F + assert get_raw_value(dat, msg.sigs['FRAME_IDX']) == b[3] & 0x0F + assert get_raw_value(dat, msg.sigs['RANGE_RAW']) == (b[2] << 4) | (b[3] >> 4) + assert get_raw_value(dat, msg.sigs['AZIMUTH_RAW']) == (b[4] << 3) | (b[5] >> 5) + + def test_f1_frame_idx(self): + msg = self.dbc.msgs[0x281] + rng = random.Random(1) + for _ in range(500): + b = [rng.randint(0, 255) for _ in range(8)] + assert get_raw_value(bytes(b), msg.sigs['FRAME_IDX']) == (b[3] >> 1) & 0x0F + + def test_f2_fields(self): + msg = self.dbc.msgs[0x282] + rng = random.Random(2) + for _ in range(500): + b = [rng.randint(0, 255) for _ in range(8)] + dat = bytes(b) + assert get_raw_value(dat, msg.sigs['FRAME_IDX']) == b[1] & 0x0F + assert get_raw_value(dat, msg.sigs['LIFECYCLE_RAW']) == (b[0] << 4) | (b[1] >> 4) + + def test_f3_fields(self): + msg = self.dbc.msgs[0x283] + rng = random.Random(3) + for _ in range(500): + b = [rng.randint(0, 255) for _ in range(8)] + dat = bytes(b) + assert get_raw_value(dat, msg.sigs['AZIMUTH_EDGE_A_RAW']) == (b[0] << 3) | (b[1] >> 5) + assert get_raw_value(dat, msg.sigs['FRAME_IDX']) == (b[1] >> 1) & 0x0F + assert get_raw_value(dat, msg.sigs['AZIMUTH_EDGE_B_RAW']) == (b[2] << 3) | (b[3] >> 5) + assert get_raw_value(dat, msg.sigs['AZIMUTH_EDGE_SIGMA_A_RAW']) == (b[4] << 2) | (b[5] >> 6) + + def test_track_id_is_f3_byte6_and_does_not_overlap_existing_fields(self): + def affected_bits(signal): + bits = set() + for byte in range(8): + for bit in range(8): + data = bytearray(8) + data[byte] = 1 << bit + if get_raw_value(bytes(data), signal) != 0: + bits.add((byte, bit)) + return bits + + expected_bits = {(6, bit) for bit in range(8)} + f3_ids = [BOSCH_A_MAIN_IDS[slot][3] for slot in range(BOSCH_A_NUM_SLOTS)] + for message_id in f3_ids: + msg = self.dbc.msgs[message_id] + track_id = msg.sigs['TRACK_ID'] + assert track_id.start_bit == 55 + assert track_id.size == 8 + assert track_id.is_little_endian is False + assert affected_bits(track_id) == expected_bits + + existing_bits = set() + for name, signal in msg.sigs.items(): + if name != 'TRACK_ID': + existing_bits |= affected_bits(signal) + assert not existing_bits & expected_bits + + rng = random.Random(33) + msg = self.dbc.msgs[BOSCH_A_MAIN_IDS[0][3]] + for _ in range(500): + data = bytes(rng.randint(0, 255) for _ in range(8)) + assert get_raw_value(data, msg.sigs['TRACK_ID']) == data[6] + + def test_aux_fields(self): + msg = self.dbc.msgs[0x2C8] + rng = random.Random(4) + for _ in range(500): + b = [rng.randint(0, 255) for _ in range(8)] + dat = bytes(b) + assert get_raw_value(dat, msg.sigs['FRAME_IDX']) == (b[1] >> 1) & 0x0F + assert get_raw_value(dat, msg.sigs['REL_VELOCITY_RAW']) == (b[0] << 3) | (b[1] >> 5) + assert get_raw_value(dat, msg.sigs['REL_VELOCITY_UNCERTAINTY_RAW']) == (b[2] << 2) | (b[3] >> 6) + assert get_raw_value(dat, msg.sigs['FW_LID_00C9_RAW']) == (b[4] << 2) | (b[5] >> 6) + assert get_raw_value(dat, msg.sigs['FW_LID_00CA_RAW']) == (b[6] << 2) | (b[7] >> 6) + assert get_raw_value(dat, msg.sigs['AZIMUTH_EDGE_SIGMA_B_RAW']) == (b[4] << 2) | (b[5] >> 6) + assert get_raw_value(dat, msg.sigs['RANGE_RATIO_RAW']) == (b[6] << 2) | (b[7] >> 6) + + def test_semantic_quality_aliases_exist_on_every_slot(self): + for slot in range(BOSCH_A_NUM_SLOTS): + f0, f1, f2, _ = BOSCH_A_MAIN_IDS[slot] + aux = BOSCH_A_AUX_IDS[slot] + assert self.dbc.msgs[f0].sigs['RANGE_SIGMA_RAW'].size == 7 + assert self.dbc.msgs[f1].sigs['OBJECT_EXISTENCE_PROBABILITY_RAW'].size == 7 + assert self.dbc.msgs[f1].sigs['ANGULAR_WIDTH_RAW'].size == 11 + assert self.dbc.msgs[f2].sigs['NORMALIZED_CLOSING_RAW'].size == 10 + assert self.dbc.msgs[f2].sigs['NORMALIZED_CLOSING_SIGMA_RAW'].size == 7 + assert self.dbc.msgs[aux].sigs['AZIMUTH_EDGE_SIGMA_B_RAW'].size == 10 + assert self.dbc.msgs[aux].sigs['RANGE_RATIO_RAW'].size == 10 + + def test_descriptor_backed_raw_fields_cover_all_unmapped_main_bits(self): + f0_ids = [ + ['25', '26', '28', '29'], ['34', '35', '37', '38'], ['43', '44', '46', '47'], + ['52', '53', '55', '56'], ['61', '62', '64', '65'], ['70', '71', '73', '74'], + ['7F', '80', '82', '83'], ['8E', '8F', '91', '92'], ['9D', '9E', 'A0', 'A1'], + ['AC', 'AD', 'AF', 'B0'], ['BB', 'BC', 'BE', 'BF'], ['CA', 'CB', 'CD', 'CE'], + ['D9', 'DA', 'DC', 'DD'], ['E8', 'E9', 'EB', 'EC'], ['F7', 'F8', 'FA', 'FB'], + ['06', '07', '09', '0A'], + ] + f1_ids = [ + ['2B', '18', '2C', '19', '2D'], ['3A', '23', '3B', '24', '3C'], ['49', '2E', '4A', '2F', '4B'], + ['58', '39', '59', '3A', '5A'], ['67', '44', '68', '45', '69'], ['76', '4F', '77', '50', '78'], + ['85', '5A', '86', '5B', '87'], ['94', '65', '95', '66', '96'], ['A3', '70', 'A4', '71', 'A5'], + ['B2', '7B', 'B3', '7C', 'B4'], ['C1', '86', 'C2', '87', 'C3'], ['D0', '91', 'D1', '92', 'D2'], + ['DF', '9C', 'E0', '9D', 'E1'], ['EE', 'A7', 'EF', 'A8', 'F0'], ['FD', 'B2', 'FE', 'B3', 'FF'], + ['0C', 'BD', '0D', 'BE', '0E'], + ] + f2_ids = [ + ['2F', '1A', '30', '1B', '1C'], ['3E', '25', '3F', '26', '27'], ['4D', '30', '4E', '31', '32'], + ['5C', '3B', '5D', '3C', '3D'], ['6B', '46', '6C', '47', '48'], ['7A', '51', '7B', '52', '53'], + ['89', '5C', '8A', '5D', '5E'], ['98', '67', '99', '68', '69'], ['A7', '72', 'A8', '73', '74'], + ['B6', '7D', 'B7', '7E', '7F'], ['C5', '88', 'C6', '89', '8A'], ['D4', '93', 'D5', '94', '95'], + ['E3', '9E', 'E4', '9F', 'A0'], ['F2', 'A9', 'F3', 'AA', 'AB'], ['01', 'B4', '02', 'B5', 'B6'], + ['10', 'BF', '11', 'C0', 'C1'], + ] + positions = { + 'F0': [(44, 4), (11, 2), (7, 7), (55, 8)], + 'F1': [(46, 7), (23, 11), (15, 8), (39, 9), (7, 6)], + 'F2': [(29, 1), (23, 10), (46, 7), (39, 9), (55, 9)], + } + ids_by_frame = {'F0': f0_ids, 'F1': f1_ids, 'F2': f2_ids} + for slot in range(BOSCH_A_NUM_SLOTS): + for frame in ('F0', 'F1', 'F2'): + msg = self.dbc.msgs[BOSCH_A_MAIN_IDS[slot][int(frame[-1])]] + for logical_id, (start_bit, size) in zip(ids_by_frame[frame][slot], positions[frame], strict=True): + signal = msg.sigs[f'FW_LID_{logical_id}_RAW'] + assert signal.start_bit == start_bit + assert signal.size == size + assert signal.is_little_endian is False + + def affected_bits(signal): + bits = set() + for byte in range(8): + for bit in range(8): + data = bytearray(8) + data[byte] = 1 << bit + if get_raw_value(bytes(data), signal) != 0: + bits.add((byte, bit)) + return bits + + fields = [msg.sigs[f'FW_LID_{logical_id}_RAW'] for logical_id in ids_by_frame[frame][slot]] + covered = set() + for signal in fields: + bits = affected_bits(signal) + assert not covered & bits + covered |= bits + + semantic_aliases = { + 'RANGE_SIGMA_RAW', 'OBJECT_EXISTENCE_PROBABILITY_RAW', + 'ANGULAR_WIDTH_RAW', 'ANGULAR_WIDTH_DEG', + 'NORMALIZED_CLOSING_RAW', 'NORMALIZED_CLOSING', 'NORMALIZED_CLOSING_SIGMA_RAW', + } + existing_bits = set() + for name, signal in msg.sigs.items(): + if not name.startswith('FW_LID_') and name not in semantic_aliases: + existing_bits |= affected_bits(signal) + assert not existing_bits & covered + + +# --- 3. range / azimuth extraction + invalid sentinels ------------------------------------------------ + +class TestRangeAzimuth: + def test_range_scale_and_offset(self): + ri = make_radar_interface() + raw_range = 1000 + ri.update(sweep(0, 0, 0x7, raw_range, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, raw_range, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].dRel == pytest.approx(0.05712 * raw_range - 3.0) + + def test_range_invalid_sentinel_0xfff(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 0xFFF, 1024, 1, 0)) + assert len(rr.points) == 0 + + def test_azimuth_invalid_sentinel_0x7ff(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 0x7FF, 1, 0)) + assert len(rr.points) == 0 + + def test_yrel_sign_right_of_center_is_negative(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024 - 100, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1000, 1024 - 100, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) # raw_angle < center -> right of center + d = 0.05712 * 1000 - 3.0 + expected_y = d * math.tan(-100.0 / 2048.0) + assert rr.points[0].yRel == pytest.approx(expected_y) + assert rr.points[0].yRel < 0 + + def test_yrel_sign_left_of_center_is_positive(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024 + 100, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1000, 1024 + 100, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) # raw_angle > center -> left of center + assert rr.points[0].yRel > 0 + + def test_yrel_zero_on_boresight(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1000, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].yRel == pytest.approx(0.0, abs=1e-9) + + +# --- 4. status / life invalid sentinels -> conservative object_valid --------------------------------- + +class TestObjectValid: + def test_status_invalid_0xf(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0xF, 1000, 1024, 1, 0)) + assert len(rr.points) == 0 + + def test_life_invalid_0xfff(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 1024, 0xFFF, 0)) + assert len(rr.points) == 0 + + def test_first_valid_sample_is_withheld(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + assert len(rr.points) == 0 + + def test_invalid_observation_does_not_mature_or_preserve_birth_history(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 1, 0xF, 1000, 1024, 3, 50_000_000)) + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 2, 0x7, 1000, 1024, 1, 100_000_000)) + assert len(rr.points) == 0 + + def test_incomplete_observation_does_not_mature_or_add_history(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + assert len(rr.points) == 0 + + f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[0] + _, _, _, trig_f3 = BOSCH_A_MAIN_IDS[15] + frames = [ + CanData(f0, make_f0(1, 0x7, 1010, 1024), BUS), + CanData(f1, make_f1(1), BUS), + CanData(f2, make_f2(1, 3), BUS), + CanData(trig_f3, make_f3(1), BUS), + ] + rr = ri.update([(50_000_000, frames)]) + assert len(rr.points) == 0 + + rr = ri.update(sweep(0, 2, 0x7, 1020, 1024, 5, 100_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert math.isfinite(rr.points[0].vRel) + + def test_second_same_incarnation_sample_without_u11_or_ratio_does_not_publish(self): + # Neither native U11 nor the ratio field is available on either sample (no aux at all). The raw + # one-sweep range derivative that would previously have matured into a measured=True point is now + # never published: with no prior trusted velocity to coast, the point is withheld entirely. + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000)) + assert len(rr.points) == 0 + + +# --- 5. lifecycle continuity --------------------------------------------------------------------------- + +class TestLifecycle: + def test_normal_plus_2_continuity_keeps_trackid(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + rr = ri.update(sweep(0, 2, 0x7, 1020, 1024, 5, 100_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].trackId == t0 + + def test_continuity_across_dropped_sweeps(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + # 3 sweeps missed: frame_idx jumps from 0 to 4, life must jump by 2*4=8 to stay the same incarnation + rr = ri.update(sweep(0, 4, 0x7, 1040, 1024, 9, 200_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].trackId == t0 + + def test_frame_idx_wraps_mod_16(self): + ri = make_radar_interface() + ri.update(sweep(0, 14, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 15, 0x7, 1005, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + # frame_idx wraps 14 -> 1 (delta = (1-14)&0xF = 3), life must advance by 6 + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 7, 150_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].trackId == t0 + + def test_life_wraps_mod_4096_stays_same_incarnation(self): + ri = make_radar_interface() + ri.update(sweep(0, 14, 0x7, 1000, 1024, 4094, 0)) + rr = ri.update(sweep(0, 15, 0x7, 1005, 1024, 0, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + # frame_idx 14 -> 0 (delta=2), life 4094 -> 2 ((2-4094)&0xFFF == 4 == 2*2) + rr = ri.update(sweep(0, 0, 0x7, 1010, 1024, 2, 100_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].trackId == t0 + + def test_in_place_replacement_no_invalid_gap_resets_history_but_keeps_can_id(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + # frame_idx advances normally (+1) but life jumps by an unrelated odd amount -> NOT +2*frame_delta + rr = ri.update(sweep(0, 2, 0x7, 50, 1024, 7, 100_000_000)) + assert len(rr.points) == 0 # replacement birth sample is withheld + rr = ri.update(sweep(0, 3, 0x7, 50, 1024, 9, 150_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert rr.points[0].trackId == t0 + + def test_replacement_does_not_assume_life_restarts_at_1_3_5(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + # a "replacement" that happens to restart life at a big/even value must still be treated as a + # replacement (not death) because it fails the frame/life identity, regardless of the new value's parity + rr = ri.update(sweep(0, 2, 0x7, 50, 1024, 4000, 100_000_000)) + assert len(rr.points) == 0 # replacement birth sample is withheld + rr = ri.update(sweep(0, 3, 0x7, 50, 1024, 4002, 150_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].trackId == t0 + assert rr.points[0].vRel == 0.0 + assert len(rr.points) == 1 + + def test_death_then_rebirth_reuses_can_id_with_clean_history(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + rr = ri.update(sweep(0, 1, 0xF, 1000, 1024, 3, 50_000_000)) # death + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 2, 0x7, 1000, 1024, 1, 100_000_000)) # rebirth, first sample withheld + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 3, 0x7, 1000, 1024, 3, 150_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert rr.points[0].trackId == t0 + + +# --- 6. CAN track identity is the RadarPoint identity ----------------------------------------------- + +def test_trackid_comes_from_can_and_is_not_synthetic(): + ri = make_radar_interface() + seen_ids = set() + t = 0 + for i in range(10): + frame_idx = (2 * i) % 16 + can_track_id = i + 1 + ri.update(sweep(0, frame_idx, 0x7, 1000, 1024, 1, t, track_id=can_track_id)) + t += 50_000_000 + rr = ri.update(sweep(0, (frame_idx + 1) % 16, 0x7, 1000, 1024, 3, t, track_id=can_track_id, + with_aux=True, direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t += 50_000_000 + assert can_track_id in {point.trackId for point in rr.points} + seen_ids.add(can_track_id) + assert seen_ids == set(range(1, 11)) + + +# --- 7. vRel derivative sign ------------------------------------------------------------------------- + +class TestVrel: + def test_direct_aux_vrel_is_preferred_over_range_derivative(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=80)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=80)) + assert rr.points[0].vRel == pytest.approx((800 - 864) / 64.0) + + def test_direct_aux_vrel_domain_and_sentinel(self): + assert _bosch_a_direct_vrel(0) == pytest.approx(-13.5) + assert _bosch_a_direct_vrel(BOSCH_A_DIRECT_VREL_MAX_RAW) == pytest.approx(13.5) + assert _bosch_a_direct_vrel(1729) is None + assert _bosch_a_direct_vrel(BOSCH_A_DIRECT_VREL_INVALID) is None + assert _bosch_a_direct_vrel(0x7FF) is None + assert _bosch_a_direct_vrel(None) is None + assert _bosch_a_direct_vrel(0, BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW) == pytest.approx(-13.5) + assert _bosch_a_direct_vrel(0, BOSCH_A_DIRECT_VREL_MAX_UNCERTAINTY_RAW + 1) is None + assert _bosch_a_direct_vrel(864, 0x3FE) is None + assert _bosch_a_direct_vrel(864, 0x3FF) is None + + def test_high_u10_live_vrel_without_trust_is_withheld(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=False)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=0, direct_vrel_uncertainty_raw=1023)) + assert len(rr.points) == 0 + assert len(ri._tracks[1].samples) == 1 + + def test_fast_clean_range_rate_without_u11_or_ratio_withholds_rather_than_synthesizes(self): + # This range rate (~-46 m/s) is well outside native U11's representable domain ([-13.5, 13.5] + # m/s), so it can only ever have come from the raw one-sweep derivative -- which is exactly the + # synthesized-measurement hazard this fix closes. With no aux at all (no U11, no ratio) and no + # prior trusted velocity to coast, the point is correctly withheld rather than published at a + # fabricated, out-of-band rate. + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 2000, 1024, 1, 0, with_aux=False)) + ri.update(sweep(0, 1, 0x7, 1945, 1024, 3, 70_000_000, with_aux=False)) + rr = ri.update(sweep(0, 2, 0x7, 1889, 1024, 5, 140_000_000, with_aux=False)) + assert len(rr.points) == 0 + assert len(ri._tracks[1].samples) == 1 # only the birth sample; later cycles coast, not append + + def test_high_u10_live_vrel_coasts_recent_trusted_motion(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1724, 1024, 1, 0, with_aux=True, + direct_vrel_raw=696, direct_vrel_uncertainty_raw=84)) + rr = ri.update(sweep(0, 1, 0x7, 1724, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=696, direct_vrel_uncertainty_raw=84)) + assert rr.points[0].vRel == pytest.approx(-2.625) + # R146/S21 ID50's 19-count / 68.962 ms high-U10 step would have synthesized about -15.74 m/s. + rr = ri.update(sweep(0, 2, 0x7, 1705, 1024, 5, 118_962_000, with_aux=True, + direct_vrel_raw=585, direct_vrel_uncertainty_raw=744)) + assert len(rr.points) == 1 + assert rr.points[0].dRel == pytest.approx(1705 * BOSCH_A_RANGE_SCALE_M - 3.0) + assert rr.points[0].vRel == pytest.approx(-2.625) + assert not rr.points[0].measured + assert len(ri._tracks[1].samples) == 2 + + def test_qualified_u11_recovers_immediately_after_high_u10_coast(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=84)) + ri.update(sweep(0, 1, 0x7, 1000, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=84)) + ri.update(sweep(0, 2, 0x7, 981, 1024, 5, 100_000_000, with_aux=True, + direct_vrel_raw=585, direct_vrel_uncertainty_raw=744)) + rr = ri.update(sweep(0, 3, 0x7, 980, 1024, 7, 150_000_000, with_aux=True, + direct_vrel_raw=760, direct_vrel_uncertainty_raw=84)) + assert rr.points[0].vRel == pytest.approx((760 - 864) / 64.0) + assert rr.points[0].measured + assert ri._tracks[1].last_trusted_vrel == pytest.approx((760 - 864) / 64.0) + + def test_high_u10_coast_cannot_cross_lifecycle_incarnation(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=84)) + ri.update(sweep(0, 1, 0x7, 1000, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=84)) + rr = ri.update(sweep(0, 2, 0x7, 981, 1024, 99, 100_000_000, with_aux=True, + direct_vrel_raw=585, direct_vrel_uncertainty_raw=744)) + assert len(rr.points) == 0 + assert ri._tracks[1].last_trusted_vrel is None + + def test_high_u10_coast_expires_with_trusted_motion_age(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=84)) + ri.update(sweep(0, 1, 0x7, 1000, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=84)) + ri.update(sweep(0, 2, 0x7, 981, 1024, 5, 100_000_000, with_aux=True, + direct_vrel_raw=585, direct_vrel_uncertainty_raw=744)) + rr = ri.update(sweep(0, 3, 0x7, 980, 1024, 7, 300_000_000, with_aux=True, + direct_vrel_raw=585, direct_vrel_uncertainty_raw=744)) + assert len(rr.points) == 0 + + def test_range_ratio_conversion_and_sentinel(self): + assert _bosch_a_range_ratio(500) == pytest.approx(1.0) + assert _bosch_a_range_ratio(509) == pytest.approx(1.009) + assert _bosch_a_range_ratio(BOSCH_A_RANGE_RATIO_INVALID) is None + assert _bosch_a_range_ratio(None) is None + assert _bosch_a_range_ratio_vrel(600, 10.0, 0.1) == pytest.approx(-10.0) + + @pytest.mark.parametrize( + ('direct_raw', 'range_raw', 'rawca', 'expected'), + [(0, 974, 528, -13.5), (BOSCH_A_DIRECT_VREL_MAX_RAW, 1027, 472, 13.5)], + ) + def test_range_ratio_does_not_extend_direct_vrel_rails(self, direct_raw, range_raw, rawca, expected): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=direct_raw, direct_vrel_uncertainty_raw=80, rawca=500)) + rr = ri.update(sweep(0, 1, 0x7, range_raw, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=direct_raw, direct_vrel_uncertainty_raw=80, rawca=rawca)) + assert len(rr.points) == 1 + assert rr.points[0].vRel == pytest.approx(expected) + + def test_first_sighting_vrel_is_zero(self): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + assert len(rr.points) == 0 + + def test_decreasing_range_gives_negative_vrel(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 2000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1990, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].vRel < 0 + + def test_increasing_range_gives_positive_vrel(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=928, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].vRel > 0 + + def test_vrel_reflects_native_u11_not_raw_range_derivative(self): + # The raw one-sweep (dRel-previous_range)/dt derivative is no longer an authoritative vRel source + # (see TestFallbackNeverPublishes below) -- confirm the published value tracks native U11 exactly, + # not the range-implied rate, even when the two would clearly disagree. + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + d1 = 0.05712 * 1000 - 3.0 + d2 = 0.05712 * 1010 - 3.0 + range_implied_vrel = (d2 - d1) / 0.05 + assert range_implied_vrel != pytest.approx(0.0) + assert rr.points[0].vRel == pytest.approx(0.0) # native U11 (864 -> 0 m/s), not the range rate + + def test_incarnation_does_not_carry_velocity_across_lifecycle_break(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 2000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1990, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].vRel < 0 + # replacement: life breaks identity -> fresh incarnation; its first sample is withheld. + rr = ri.update(sweep(0, 2, 0x7, 500, 1024, 99, 100_000_000)) + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 3, 0x7, 500, 1024, 101, 150_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].vRel == 0.0 + + def test_discontinuous_range_coasts_last_accepted_point_unmeasured(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=80, rawca=500)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=80, rawca=500)) + assert len(rr.points) == 1 + accepted_drel = rr.points[0].dRel + + rr = ri.update(sweep(0, 2, 0x7, 100, 1024, 5, 100_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=80, rawca=509, + range_sigma_raw=4, existence_raw=0)) + assert len(rr.points) == 1 + assert rr.points[0].dRel == pytest.approx(accepted_drel) + assert rr.points[0].measured is False + assert len(ri._tracks[1].samples) == 2 + + def test_exact_peter_reset_sequence_never_rebases_on_rejected_ranges(self): + ri = make_radar_interface() + rows = [ + # range_raw, U11, U10, range sigma, existence, 00CA + (203, 713, 136, 2, 3, 512), + (198, 740, 92, 2, 76, 510), + (64, 463, 612, 4, 0, 509), + (82, 554, 437, 4, 0, 508), + (96, 727, 101, 2, 0, 508), + (102, 795, 31, 1, 0, 508), + ] + times = [0, 70_696_000, 120_413_000, 200_552_000, 260_482_000, 331_286_000] + + rr = None + for i, ((range_raw, u11, u10, sigma, existence, rawca), t_nanos) in enumerate(zip(rows, times, strict=True)): + rr = ri.update(sweep( + 0, (11 + i) & 0xF, 0x9, range_raw, 1024, 5 + 2 * i, t_nanos, + with_aux=True, direct_vrel_raw=u11, direct_vrel_uncertainty_raw=u10, + rawca=rawca, range_sigma_raw=sigma, existence_raw=existence, track_id=23, + )) + + assert rr is not None + assert len(rr.points) == 0 + assert len(ri._tracks[23].samples) == 2 + assert ri._tracks[23].samples[-1][1] == pytest.approx(8.30976) + + +# --- 7b. residual vRel-authority fix: raw one-sweep fallback never becomes a published measurement ---- +# (U11 genuinely unavailable -- sentinel/out-of-range -- is a different path than the high-u10-but-live +# case above; see radar_interface.py's u11_and_ratio_unavailable branch.) + +class TestFallbackNeverPublishes: + def test_invalid_u11_and_ratio_with_trusted_history_coasts_not_derivative(self): + """TEST 1: invalid U11 + unusable ratio + a recent trusted velocity on record. A large range jump + that would imply a big closing velocity via the raw derivative must not reach vRel/measured/KF -- + it should coast the trusted value instead.""" + ri = make_radar_interface() + # Establish trust: two qualified-U11 samples at a steady range. + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + rr = ri.update(sweep(0, 1, 0x7, 1000, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].measured is True + assert rr.points[0].vRel == pytest.approx(0.0) + + # Third sweep: U11 sentinel (unavailable), ratio invalid, and a range jump that -- via the raw + # (dRel-previous_range)/dt derivative -- would imply a closing velocity of about -17 m/s over 50ms. + # Deliberately kept under the 50 m/s generic range-rejection ceiling so this exercises the new + # u11_and_ratio_unavailable coast path specifically, not the pre-existing range_rejected path. + rr = ri.update(sweep(0, 2, 0x7, 985, 1024, 5, 100_000_000, with_aux=True, + direct_vrel_raw=BOSCH_A_DIRECT_VREL_INVALID, direct_vrel_uncertainty_raw=0, + rawca=BOSCH_A_RANGE_RATIO_INVALID)) + implied_fallback_vrel = (985 - 1000) * BOSCH_A_RANGE_SCALE_M / 0.05 + assert implied_fallback_vrel == pytest.approx(-17.136) # confirms the setup would poison, if reached + assert abs(implied_fallback_vrel) < BOSCH_A_FALLBACK_RANGE_RATE_MAX_MPS # and clears range_rejected + assert len(rr.points) == 1 + assert rr.points[0].measured is False + assert rr.points[0].vRel == pytest.approx(0.0) # coasted trusted value, not the -17 m/s derivative + # The KF-facing measurement update never happened: last_trusted_vrel/age is untouched by this cycle. + assert ri._tracks[1].last_trusted_vrel == pytest.approx(0.0) + assert ri._tracks[1].last_trusted_vrel_nanos == 50_000_000 + + def test_invalid_u11_and_ratio_without_trusted_history_withholds_point(self): + """TEST 2: invalid U11 + unusable ratio + no prior trusted velocity. No fabricated point.""" + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=BOSCH_A_DIRECT_VREL_INVALID, direct_vrel_uncertainty_raw=0)) + rr = ri.update(sweep(0, 1, 0x7, 985, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=BOSCH_A_DIRECT_VREL_INVALID, direct_vrel_uncertainty_raw=0, + rawca=BOSCH_A_RANGE_RATIO_INVALID)) + assert len(rr.points) == 0 + + def test_invalid_u11_with_valid_ratio_still_publishes_via_ratio(self): + """TEST 3: invalid U11 + a valid ratio field. Untouched by this fix -- still publishes via + ratio_vrel, exactly as before. rawca=520 is chosen so the ratio-implied residual (~1.08m) clears + innovation checking, so this exercises the ratio_vrel publish path and not range_rejected.""" + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=BOSCH_A_DIRECT_VREL_INVALID, direct_vrel_uncertainty_raw=0, + rawca=500)) + rr = ri.update(sweep(0, 1, 0x7, 1000, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=BOSCH_A_DIRECT_VREL_INVALID, direct_vrel_uncertainty_raw=0, + rawca=520)) + d = 0.05712 * 1000 - 3.0 + ratio = 0.5 + 0.001 * 520 + residual = abs(d - d * ratio) + assert residual < 2.0 # clears innovation checking regardless of degraded status + assert len(rr.points) == 1 + assert rr.points[0].measured is True + expected = d * (1.0 - ratio) / 0.05 + assert rr.points[0].vRel == pytest.approx(expected) + + +# --- 8. auxiliary tag join: enrichment only, never gates validity ------------------------------------- + +class TestAuxiliary: + def test_aux_matching_cycle_is_attached(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, aux_frame_idx=0, rawc9=123, rawca=456)) + st = ri._slots[0] + assert st.logical_00c9_raw == 123 + assert st.logical_00ca_raw == 456 + + def test_aux_mismatched_cycle_not_attached_and_point_withheld_without_vrel_source(self): + # A mismatched aux frame_idx means the AUX payload (U11, ratio, and the c9/ca enrichment fields) + # is never attached for that cycle -- it's as if aux were absent. With no vRel source and no prior + # trusted velocity, the point is correctly withheld rather than synthesized from the range alone. + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, aux_frame_idx=5, rawc9=123, rawca=456)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, aux_frame_idx=5, rawc9=123, rawca=456)) + assert len(rr.points) == 0 + st = ri._slots[0] + assert math.isnan(st.logical_00c9_raw) and math.isnan(st.logical_00ca_raw) # never attached + + def test_aux_absent_without_trust_withholds_point(self): + # Aux absence alone no longer implies "fall back to the raw range derivative" -- with no U11, no + # ratio, and no prior trusted velocity to coast, the point is withheld rather than fabricated. + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=False)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=False)) + assert len(rr.points) == 0 + + def test_aux_absent_does_not_suppress_point_once_trust_is_established(self): + # A subsequent aux-absent sweep does NOT suppress a point once a recent trusted velocity exists -- + # it coasts that trusted value (measured=False) rather than withholding the point outright. + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + ri.update(sweep(0, 1, 0x7, 1000, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + rr = ri.update(sweep(0, 2, 0x7, 1000, 1024, 5, 100_000_000, with_aux=False)) + assert len(rr.points) == 1 + assert rr.points[0].vRel == pytest.approx(0.0) + assert rr.points[0].measured is False + + def test_aux_param_invalid_sentinel(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, aux_frame_idx=0, rawc9=1, rawca=0x3FF)) + st = ri._slots[0] + assert st.logical_00c9_raw == 1 + assert math.isnan(st.logical_00ca_raw) + + def test_sigma_does_not_share_aux_param_invalid_sentinel(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, with_aux=True, + aux_frame_idx=0, rawc9=0x3FF, rawca=500)) + st = ri._slots[0] + assert st.logical_00c9_raw == 0x3FF + assert st.logical_00ca_raw == 500 + + +# --- 9. missing CAN frame within a cycle does not kill an existing point ------------------------------- + +def test_incomplete_main_frame_set_leaves_existing_point_untouched(): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + t0 = rr.points[0].trackId + + # Next cycle: only f0/f1/f2 arrive for slot 0 (f3 missing) -- still send the trigger so update() runs. + f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[0] + _, _, _, trig_f3 = BOSCH_A_MAIN_IDS[15] + frames = [ + CanData(f0, make_f0(1, 0x7, 1000, 1024), BUS), + CanData(f1, make_f1(1), BUS), + CanData(f2, make_f2(1, 3), BUS), + CanData(trig_f3, make_f3(1), BUS), + ] + rr = ri.update([(50_000_000, frames)]) + assert len(rr.points) == 1 # untouched, not dropped + assert rr.points[0].trackId == t0 + + +def test_incoherent_frame_index_across_main_frames_is_skipped(): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + t0 = rr.points[0].trackId + + f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[0] + _, _, _, trig_f3 = BOSCH_A_MAIN_IDS[15] + frames = [ + CanData(f0, make_f0(1, 0x7, 1000, 1024), BUS), + CanData(f1, make_f1(1), BUS), + CanData(f2, make_f2(2, 3), BUS), # frame idx mismatch vs f0/f1 + CanData(f3, make_f3(1), BUS), + CanData(trig_f3, make_f3(1), BUS), + ] + rr = ri.update([(50_000_000, frames)]) + assert len(rr.points) == 1 # unchanged from before, not re-derived, not dropped + assert rr.points[0].trackId == t0 + + +# --- 10. staleness gate ----------------------------------------------------------------------------- + +def test_stale_bus_clears_points_and_flags_temporary_unavailable(): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + + # Advance the parser clock well past BOSCH_A_STALE_S with no trigger frame at all. + f0, f1, f2, f3 = BOSCH_A_MAIN_IDS[0] + frames = [CanData(f0, make_f0(1, 0x7, 1000, 1024), BUS)] # not a full/coherent set, and no trigger + rr = ri.update([(300_000_000, frames)]) # +300ms, no trigger msg present + assert rr is not None + assert len(rr.points) == 0 + assert rr.errors.radarUnavailableTemporary is True + + +def test_stale_bus_clears_pending_birth_history_before_maturity(): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0)) + assert len(rr.points) == 0 + assert len(ri._tracks[1].samples) == 1 + + # The birth has started, but no RadarPoint exists yet. A silent bus must still clear the pending + # lifecycle/history state rather than allowing it to survive indefinitely. + f0, _, _, _ = BOSCH_A_MAIN_IDS[0] + rr = ri.update([(300_000_000, [CanData(f0, make_f0(1, 0x7, 1000, 1024), BUS)])]) + assert rr is not None + assert len(rr.points) == 0 + assert rr.errors.radarUnavailableTemporary is True + assert not ri._tracks + + +# --- 11. persistent CAN identity is separate from wire-slot assembly ------------------------------- + +class TestPersistentCanIdentity: + def test_same_slot_same_can_identity_is_stable(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, track_id=42)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, track_id=42, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert rr.points[0].trackId == 42 + assert set(ri._tracks) == {42} + + def test_slot_migration_preserves_identity_and_ols_history(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, track_id=23)) + rr = ri.update(sweep(1, 1, 0x7, 1010, 1024, 3, 50_000_000, track_id=23, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert rr.points[0].trackId == 23 + assert len(ri._tracks[23].samples) == 2 + assert ri._tracks[23].wire_slot == 1 + + def test_slot_zero_one_zero_keeps_one_logical_track(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, track_id=17)) + ri.update(sweep(1, 1, 0x7, 1010, 1024, 3, 50_000_000, track_id=17, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + rr = ri.update(sweep(0, 2, 0x7, 1020, 1024, 5, 100_000_000, track_id=17, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert [point.trackId for point in rr.points] == [17] + assert len(ri._tracks) == 1 + assert len(ri._tracks[17].samples) == 3 + assert ri._tracks[17].wire_slot == 0 + + def test_same_slot_replacement_preserves_returning_identity_history(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, track_id=2)) + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, track_id=2, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert [point.trackId for point in rr.points] == [2] + assert len(ri._tracks[2].samples) == 2 + + # Bosch can put a different persistent object in the same wire slot while the slot's lifecycle + # counter continues. The old logical track is not dead: retain its state until its own staleness + # deadline so it can resume without a synthetic birth or an OLS reset. + rr = ri.update(sweep(0, 2, 0x7, 1500, 1024, 5, 100_000_000, track_id=63)) + assert set(ri._tracks) == {2, 63} + assert len(ri._tracks[2].samples) == 2 + assert len(rr.points) == 0 + assert 2 not in ri.pts + + rr = ri.update(sweep(0, 3, 0x7, 1020, 1024, 7, 150_000_000, track_id=2, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert [point.trackId for point in rr.points] == [2] + assert len(ri._tracks[2].samples) == 3 + assert math.isfinite(rr.points[0].vRel) + + def test_two_wire_slots_with_different_ids_publish_two_points(self): + ri = make_radar_interface() + first_extra = make_main_frames(1, 0, 0x7, 1400, 1024, 11, track_id=22) + ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, extra_slots=first_extra, track_id=11)) + second_extra = make_main_frames(1, 1, 0x7, 1410, 1024, 13, track_id=22) + [ + CanData(BOSCH_A_AUX_IDS[1], make_aux(1, direct_vrel_raw=864, direct_vrel_uncertainty_raw=0), BUS), + ] + rr = ri.update(sweep(0, 1, 0x7, 1010, 1024, 3, 50_000_000, + extra_slots=second_extra, track_id=11, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert {point.trackId for point in rr.points} == {11, 22} + assert len(rr.points) == 2 + + @pytest.mark.parametrize('track_id', [0, 0xFF, 0x40, 0xA5]) + def test_invalid_can_identity_never_creates_logical_track(self, track_id): + ri = make_radar_interface() + rr = ri.update(sweep(0, 0, 0x7, 1000, 1024, 1, 0, track_id=track_id)) + assert len(rr.points) == 0 + assert not ri._tracks + + def test_track_id_reuse_after_death_starts_with_clean_history(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 2000, 1024, 1, 0, track_id=9)) + rr = ri.update(sweep(0, 1, 0x7, 1990, 1024, 3, 50_000_000, track_id=9, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].trackId == 9 + rr = ri.update(sweep(0, 2, 0xF, 1900, 1024, 5, 100_000_000, track_id=9)) + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 3, 0x7, 1000, 1024, 1, 150_000_000, track_id=9)) + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 4, 0x7, 1000, 1024, 3, 200_000_000, track_id=9, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert rr.points[0].trackId == 9 + assert rr.points[0].vRel == 0.0 + + def test_lifecycle_discontinuity_keeps_can_id_but_clears_derivative(self): + ri = make_radar_interface() + ri.update(sweep(0, 0, 0x7, 2000, 1024, 1, 0, track_id=31)) + rr = ri.update(sweep(0, 1, 0x7, 1990, 1024, 3, 50_000_000, track_id=31, with_aux=True, + direct_vrel_raw=800, direct_vrel_uncertainty_raw=0)) + assert rr.points[0].vRel < 0 + rr = ri.update(sweep(0, 2, 0x7, 500, 1024, 99, 100_000_000, track_id=31)) + assert len(rr.points) == 0 + rr = ri.update(sweep(0, 3, 0x7, 500, 1024, 101, 150_000_000, track_id=31, with_aux=True, + direct_vrel_raw=864, direct_vrel_uncertainty_raw=0)) + assert len(rr.points) == 1 + assert rr.points[0].trackId == 31 + assert rr.points[0].vRel == 0.0 + + +def test_no_can_data_returns_none_without_crash(): + ri = make_radar_interface() + assert ri.update([]) is None + + +def test_bosch_a_azimuth_scale_is_exact_firmware_scale(): + assert BOSCH_A_AZIMUTH_SCALE_RAD == pytest.approx(1.0 / 2048.0) + + +def test_bosch_a_observed_sweep_end_and_publish_trigger_are_distinct(): + # 0x297 is the observed final object-family frame, but 0x2FF intentionally + # remains the publish trigger while companion data is optional. + assert BOSCH_A_TRIGGER_MSG == 0x2FF + assert BOSCH_A_SWEEP_END_MSG == 0x297 + + +def test_bosch_a_timing_contract(): + assert BOSCH_A_FREQ_HZ == 15 + assert BOSCH_A_STALE_S == pytest.approx(0.20) + + +# --- misc integration: DBC wiring ------------------------------------------------------------------- + +def test_civic_bosch_radar_dbc_wired_and_available(): + assert CP.radarUnavailable is False + ri = make_radar_interface() + assert ri.bosch_a_radar is True + assert ri.rcp is not None + + +def test_civic_bosch_object_feed_uses_camera_side_acc_can(): + ri = make_radar_interface() + can = CanBus(CP) + + assert ri.rcp.bus == can.camera + assert ri.rcp.bus != can.radar + + +# Computed once at collection time, like CP above -- the openpilot_function_fixture in the root +# conftest.py gives every test function its own fresh, isolated OpenpilotPrefix(), so a +# get_non_essential_params() call made from *inside* a test body runs in an environment where the +# HondaBoschARadar=True set at module import time (before any fixture exists) was never written. +# Computing these at module scope, in the same environment CP uses, sidesteps that entirely. +_OPEN_BOSCH_A_CARS = [CAR.HONDA_CRV_5G, CAR.HONDA_CRV_HYBRID, CAR.HONDA_ACCORD, CAR.HONDA_INSIGHT, CAR.ACURA_RDX_3G] +_OPEN_BOSCH_A_CPS = {car: CarInterface.get_non_essential_params(car) for car in _OPEN_BOSCH_A_CARS} + +# radarless and CANFD Bosch platforms respectively -- same HONDA_BOSCH family as Civic Bosch, but +# excluded from HONDA_BOSCH_A, so the gate must stay closed regardless of the tester toggle. +_CLOSED_BOSCH_A_CARS = [CAR.HONDA_CIVIC_2022, CAR.HONDA_ACCORD_11G] +_CLOSED_BOSCH_A_CPS = {car: CarInterface.get_non_essential_params(car) for car in _CLOSED_BOSCH_A_CARS} + + +@pytest.mark.parametrize("car", _OPEN_BOSCH_A_CARS) +def test_bosch_a_gate_open_to_other_bosch_a_platforms(car): + cp = _OPEN_BOSCH_A_CPS[car] + assert cp.radarUnavailable is False + from opendbc.car.honda.radar_interface import RadarInterface + ri = RadarInterface(cp) + assert ri.bosch_a_radar is True + assert ri.rcp is not None + + +@pytest.mark.parametrize("car", _CLOSED_BOSCH_A_CARS) +def test_bosch_a_gate_stays_closed_for_non_bosch_a_platforms(car): + cp = _CLOSED_BOSCH_A_CPS[car] + assert cp.radarUnavailable is True diff --git a/opendbc_repo/opendbc/car/honda/values.py b/opendbc_repo/opendbc/car/honda/values.py index 34fb86ab2..f9292f2e2 100644 --- a/opendbc_repo/opendbc/car/honda/values.py +++ b/opendbc_repo/opendbc/car/honda/values.py @@ -120,11 +120,17 @@ class HondaCarDocs(CarDocs): self.car_parts = CarParts.common([harness]) + if CP.alphaLongitudinalAvailable: + self.footnotes.append(Footnote.EXP_LONG) + class Footnote(Enum): CIVIC_DIESEL = CarFootnote( "2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.", Column.FSR_STEERING) + EXP_LONG = CarFootnote( + "Enabling longitudinal control (alpha) will disable all CMBS functionality, including AEB and FCW.", + Column.LONGITUDINAL) @dataclass @@ -165,7 +171,12 @@ class CAR(Platforms): HondaCarDocs("Honda N-Box 2018", "All", min_steer_speed=5.), ], CarSpecs(mass=890., wheelbase=2.520, steerRatio=18.64), - {Bus.pt: 'acura_rdx_2020_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'}, ) HONDA_ACCORD = HondaBoschPlatformConfig( [ @@ -175,7 +186,12 @@ class CAR(Platforms): ], # steerRatio: 11.82 is spec end-to-end CarSpecs(mass=3279 * CV.LB_TO_KG, wheelbase=2.83, steerRatio=16.33, centerToFrontRatio=0.39, tireStiffnessFactor=0.8467), - {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'}, flags=HondaFlags.ALLOW_MANUAL_TRANS, ) HONDA_ACCORD_11G = HondaBoschCANFDPlatformConfig( @@ -194,12 +210,22 @@ class CAR(Platforms): HondaCarDocs("Honda Civic Hatchback 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS), ], CarSpecs(mass=1326, wheelbase=2.7, steerRatio=15.38, centerToFrontRatio=0.4), # steerRatio: 10.93 is end-to-end spec - {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'}, ) HONDA_CIVIC_BOSCH_DIESEL = HondaBoschPlatformConfig( [], # don't show in docs HONDA_CIVIC_BOSCH.specs, - {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'}, ) HONDA_CIVIC_2022 = HondaBoschPlatformConfig( [ @@ -218,7 +244,12 @@ class CAR(Platforms): [HondaCarDocs("Honda CR-V 2017-22", min_steer_speed=15. * CV.MPH_TO_MS)], # steerRatio: 12.3 is spec end-to-end CarSpecs(mass=3410 * CV.LB_TO_KG, wheelbase=2.66, steerRatio=16.0, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), - {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated', Bus.radar: 'honda_bosch_a_radar'}, flags=HondaFlags.BOSCH_ALT_BRAKE, ) HONDA_CRV_6G = HondaBoschCANFDPlatformConfig( @@ -232,7 +263,12 @@ class CAR(Platforms): [HondaCarDocs("Honda CR-V Hybrid 2017-22", min_steer_speed=12. * CV.MPH_TO_MS)], # mass: mean of 4 models in kg, steerRatio: 12.3 is spec end-to-end CarSpecs(mass=1667, wheelbase=2.66, steerRatio=16, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), - {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_bosch_a_radar'}, ) HONDA_HRV_3G = HondaBoschPlatformConfig( [HondaCarDocs("Honda HR-V 2023-25", "All")], @@ -249,7 +285,12 @@ class CAR(Platforms): ACURA_RDX_3G = HondaBoschPlatformConfig( [HondaCarDocs("Acura RDX 2019-21", "All", min_steer_speed=3. * CV.MPH_TO_MS)], CarSpecs(mass=4068 * CV.LB_TO_KG, wheelbase=2.75, steerRatio=11.95, centerToFrontRatio=0.41, tireStiffnessFactor=0.677), # as spec - {Bus.pt: 'acura_rdx_2020_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'}, flags=HondaFlags.BOSCH_ALT_BRAKE, ) ACURA_RDX_3G_MMR = HondaBoschPlatformConfig( @@ -261,17 +302,32 @@ class CAR(Platforms): HONDA_INSIGHT = HondaBoschPlatformConfig( [HondaCarDocs("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)], CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.0, centerToFrontRatio=0.39, tireStiffnessFactor=0.82), # as spec - {Bus.pt: 'honda_insight_ex_2019_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'honda_insight_ex_2019_can_generated', Bus.radar: 'honda_bosch_a_radar'}, ) HONDA_E = HondaBoschPlatformConfig( [HondaCarDocs("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS)], CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82), - {Bus.pt: 'acura_rdx_2020_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'}, ) HONDA_E_ADVANCE = HondaBoschPlatformConfig( [], # don't show in docs, base trim already in docs CarSpecs(mass=1527, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82), - {Bus.pt: 'honda_e_advance_2020_can_generated'}, + # Bus.radar = the hand-written 16-slot Bosch-A object bank DBC (see radar_interface.py for the decode). + # RX-parse only; the decode itself takes no CAN authority, so factory AEB/CMBS/FCW stay live as long as + # stock longitudinal remains in control. Enabling openpilot longitudinal (alpha) disables all CMBS + # functionality, including AEB and FCW -- see Footnote.EXP_LONG and CarInterface.init()'s UDS + # CommunicationControl call, which suppresses the stock radar's longitudinal TX for the same reason. + {Bus.pt: 'honda_e_advance_2020_can_generated', Bus.radar: 'honda_bosch_a_radar'}, ) HONDA_PILOT_4G = HondaBoschCANFDPlatformConfig( [HondaCarDocs("Honda Pilot 2023-25", "All")], @@ -472,6 +528,10 @@ HONDA_BOSCH = CAR.with_flags(HondaFlags.BOSCH) HONDA_BOSCH_RADARLESS = CAR.with_flags(HondaFlags.BOSCH_RADARLESS) HONDA_BOSCH_CANFD = CAR.with_flags(HondaFlags.BOSCH_CANFD) HONDA_BOSCH_ALT_RADAR = CAR.with_flags(HondaFlags.BOSCH_ALT_RADAR) +# Plain bosch_a harness: not CANFD, not radarless, not alt-radar. These platforms have a firmware-correct +# RadarInterface (16-slot Bosch-A object bank, RX-only, see radar_interface.py) available behind +# HondaBoschARadar; every other Bosch platform still has no parsed radar DBC and stays radarUnavailable. +HONDA_BOSCH_A = HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR HONDA_BOSCH_TJA_CONTROL = CAR.with_flags(HondaFlags.BOSCH_TJA_CONTROL) HONDA_CAMERA_MESSAGE_CARS = { CAR.HONDA_ACCORD, diff --git a/opendbc_repo/opendbc/dbc/honda_bosch_a_radar.dbc b/opendbc_repo/opendbc/dbc/honda_bosch_a_radar.dbc new file mode 100644 index 000000000..5e9da540d --- /dev/null +++ b/opendbc_repo/opendbc/dbc/honda_bosch_a_radar.dbc @@ -0,0 +1,1014 @@ +VERSION "honda_bosch_a_radar -- 16-slot Bosch-A object bank (16 CAN-visible object slots, each a 4-frame main burst f0..f3 + one synchronized companion frame). Hand-written (NOT generated by the opendbc generator pipeline). RANGE/AZIMUTH scaled companions are documentation-only; RadarInterface recomputes the physical values from RAW signals. Firmware descriptor-backed raw aliases are retained for traceability; semantic aliases are added only where firmware/capture evidence is strong. f3 contains angular bounds, matched edge uncertainty, and TRACK_ID; AUX contains direct relative velocity/uncertainty, the second edge uncertainty, and a capture-validated range-ratio candidate. FRAME_IDX / LIFECYCLE_RAW are intentionally not named COUNTER." + +NS_ : + NS_DESC_ + CM_ + BA_DEF_ + BA_ + VAL_ + CAT_DEF_ + CAT_ + FILTER + BA_DEF_DEF_ + EV_DATA_ + ENVVAR_DATA_ + SGTYPE_ + SGTYPE_VAL_ + BA_DEF_SGTYPE_ + BA_SGTYPE_ + SIG_TYPE_REF_ + VAL_TABLE_ + SIG_GROUP_ + SIG_VALTYPE_ + SIGTYPE_VALTYPE_ + BO_TX_BU_ + BA_DEF_REL_ + BA_REL_ + BA_DEF_DEF_REL_ + BU_SG_REL_ + BU_EV_REL_ + BU_BO_REL_ + SG_MUL_VAL_ + +BS_: + +BU_: RADAR XXX + + +BO_ 640 BOSCH_A_S00_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_25_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_26_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_28_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_29_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 641 BOSCH_A_S00_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_2B_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_18_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_2C_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_19_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_2D_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 642 BOSCH_A_S00_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_2F_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_1A_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_30_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_1B_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_1C_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 643 BOSCH_A_S00_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 712 BOSCH_A_S00_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 644 BOSCH_A_S01_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_34_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_35_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_37_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_38_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 645 BOSCH_A_S01_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_3A_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_23_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_3B_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_24_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_3C_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 646 BOSCH_A_S01_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_3E_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_25_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_3F_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_26_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_27_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 647 BOSCH_A_S01_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 713 BOSCH_A_S01_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 648 BOSCH_A_S02_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_43_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_44_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_46_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_47_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 649 BOSCH_A_S02_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_49_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_2E_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_4A_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_2F_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_4B_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 650 BOSCH_A_S02_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_4D_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_30_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_4E_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_31_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_32_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 651 BOSCH_A_S02_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 714 BOSCH_A_S02_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 652 BOSCH_A_S03_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_52_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_53_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_55_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_56_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 653 BOSCH_A_S03_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_58_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_39_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_59_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_3A_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_5A_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 654 BOSCH_A_S03_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_5C_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_3B_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_5D_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_3C_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_3D_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 655 BOSCH_A_S03_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 715 BOSCH_A_S03_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 720 BOSCH_A_S04_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_61_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_62_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_64_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_65_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 721 BOSCH_A_S04_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_67_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_44_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_68_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_45_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_69_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 722 BOSCH_A_S04_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_6B_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_46_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_6C_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_47_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_48_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 723 BOSCH_A_S04_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 716 BOSCH_A_S04_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 724 BOSCH_A_S05_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_70_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_71_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_73_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_74_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 725 BOSCH_A_S05_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_76_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_4F_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_77_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_50_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_78_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 726 BOSCH_A_S05_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_7A_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_51_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_7B_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_52_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_53_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 727 BOSCH_A_S05_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 717 BOSCH_A_S05_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 728 BOSCH_A_S06_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_7F_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_80_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_82_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_83_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 729 BOSCH_A_S06_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_85_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_5A_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_86_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_5B_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_87_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 730 BOSCH_A_S06_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_89_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_5C_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_8A_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_5D_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_5E_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 731 BOSCH_A_S06_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 718 BOSCH_A_S06_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 732 BOSCH_A_S07_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_8E_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_8F_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_91_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_92_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 733 BOSCH_A_S07_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_94_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_65_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_95_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_66_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_96_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 734 BOSCH_A_S07_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_98_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_67_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_99_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_68_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_69_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 735 BOSCH_A_S07_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 719 BOSCH_A_S07_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 736 BOSCH_A_S08_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_9D_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_9E_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_A0_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_A1_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 737 BOSCH_A_S08_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_A3_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_70_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_A4_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_71_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_A5_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 738 BOSCH_A_S08_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_A7_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_72_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_A8_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_73_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_74_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 739 BOSCH_A_S08_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 656 BOSCH_A_S08_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 740 BOSCH_A_S09_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_AC_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_AD_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_AF_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_B0_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 741 BOSCH_A_S09_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_B2_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_7B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_B3_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_7C_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_B4_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 742 BOSCH_A_S09_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_B6_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_7D_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_B7_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_7E_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_7F_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 743 BOSCH_A_S09_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 657 BOSCH_A_S09_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 744 BOSCH_A_S10_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_BB_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_BC_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_BE_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_BF_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 745 BOSCH_A_S10_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_C1_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_86_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_C2_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_87_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_C3_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 746 BOSCH_A_S10_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_C5_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_88_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_C6_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_89_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_8A_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 747 BOSCH_A_S10_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 658 BOSCH_A_S10_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 748 BOSCH_A_S11_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_CA_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_CB_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_CD_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_CE_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 749 BOSCH_A_S11_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_D0_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_91_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_D1_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_92_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_D2_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 750 BOSCH_A_S11_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_D4_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_93_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_D5_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_94_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_95_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 751 BOSCH_A_S11_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 659 BOSCH_A_S11_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 752 BOSCH_A_S12_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_D9_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_DA_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_DC_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_DD_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 753 BOSCH_A_S12_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_DF_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_9C_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_E0_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_9D_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_E1_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 754 BOSCH_A_S12_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_E3_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_9E_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_E4_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_9F_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_A0_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 755 BOSCH_A_S12_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 660 BOSCH_A_S12_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 756 BOSCH_A_S13_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_E8_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_E9_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_EB_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_EC_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 757 BOSCH_A_S13_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_EE_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_A7_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_EF_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_A8_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_F0_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 758 BOSCH_A_S13_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_F2_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_A9_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_F3_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_AA_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_AB_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 759 BOSCH_A_S13_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 661 BOSCH_A_S13_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 760 BOSCH_A_S14_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_F7_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_F8_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_FA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_FB_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 761 BOSCH_A_S14_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_FD_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_B2_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_FE_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_B3_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_FF_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 762 BOSCH_A_S14_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_01_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_B4_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_02_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_B5_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_B6_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 763 BOSCH_A_S14_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 662 BOSCH_A_S14_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + +BO_ 764 BOSCH_A_S15_F0: 8 RADAR + SG_ STATUS : 15|4@0+ (1,0) [0|15] "" XXX + SG_ FRAME_IDX : 27|4@0+ (1,0) [0|15] "" XXX + SG_ RANGE_RAW : 23|12@0+ (1,0) [0|4095] "" XXX + SG_ RANGE : 23|12@0+ (0.05712,-3.0) [-3.0|231.7965] "m" XXX + SG_ AZIMUTH_RAW : 39|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_RAD : 39|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_DEG : 39|11@0+ (0.0279764548403722,-28.6478897565412) [-28.6478897565412|28.6199133017008] "deg" XXX + SG_ FW_LID_06_RAW : 44|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_07_RAW : 11|2@0+ (1,0) [0|3] "" XXX + SG_ FW_LID_09_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ RANGE_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_0A_RAW : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 765 BOSCH_A_S15_F1: 8 RADAR + SG_ FRAME_IDX : 28|4@0+ (1,0) [0|15] "" XXX + SG_ FW_LID_0C_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ OBJECT_EXISTENCE_PROBABILITY_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_BD_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ ANGULAR_WIDTH_DEG : 23|11@0+ (0.03125,0) [0|63.96875] "deg" XXX + SG_ FW_LID_0D_RAW : 15|8@0+ (1,0) [0|255] "" XXX + SG_ FW_LID_BE_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_0E_RAW : 7|6@0+ (1,0) [0|63] "" XXX + +BO_ 766 BOSCH_A_S15_F2: 8 RADAR + SG_ FRAME_IDX : 11|4@0+ (1,0) [0|15] "" XXX + SG_ LIFECYCLE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX + SG_ FW_LID_10_RAW : 29|1@0+ (1,0) [0|1] "" XXX + SG_ FW_LID_BF_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ NORMALIZED_CLOSING : 23|10@0+ (0.015625,-8.0) [-8.0|7.984375] "" XXX + SG_ FW_LID_11_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ NORMALIZED_CLOSING_SIGMA_RAW : 46|7@0+ (1,0) [0|127] "" XXX + SG_ FW_LID_C0_RAW : 39|9@0+ (1,0) [0|511] "" XXX + SG_ FW_LID_C1_RAW : 55|9@0+ (1,0) [0|511] "" XXX + +BO_ 767 BOSCH_A_S15_F3: 8 RADAR + SG_ AZIMUTH_EDGE_A_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_A_RAD : 7|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ AZIMUTH_EDGE_B_RAW : 23|11@0+ (1,0) [0|2047] "" XXX + SG_ AZIMUTH_EDGE_B_RAD : 23|11@0+ (0.00048828125,-0.5) [-0.5|0.49951171875] "rad" XXX + SG_ AZIMUTH_EDGE_SIGMA_A_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ TRACK_ID : 55|8@0+ (1,0) [0|255] "" XXX + +BO_ 663 BOSCH_A_S15_AUX: 8 RADAR + SG_ FRAME_IDX : 12|4@0+ (1,0) [0|15] "" XXX + SG_ REL_VELOCITY_RAW : 7|11@0+ (1,0) [0|2047] "" XXX + SG_ REL_VELOCITY_UNCERTAINTY_RAW : 23|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00C9_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ AZIMUTH_EDGE_SIGMA_B_RAW : 39|10@0+ (1,0) [0|1023] "" XXX + SG_ FW_LID_00CA_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX + SG_ RANGE_RATIO : 55|10@0+ (0.001,0.5) [0.5|1.523] "" XXX + + +CM_ BO_ 640 "Slot 0 frame 0 (range/status/azimuth/frame-index). 0x280."; +CM_ BO_ 641 "Slot 0 frame 1 (frame-index only, core contract). 0x281."; +CM_ BO_ 642 "Slot 0 frame 2 (lifecycle/frame-index). 0x282."; +CM_ BO_ 643 "Slot 0 frame 3 (frame-index only, core contract). 0x283."; +CM_ BO_ 712 "Slot 0 auxiliary frame (enrichment only, never gates validity). 0x2C8."; +CM_ BO_ 644 "Slot 1 frame 0 (range/status/azimuth/frame-index). 0x284."; +CM_ BO_ 645 "Slot 1 frame 1 (frame-index only, core contract). 0x285."; +CM_ BO_ 646 "Slot 1 frame 2 (lifecycle/frame-index). 0x286."; +CM_ BO_ 647 "Slot 1 frame 3 (frame-index only, core contract). 0x287."; +CM_ BO_ 713 "Slot 1 auxiliary frame (enrichment only, never gates validity). 0x2C9."; +CM_ BO_ 648 "Slot 2 frame 0 (range/status/azimuth/frame-index). 0x288."; +CM_ BO_ 649 "Slot 2 frame 1 (frame-index only, core contract). 0x289."; +CM_ BO_ 650 "Slot 2 frame 2 (lifecycle/frame-index). 0x28A."; +CM_ BO_ 651 "Slot 2 frame 3 (frame-index only, core contract). 0x28B."; +CM_ BO_ 714 "Slot 2 auxiliary frame (enrichment only, never gates validity). 0x2CA."; +CM_ BO_ 652 "Slot 3 frame 0 (range/status/azimuth/frame-index). 0x28C."; +CM_ BO_ 653 "Slot 3 frame 1 (frame-index only, core contract). 0x28D."; +CM_ BO_ 654 "Slot 3 frame 2 (lifecycle/frame-index). 0x28E."; +CM_ BO_ 655 "Slot 3 frame 3 (frame-index only, core contract). 0x28F."; +CM_ BO_ 715 "Slot 3 auxiliary frame (enrichment only, never gates validity). 0x2CB."; +CM_ BO_ 720 "Slot 4 frame 0 (range/status/azimuth/frame-index). 0x2D0."; +CM_ BO_ 721 "Slot 4 frame 1 (frame-index only, core contract). 0x2D1."; +CM_ BO_ 722 "Slot 4 frame 2 (lifecycle/frame-index). 0x2D2."; +CM_ BO_ 723 "Slot 4 frame 3 (frame-index only, core contract). 0x2D3."; +CM_ BO_ 716 "Slot 4 auxiliary frame (enrichment only, never gates validity). 0x2CC."; +CM_ BO_ 724 "Slot 5 frame 0 (range/status/azimuth/frame-index). 0x2D4."; +CM_ BO_ 725 "Slot 5 frame 1 (frame-index only, core contract). 0x2D5."; +CM_ BO_ 726 "Slot 5 frame 2 (lifecycle/frame-index). 0x2D6."; +CM_ BO_ 727 "Slot 5 frame 3 (frame-index only, core contract). 0x2D7."; +CM_ BO_ 717 "Slot 5 auxiliary frame (enrichment only, never gates validity). 0x2CD."; +CM_ BO_ 728 "Slot 6 frame 0 (range/status/azimuth/frame-index). 0x2D8."; +CM_ BO_ 729 "Slot 6 frame 1 (frame-index only, core contract). 0x2D9."; +CM_ BO_ 730 "Slot 6 frame 2 (lifecycle/frame-index). 0x2DA."; +CM_ BO_ 731 "Slot 6 frame 3 (frame-index only, core contract). 0x2DB."; +CM_ BO_ 718 "Slot 6 auxiliary frame (enrichment only, never gates validity). 0x2CE."; +CM_ BO_ 732 "Slot 7 frame 0 (range/status/azimuth/frame-index). 0x2DC."; +CM_ BO_ 733 "Slot 7 frame 1 (frame-index only, core contract). 0x2DD."; +CM_ BO_ 734 "Slot 7 frame 2 (lifecycle/frame-index). 0x2DE."; +CM_ BO_ 735 "Slot 7 frame 3 (frame-index only, core contract). 0x2DF."; +CM_ BO_ 719 "Slot 7 auxiliary frame (enrichment only, never gates validity). 0x2CF."; +CM_ BO_ 736 "Slot 8 frame 0 (range/status/azimuth/frame-index). 0x2E0."; +CM_ BO_ 737 "Slot 8 frame 1 (frame-index only, core contract). 0x2E1."; +CM_ BO_ 738 "Slot 8 frame 2 (lifecycle/frame-index). 0x2E2."; +CM_ BO_ 739 "Slot 8 frame 3 (frame-index only, core contract). 0x2E3."; +CM_ BO_ 656 "Slot 8 auxiliary frame (enrichment only, never gates validity). 0x290."; +CM_ BO_ 740 "Slot 9 frame 0 (range/status/azimuth/frame-index). 0x2E4."; +CM_ BO_ 741 "Slot 9 frame 1 (frame-index only, core contract). 0x2E5."; +CM_ BO_ 742 "Slot 9 frame 2 (lifecycle/frame-index). 0x2E6."; +CM_ BO_ 743 "Slot 9 frame 3 (frame-index only, core contract). 0x2E7."; +CM_ BO_ 657 "Slot 9 auxiliary frame (enrichment only, never gates validity). 0x291."; +CM_ BO_ 744 "Slot 10 frame 0 (range/status/azimuth/frame-index). 0x2E8."; +CM_ BO_ 745 "Slot 10 frame 1 (frame-index only, core contract). 0x2E9."; +CM_ BO_ 746 "Slot 10 frame 2 (lifecycle/frame-index). 0x2EA."; +CM_ BO_ 747 "Slot 10 frame 3 (frame-index only, core contract). 0x2EB."; +CM_ BO_ 658 "Slot 10 auxiliary frame (enrichment only, never gates validity). 0x292."; +CM_ BO_ 748 "Slot 11 frame 0 (range/status/azimuth/frame-index). 0x2EC."; +CM_ BO_ 749 "Slot 11 frame 1 (frame-index only, core contract). 0x2ED."; +CM_ BO_ 750 "Slot 11 frame 2 (lifecycle/frame-index). 0x2EE."; +CM_ BO_ 751 "Slot 11 frame 3 (frame-index only, core contract). 0x2EF."; +CM_ BO_ 659 "Slot 11 auxiliary frame (enrichment only, never gates validity). 0x293."; +CM_ BO_ 752 "Slot 12 frame 0 (range/status/azimuth/frame-index). 0x2F0."; +CM_ BO_ 753 "Slot 12 frame 1 (frame-index only, core contract). 0x2F1."; +CM_ BO_ 754 "Slot 12 frame 2 (lifecycle/frame-index). 0x2F2."; +CM_ BO_ 755 "Slot 12 frame 3 (frame-index only, core contract). 0x2F3."; +CM_ BO_ 660 "Slot 12 auxiliary frame (enrichment only, never gates validity). 0x294."; +CM_ BO_ 756 "Slot 13 frame 0 (range/status/azimuth/frame-index). 0x2F4."; +CM_ BO_ 757 "Slot 13 frame 1 (frame-index only, core contract). 0x2F5."; +CM_ BO_ 758 "Slot 13 frame 2 (lifecycle/frame-index). 0x2F6."; +CM_ BO_ 759 "Slot 13 frame 3 (frame-index only, core contract). 0x2F7."; +CM_ BO_ 661 "Slot 13 auxiliary frame (enrichment only, never gates validity). 0x295."; +CM_ BO_ 760 "Slot 14 frame 0 (range/status/azimuth/frame-index). 0x2F8."; +CM_ BO_ 761 "Slot 14 frame 1 (frame-index only, core contract). 0x2F9."; +CM_ BO_ 762 "Slot 14 frame 2 (lifecycle/frame-index). 0x2FA."; +CM_ BO_ 763 "Slot 14 frame 3 (frame-index only, core contract). 0x2FB."; +CM_ BO_ 662 "Slot 14 auxiliary frame (enrichment only, never gates validity). 0x296."; +CM_ BO_ 764 "Slot 15 frame 0 (range/status/azimuth/frame-index). 0x2FC."; +CM_ BO_ 765 "Slot 15 frame 1 (frame-index only, core contract). 0x2FD."; +CM_ BO_ 766 "Slot 15 frame 2 (lifecycle/frame-index). 0x2FE."; +CM_ BO_ 767 "Slot 15 frame 3 (frame-index only, core contract). 0x2FF."; +CM_ BO_ 663 "Slot 15 auxiliary frame (enrichment only, never gates validity). 0x297."; diff --git a/starpilot/common/assets/device_settings_layout.json b/starpilot/common/assets/device_settings_layout.json index a24bfa421..4c8efc9e6 100644 --- a/starpilot/common/assets/device_settings_layout.json +++ b/starpilot/common/assets/device_settings_layout.json @@ -682,6 +682,15 @@ "parent_key": "AdvancedLongitudinalTune", "settings_tier": "advanced" }, + { + "key": "HondaBoschARadar", + "label": "Honda Bosch-A Radar", + "description": "Honda Bosch-A harness only (N-Box, Accord, Civic, CR-V, CR-V Hybrid, Acura RDX, Insight, Honda e). Parses the car's own 16-slot Bosch-A radar CAN traffic into real leadOne/leadTwo tracks instead of treating the car as radarless. RX-only -- never transmits on the radar bus. Factory AEB/CMBS/FCW remain available as long as stock longitudinal stays in control; enabling openpilot longitudinal (alpha) disables them regardless of this toggle. Restart required to take effect.", + "data_type": "bool", + "ui_type": "toggle", + "parent_key": "AdvancedLongitudinalTune", + "settings_tier": "advanced" + }, { "key": "CustomAccelProfile", "label": "Custom Accel Profile",