IQ.Pilot Release Commit @ 9ba2ea7
This commit is contained in:
@@ -5,7 +5,8 @@ from iqdbc.car.common.conversions import Conversions as CV
|
||||
from iqdbc.car.disable_ecu import disable_ecu, clear_all_dtcs, clear_ecu_dtcs
|
||||
from iqdbc.car.honda.hondacan import CanBus
|
||||
from iqdbc.car.honda.values import CarControllerParams, HondaFlags, CAR, HONDA_BOSCH, HONDA_BOSCH_CANFD, \
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, HondaSafetyFlags
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \
|
||||
HONDA_RADAR_SCAN_VERIFIED, HondaSafetyFlags
|
||||
from iqdbc.car.honda.carcontroller import CarController
|
||||
from iqdbc.car.honda.carstate import CarState
|
||||
from iqdbc.car.honda.radar_interface import RadarInterface
|
||||
@@ -48,7 +49,9 @@ class CarInterface(CarInterfaceBase):
|
||||
cfgs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput))
|
||||
ret.safetyConfigs = cfgs
|
||||
|
||||
ret.radarUnavailable = True
|
||||
# The object scan survives openpilot longitudinal: the radar disable is subnet-scoped to the
|
||||
# powertrain bus, while the scan rides the camera-side ACC-CAN
|
||||
ret.radarUnavailable = docs or candidate not in HONDA_RADAR_SCAN_VERIFIED
|
||||
# Disable the radar and let openpilot control longitudinal
|
||||
# WARNING: THIS DISABLES AEB!
|
||||
# If Bosch radarless, this blocks ACC messages from the camera
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from iqdbc.can import CANParser
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.car.interfaces import RadarInterfaceBase
|
||||
from iqdbc.car.honda.radar_scan import SCAN_DBC_NAME, HondaRadarScanner
|
||||
from iqdbc.car.honda.values import DBC
|
||||
|
||||
|
||||
@@ -18,13 +19,20 @@ class RadarInterface(RadarInterfaceBase):
|
||||
self.radar_fault = False
|
||||
self.radar_wrong_config = False
|
||||
self.radar_off_can = CP.radarUnavailable
|
||||
self.scanner = None
|
||||
|
||||
# Nidec
|
||||
if self.radar_off_can:
|
||||
self.rcp = None
|
||||
self.trigger_msg = 0x445
|
||||
elif DBC[CP.carFingerprint].get(Bus.radar) == SCAN_DBC_NAME:
|
||||
self.scanner = HondaRadarScanner(CP)
|
||||
self.rcp = self.scanner.rcp
|
||||
self.pts = self.scanner.pts
|
||||
self.trigger_msg = self.scanner.trigger_msg
|
||||
else:
|
||||
# Nidec
|
||||
self.rcp = _create_nidec_can_parser(CP.carFingerprint)
|
||||
self.trigger_msg = 0x445
|
||||
self.trigger_msg = 0x445
|
||||
self.updated_messages = set()
|
||||
|
||||
def update(self, can_strings):
|
||||
@@ -37,6 +45,8 @@ class RadarInterface(RadarInterfaceBase):
|
||||
self.updated_messages.update(vls)
|
||||
|
||||
if self.trigger_msg not in self.updated_messages:
|
||||
if self.scanner is not None and self.scanner.sweep_overdue():
|
||||
return self.scanner.quiet_bus_radardata()
|
||||
return None
|
||||
|
||||
rr = self._update(self.updated_messages)
|
||||
@@ -44,6 +54,9 @@ class RadarInterface(RadarInterfaceBase):
|
||||
return rr
|
||||
|
||||
def _update(self, updated_messages):
|
||||
if self.scanner is not None:
|
||||
return self.scanner.process_sweep(updated_messages)
|
||||
|
||||
ret = structs.RadarData()
|
||||
|
||||
for ii in sorted(updated_messages):
|
||||
|
||||
396
artifacts/package_sources/iqdbc/iqdbc/car/honda/radar_scan.py
Normal file
396
artifacts/package_sources/iqdbc/iqdbc/car/honda/radar_scan.py
Normal file
@@ -0,0 +1,396 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import math
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from iqdbc.can import CANParser
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.car.honda.hondacan import CanBus
|
||||
from iqdbc.car.honda.values import DBC
|
||||
from iqdbc.dbc.generator.honda.honda_radar_scan import QUARTET_KINDS, SCAN_SLOTS, frame_address
|
||||
|
||||
SCAN_DBC_NAME = 'honda_radar_scan_generated'
|
||||
SWEEP_HZ = 15
|
||||
|
||||
SLOT_ADDRS = [tuple(frame_address(slot, kind) for kind in (*QUARTET_KINDS, "MOTION")) for slot in range(SCAN_SLOTS)]
|
||||
ALL_SCAN_ADDRS = [addr for addrs in SLOT_ADDRS for addr in addrs]
|
||||
# slot 15's IDENT frame closes every observed sweep's quartet family; its MOTION companion follows
|
||||
# but must never gate an otherwise valid sweep, so the quartet frame stays the trigger
|
||||
SWEEP_TRIGGER_ADDR = SLOT_ADDRS[SCAN_SLOTS - 1][3]
|
||||
|
||||
DIST_LSB_M = 0.05712
|
||||
DIST_BIAS_M = -3.0
|
||||
BEARING_LSB_RAD = 1.0 / 2048.0
|
||||
BEARING_ZERO = 1024
|
||||
|
||||
STATE_INVALID = 0xF
|
||||
DIST_RAW_INVALID = 0xFFF
|
||||
BEARING_RAW_INVALID = 0x7FF
|
||||
AGE_RAW_INVALID = 0xFFF
|
||||
HANDLE_MIN = 1
|
||||
HANDLE_MAX = 0x3F
|
||||
|
||||
CLOSING_SPEED_RAW_INVALID = 0x7FE
|
||||
CLOSING_SPEED_RAW_MIN = 0
|
||||
CLOSING_SPEED_RAW_MAX = 1728
|
||||
CLOSING_SPEED_RAW_ZERO = 864
|
||||
CLOSING_SPEED_LSB_MPS = 1.0 / 64.0
|
||||
# replay-derived quality gate: the native speed field degrades gradually with its sigma companion;
|
||||
# above this the field is no longer authoritative and the decoder coasts instead
|
||||
CLOSING_SPEED_SIGMA_TRUST_MAX = 511
|
||||
|
||||
DIST_RATIO_RAW_INVALID = 0x3FF
|
||||
DIST_RATIO_LSB = 0.001
|
||||
DIST_RATIO_BIAS = 0.5
|
||||
|
||||
# replay-derived acceptance gates, not recovered firmware constants; innovation is measured from the
|
||||
# previous ACCEPTED observation so a reset can never become the baseline for following sweeps
|
||||
DIST_SIGMA_DEGRADED_RAW = 4
|
||||
DIST_INNOVATION_SOFT_M = 2.0
|
||||
DIST_INNOVATION_HARD_M = 5.0
|
||||
RAW_RATE_LIMIT_MPS = 50.0
|
||||
|
||||
HISTORY_LEN = 8
|
||||
QUIET_TIMEOUT_S = 0.20
|
||||
|
||||
|
||||
def decode_closing_speed(raw, sigma_raw=None):
|
||||
if raw is None:
|
||||
return None
|
||||
raw = int(raw)
|
||||
if raw == CLOSING_SPEED_RAW_INVALID or not CLOSING_SPEED_RAW_MIN <= raw <= CLOSING_SPEED_RAW_MAX:
|
||||
return None
|
||||
if sigma_raw is not None and int(sigma_raw) > CLOSING_SPEED_SIGMA_TRUST_MAX:
|
||||
return None
|
||||
return (raw - CLOSING_SPEED_RAW_ZERO) * CLOSING_SPEED_LSB_MPS
|
||||
|
||||
|
||||
def decode_dist_ratio(raw):
|
||||
if raw is None:
|
||||
return None
|
||||
raw = int(raw)
|
||||
if raw == DIST_RATIO_RAW_INVALID or not 0 <= raw < DIST_RATIO_RAW_INVALID:
|
||||
return None
|
||||
return DIST_RATIO_BIAS + DIST_RATIO_LSB * raw
|
||||
|
||||
|
||||
def ratio_implied_rate(raw, dist, dt):
|
||||
ratio = decode_dist_ratio(raw)
|
||||
if ratio is None or dt <= 0.0 or not math.isfinite(dist):
|
||||
return None
|
||||
return dist * (1.0 - ratio) / dt
|
||||
|
||||
|
||||
def reading_degraded(dist_sigma_raw, presence_raw, speed_sigma_raw):
|
||||
geometry_bad = dist_sigma_raw >= DIST_SIGMA_DEGRADED_RAW or presence_raw in (0, 0x7F)
|
||||
motion_bad = speed_sigma_raw is not None and speed_sigma_raw > CLOSING_SPEED_SIGMA_TRUST_MAX
|
||||
return geometry_bad or motion_bad
|
||||
|
||||
|
||||
@dataclass
|
||||
class SlotReading:
|
||||
slot: int
|
||||
cycle: int
|
||||
age: int
|
||||
handle: int
|
||||
handle_ok: bool
|
||||
coherent: bool
|
||||
dist_raw: int = 0
|
||||
bearing_raw: int = 0
|
||||
dist_sigma_raw: int = 0
|
||||
presence_raw: int = 0
|
||||
speed_raw: int | None = None
|
||||
speed_sigma_raw: int | None = None
|
||||
ratio_raw: int | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ObjectLedger:
|
||||
handle: int
|
||||
prev_cycle: int | None = None
|
||||
prev_age: int | None = None
|
||||
last_seen_nanos: int | None = None
|
||||
wire_slot: int | None = None
|
||||
history: deque = field(default_factory=lambda: deque(maxlen=HISTORY_LEN))
|
||||
held_speed: float | None = None
|
||||
held_speed_nanos: int | None = None
|
||||
|
||||
def continues_incarnation(self, cycle: int, age: int) -> bool:
|
||||
if self.prev_cycle is None or self.prev_age is None:
|
||||
return False
|
||||
cycle_delta = (cycle - self.prev_cycle) & 0xF
|
||||
age_delta = (age - self.prev_age) & 0xFFF
|
||||
return age_delta == 2 * cycle_delta
|
||||
|
||||
def restart_incarnation(self):
|
||||
self.history.clear()
|
||||
self.held_speed = None
|
||||
self.held_speed_nanos = None
|
||||
|
||||
def held_speed_fresh(self, now: int) -> bool:
|
||||
return (self.held_speed is not None and self.held_speed_nanos is not None and
|
||||
(now - self.held_speed_nanos) * 1e-9 <= QUIET_TIMEOUT_S)
|
||||
|
||||
|
||||
def create_scan_parser(CP) -> CANParser:
|
||||
# the object scan is physically on the camera-side ACC-CAN
|
||||
return CANParser(DBC[CP.carFingerprint][Bus.radar], [(addr, SWEEP_HZ) for addr in ALL_SCAN_ADDRS], CanBus(CP).camera)
|
||||
|
||||
|
||||
class HondaRadarScanner:
|
||||
def __init__(self, CP):
|
||||
self.rcp = create_scan_parser(CP)
|
||||
self.trigger_msg = SWEEP_TRIGGER_ADDR
|
||||
self.pts: dict[int, structs.RadarData.RadarPoint] = {}
|
||||
self._ledgers: dict[int, ObjectLedger] = {}
|
||||
self._slot_handles: list[int | None] = [None] * SCAN_SLOTS
|
||||
self._last_sweep_nanos = -1
|
||||
|
||||
def sweep_overdue(self) -> bool:
|
||||
if self._last_sweep_nanos < 0:
|
||||
return False
|
||||
return (self.rcp._last_update_nanos - self._last_sweep_nanos) * 1e-9 > QUIET_TIMEOUT_S
|
||||
|
||||
def quiet_bus_radardata(self) -> structs.RadarData:
|
||||
# whole-bus silence: drop everything and emit an EMPTY RadarData (not None) so radard sheds any
|
||||
# lead within a cycle instead of freezing a phantom
|
||||
self.pts.clear()
|
||||
self._ledgers.clear()
|
||||
self._slot_handles = [None] * SCAN_SLOTS
|
||||
self._last_sweep_nanos = -1
|
||||
ret = structs.RadarData()
|
||||
if not self.rcp.can_valid:
|
||||
ret.errors.canError = True
|
||||
ret.errors.radarUnavailableTemporary = True
|
||||
return ret
|
||||
|
||||
def _drop_ledger(self, handle: int):
|
||||
self._ledgers.pop(handle, None)
|
||||
self.pts.pop(handle, None)
|
||||
for slot, bound in enumerate(self._slot_handles):
|
||||
if bound == handle:
|
||||
self._slot_handles[slot] = None
|
||||
|
||||
def _drop_expired_ledgers(self, now: int):
|
||||
for handle, ledger in list(self._ledgers.items()):
|
||||
if ledger.last_seen_nanos is not None and (now - ledger.last_seen_nanos) * 1e-9 > QUIET_TIMEOUT_S:
|
||||
self._drop_ledger(handle)
|
||||
|
||||
def _read_slot(self, slot: int, updated_messages) -> SlotReading | None:
|
||||
pos, shape, life, ident, motion = SLOT_ADDRS[slot]
|
||||
if not all(addr in updated_messages for addr in (pos, shape, life, ident)):
|
||||
# a missing CAN frame is not a lifecycle event; ledgers expire on their own staleness only
|
||||
return None
|
||||
|
||||
v_pos, v_shape, v_life, v_ident = (self.rcp.vl[a] for a in (pos, shape, life, ident))
|
||||
cycle = int(v_pos['CYCLE'])
|
||||
if not (cycle == int(v_shape['CYCLE']) == int(v_life['CYCLE']) == int(v_ident['CYCLE'])):
|
||||
# the quartet doesn't share one radar cycle: not a coherent observation this window
|
||||
return None
|
||||
|
||||
state = int(v_pos['SCAN_STATE'])
|
||||
dist_raw = int(v_pos['DIST_RAW'])
|
||||
bearing_raw = int(v_pos['BEARING_RAW'])
|
||||
age = int(v_life['AGE_RAW'])
|
||||
handle = int(v_ident['OBJECT_HANDLE'])
|
||||
reading = SlotReading(
|
||||
slot=slot,
|
||||
cycle=cycle,
|
||||
age=age,
|
||||
handle=handle,
|
||||
handle_ok=HANDLE_MIN <= handle <= HANDLE_MAX,
|
||||
coherent=(state != STATE_INVALID and dist_raw != DIST_RAW_INVALID and
|
||||
bearing_raw != BEARING_RAW_INVALID and age != AGE_RAW_INVALID),
|
||||
dist_raw=dist_raw,
|
||||
bearing_raw=bearing_raw,
|
||||
dist_sigma_raw=int(v_pos['DIST_SIGMA_RAW']),
|
||||
presence_raw=int(v_shape['PRESENCE_RAW']),
|
||||
)
|
||||
|
||||
# the MOTION companion only contributes when it rides the same cycle; its absence never
|
||||
# invalidates the quartet, it only removes independent motion evidence
|
||||
if motion in updated_messages:
|
||||
v_motion = self.rcp.vl[motion]
|
||||
if int(v_motion['CYCLE']) == cycle:
|
||||
reading.speed_raw = int(v_motion['CLOSING_SPEED_RAW'])
|
||||
reading.speed_sigma_raw = int(v_motion['CLOSING_SPEED_SIGMA_RAW'])
|
||||
reading.ratio_raw = int(v_motion['DIST_RATIO_RAW'])
|
||||
return reading
|
||||
|
||||
def _elect_by_handle(self, readings: list[SlotReading]) -> dict[int, SlotReading]:
|
||||
# one CAN identity can never yield two points; ties prefer the wire slot already bound to the
|
||||
# ledger, then the lower slot for deterministic handling of a malformed duplicate
|
||||
elected: dict[int, SlotReading] = {}
|
||||
for reading in readings:
|
||||
if not (reading.coherent and reading.handle_ok):
|
||||
# an invalid observation ends publication for the slot's current occupant without destroying
|
||||
# persistent state; the object may be multiplexed elsewhere or return before its deadline
|
||||
hidden = {self._slot_handles[reading.slot]}
|
||||
if reading.handle_ok:
|
||||
hidden.add(reading.handle)
|
||||
for handle in hidden - {None}:
|
||||
self.pts.pop(handle, None)
|
||||
continue
|
||||
current = elected.get(reading.handle)
|
||||
if current is None:
|
||||
elected[reading.handle] = reading
|
||||
continue
|
||||
bound_slot = self._ledgers[reading.handle].wire_slot if reading.handle in self._ledgers else None
|
||||
current_rank = (0 if bound_slot == current.slot else 1, current.slot)
|
||||
candidate_rank = (0 if bound_slot == reading.slot else 1, reading.slot)
|
||||
if candidate_rank < current_rank:
|
||||
elected[reading.handle] = reading
|
||||
return elected
|
||||
|
||||
def _bind_slot(self, ledger: ObjectLedger, reading: SlotReading, now: int):
|
||||
ledger.prev_cycle = reading.cycle
|
||||
ledger.prev_age = reading.age
|
||||
ledger.last_seen_nanos = now
|
||||
ledger.wire_slot = reading.slot
|
||||
for slot, bound in enumerate(self._slot_handles):
|
||||
if slot != reading.slot and bound == ledger.handle:
|
||||
self._slot_handles[slot] = None
|
||||
self._slot_handles[reading.slot] = ledger.handle
|
||||
|
||||
def _coast_point(self, ledger: ObjectLedger, now: int, dist: float, y_rel: float):
|
||||
# coasting keeps trustworthy geometry visible with the last authoritative motion, unmeasured,
|
||||
# instead of publishing a synthesized rate; without fresh held motion the point drops
|
||||
if ledger.held_speed_fresh(now):
|
||||
point = self.pts.get(ledger.handle)
|
||||
if point is not None:
|
||||
point.dRel = dist
|
||||
point.yRel = y_rel
|
||||
point.vRel = ledger.held_speed
|
||||
point.measured = False
|
||||
return
|
||||
ledger.held_speed = None
|
||||
ledger.held_speed_nanos = None
|
||||
self.pts.pop(ledger.handle, None)
|
||||
|
||||
def process_sweep(self, updated_messages) -> structs.RadarData:
|
||||
ret = structs.RadarData()
|
||||
if not self.rcp.can_valid:
|
||||
ret.errors.canError = True
|
||||
|
||||
now = self.rcp._last_update_nanos
|
||||
self._last_sweep_nanos = now
|
||||
self._drop_expired_ledgers(now)
|
||||
|
||||
readings = [r for r in (self._read_slot(slot, updated_messages) for slot in range(SCAN_SLOTS)) if r is not None]
|
||||
elected = self._elect_by_handle(readings)
|
||||
elected_handles = set(elected)
|
||||
|
||||
for handle, reading in sorted(elected.items(), key=lambda item: item[1].slot):
|
||||
# a wire-slot replacement ends publication for the old occupant, but not its persistent state
|
||||
old_handle = self._slot_handles[reading.slot]
|
||||
if old_handle is not None and old_handle != handle and old_handle not in elected_handles:
|
||||
self.pts.pop(old_handle, None)
|
||||
|
||||
ledger = self._ledgers.get(handle)
|
||||
if ledger is None:
|
||||
ledger = ObjectLedger(handle=handle)
|
||||
self._ledgers[handle] = ledger
|
||||
|
||||
if not ledger.continues_incarnation(reading.cycle, reading.age):
|
||||
# the CAN identity stays the external key, but a lifecycle discontinuity starts a new
|
||||
# incarnation and must not inherit the previous object's range-rate history
|
||||
ledger.restart_incarnation()
|
||||
self.pts.pop(handle, None)
|
||||
|
||||
dist = DIST_LSB_M * reading.dist_raw + DIST_BIAS_M
|
||||
bearing = BEARING_LSB_RAD * (reading.bearing_raw - BEARING_ZERO)
|
||||
# the radar consumes range as a forward-axis quantity; positive bearing is left of center,
|
||||
# matching the RadarPoint.yRel sign contract
|
||||
y_rel = dist * math.tan(bearing)
|
||||
|
||||
now_s = now * 1e-9
|
||||
native_speed = decode_closing_speed(reading.speed_raw, reading.speed_sigma_raw)
|
||||
unqualified_speed = decode_closing_speed(reading.speed_raw)
|
||||
# a live native speed rejected only by its sigma companion: geometry acceptance is still decided
|
||||
# on the same terms as every other sweep (high sigma correlates with bad/discontinuous range),
|
||||
# and only then does the point coast instead of publishing a synthesized rate
|
||||
sigma_veto = (native_speed is None and unqualified_speed is not None and
|
||||
reading.speed_sigma_raw is not None and
|
||||
reading.speed_sigma_raw > CLOSING_SPEED_SIGMA_TRUST_MAX)
|
||||
|
||||
degraded = reading_degraded(reading.dist_sigma_raw, reading.presence_raw, reading.speed_sigma_raw)
|
||||
previous = ledger.history[-1] if ledger.history else None
|
||||
ratio_rate = None
|
||||
dist_rejected = False
|
||||
if previous is not None:
|
||||
prev_time, prev_dist = previous
|
||||
dt = now_s - prev_time
|
||||
if dt <= 0.0:
|
||||
dist_rejected = True
|
||||
else:
|
||||
ratio = decode_dist_ratio(reading.ratio_raw)
|
||||
ratio_rate = ratio_implied_rate(reading.ratio_raw, dist, dt)
|
||||
|
||||
residuals_m = []
|
||||
if native_speed is not None:
|
||||
residuals_m.append(abs(dist - (prev_dist + native_speed * dt)))
|
||||
if ratio is not None:
|
||||
residuals_m.append(abs(prev_dist - dist * ratio))
|
||||
|
||||
if residuals_m:
|
||||
innovation_m = min(residuals_m)
|
||||
dist_rejected = (innovation_m > DIST_INNOVATION_HARD_M or
|
||||
(degraded and innovation_m > DIST_INNOVATION_SOFT_M))
|
||||
else:
|
||||
dist_rejected = abs((dist - prev_dist) / dt) > RAW_RATE_LIMIT_MPS
|
||||
|
||||
if dist_rejected:
|
||||
# keep the last accepted point briefly as an unmeasured coast; rejected geometry is never
|
||||
# published and never becomes the baseline for a later derivative
|
||||
accepted_fresh = previous is not None and now_s - previous[0] <= QUIET_TIMEOUT_S
|
||||
point = self.pts.get(handle)
|
||||
if accepted_fresh and point is not None:
|
||||
point.measured = False
|
||||
else:
|
||||
self.pts.pop(handle, None)
|
||||
self._bind_slot(ledger, reading, now)
|
||||
continue
|
||||
|
||||
if sigma_veto:
|
||||
self._coast_point(ledger, now, dist, y_rel)
|
||||
self._bind_slot(ledger, reading, now)
|
||||
continue
|
||||
|
||||
# with neither a qualified native speed nor a usable ratio, the only remaining source is the
|
||||
# raw one-sweep derivative, which must never become an authoritative measurement: coast instead.
|
||||
# A true birth (no previous accepted sample) cannot mature this cycle regardless, so only
|
||||
# intercept once a derivative would have something to poison
|
||||
if native_speed is None and (ratio_rate is None or degraded) and previous is not None:
|
||||
self._coast_point(ledger, now, dist, y_rel)
|
||||
self._bind_slot(ledger, reading, now)
|
||||
continue
|
||||
|
||||
ledger.history.append((now_s, dist))
|
||||
|
||||
speed = native_speed if native_speed is not None else ratio_rate
|
||||
ledger.held_speed = speed
|
||||
ledger.held_speed_nanos = now
|
||||
|
||||
# a birth observation has no range rate yet: keep it as history, publish only once a second
|
||||
# coherent observation of the same identity supplies a finite rate
|
||||
matured = len(ledger.history) >= 2 and math.isfinite(speed)
|
||||
if matured:
|
||||
if handle not in self.pts:
|
||||
point = structs.RadarData.RadarPoint()
|
||||
point.trackId = handle
|
||||
point.aRel = float('nan')
|
||||
point.yvRel = float('nan')
|
||||
self.pts[handle] = point
|
||||
self.pts[handle].dRel = dist
|
||||
self.pts[handle].yRel = y_rel
|
||||
self.pts[handle].vRel = speed
|
||||
self.pts[handle].measured = True
|
||||
else:
|
||||
self.pts.pop(handle, None)
|
||||
|
||||
self._bind_slot(ledger, reading, now)
|
||||
|
||||
ret.points = [self.pts[handle] for handle in sorted(self.pts)]
|
||||
return ret
|
||||
@@ -0,0 +1,490 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from iqdbc.can import CANParser
|
||||
from iqdbc.car.honda.radar_scan import (AGE_RAW_INVALID, ALL_SCAN_ADDRS, BEARING_RAW_INVALID, BEARING_ZERO,
|
||||
CLOSING_SPEED_RAW_INVALID, CLOSING_SPEED_RAW_ZERO,
|
||||
CLOSING_SPEED_SIGMA_TRUST_MAX, DIST_BIAS_M, DIST_LSB_M,
|
||||
DIST_RATIO_RAW_INVALID, DIST_RAW_INVALID, HondaRadarScanner,
|
||||
QUIET_TIMEOUT_S, SCAN_DBC_NAME, SCAN_SLOTS, STATE_INVALID,
|
||||
SWEEP_TRIGGER_ADDR, decode_closing_speed, decode_dist_ratio)
|
||||
from iqdbc.dbc.generator.honda.honda_radar_scan import FRAME_SIGNALS, frame_address
|
||||
|
||||
BUS = 2
|
||||
SWEEP_DT_NS = 66_000_000
|
||||
|
||||
|
||||
def set_bits(data, start_bit, size, value):
|
||||
value = int(value) & ((1 << size) - 1)
|
||||
pos = start_bit
|
||||
for i in range(size):
|
||||
bit = (value >> (size - 1 - i)) & 1
|
||||
byte_i, bit_i = pos // 8, pos % 8
|
||||
if bit:
|
||||
data[byte_i] |= (1 << bit_i)
|
||||
pos = pos - 1 if bit_i > 0 else pos + 15
|
||||
|
||||
|
||||
GEOMETRY = {kind: {name: (start, size) for name, start, size in sigs} for kind, sigs in FRAME_SIGNALS.items()}
|
||||
|
||||
|
||||
def build_frame(slot, kind, **fields):
|
||||
data = bytearray(8)
|
||||
for name, value in fields.items():
|
||||
set_bits(data, *GEOMETRY[kind][name], value)
|
||||
return (frame_address(slot, kind), bytes(data), BUS)
|
||||
|
||||
|
||||
def quartet(slot, cycle, dist_raw=1000, bearing_raw=BEARING_ZERO, state=1, dist_sigma=0, presence=40,
|
||||
age=100, handle=5):
|
||||
return [
|
||||
build_frame(slot, "POS", SCAN_STATE=state, CYCLE=cycle, DIST_RAW=dist_raw, BEARING_RAW=bearing_raw,
|
||||
DIST_SIGMA_RAW=dist_sigma),
|
||||
build_frame(slot, "SHAPE", CYCLE=cycle, PRESENCE_RAW=presence),
|
||||
build_frame(slot, "LIFE", CYCLE=cycle, AGE_RAW=age),
|
||||
build_frame(slot, "IDENT", CYCLE=cycle, OBJECT_HANDLE=handle),
|
||||
]
|
||||
|
||||
|
||||
def motion_frame(slot, cycle, speed_raw=CLOSING_SPEED_RAW_ZERO, sigma_raw=0, ratio_raw=500):
|
||||
return build_frame(slot, "MOTION", CYCLE=cycle, CLOSING_SPEED_RAW=speed_raw,
|
||||
CLOSING_SPEED_SIGMA_RAW=sigma_raw, DIST_RATIO_RAW=ratio_raw)
|
||||
|
||||
|
||||
def closing_sweep(slot_msgs, cycle):
|
||||
# slot 15's quartet closes every sweep so the trigger fires
|
||||
msgs = list(slot_msgs)
|
||||
if not any(m[0] == SWEEP_TRIGGER_ADDR for m in msgs):
|
||||
msgs += quartet(15, cycle, state=STATE_INVALID, dist_raw=DIST_RAW_INVALID,
|
||||
bearing_raw=BEARING_RAW_INVALID, age=AGE_RAW_INVALID, handle=0)
|
||||
return msgs
|
||||
|
||||
|
||||
class ScanHarness:
|
||||
def __init__(self):
|
||||
self.scanner = object.__new__(HondaRadarScanner)
|
||||
self.scanner.rcp = CANParser(SCAN_DBC_NAME, [(a, 15) for a in ALL_SCAN_ADDRS], BUS)
|
||||
self.scanner.trigger_msg = SWEEP_TRIGGER_ADDR
|
||||
self.scanner.pts = {}
|
||||
self.scanner._ledgers = {}
|
||||
self.scanner._slot_handles = [None] * SCAN_SLOTS
|
||||
self.scanner._last_sweep_nanos = -1
|
||||
self.updated = set()
|
||||
self.nanos = 0
|
||||
self.cycle = 0
|
||||
|
||||
def feed(self, msgs, dt_ns=SWEEP_DT_NS):
|
||||
self.nanos += dt_ns
|
||||
vls = self.scanner.rcp.update([self.nanos, list(msgs)])
|
||||
self.updated.update(vls)
|
||||
if self.scanner.trigger_msg not in self.updated:
|
||||
if self.scanner.sweep_overdue():
|
||||
return self.scanner.quiet_bus_radardata()
|
||||
return None
|
||||
result = self.scanner.process_sweep(self.updated)
|
||||
self.updated.clear()
|
||||
return result
|
||||
|
||||
def sweep(self, slot_msgs=(), cycle_step=1, dt_ns=SWEEP_DT_NS):
|
||||
self.cycle = (self.cycle + cycle_step) & 0xF
|
||||
return self.feed(closing_sweep(slot_msgs, self.cycle), dt_ns=dt_ns)
|
||||
|
||||
def object_sweep(self, slot=0, handle=5, dist_raw=1000, with_motion=True, cycle_step=1, age_step=None,
|
||||
dt_ns=SWEEP_DT_NS, **kwargs):
|
||||
if age_step is None:
|
||||
age_step = 2 * cycle_step
|
||||
self._age = (getattr(self, "_age", 100) + age_step) & 0xFFF
|
||||
cycle = (self.cycle + cycle_step) & 0xF
|
||||
msgs = quartet(slot, cycle, dist_raw=dist_raw, age=self._age, handle=handle, **kwargs)
|
||||
if with_motion:
|
||||
msgs.append(motion_frame(slot, cycle))
|
||||
return self.sweep(msgs, cycle_step=cycle_step, dt_ns=dt_ns)
|
||||
|
||||
|
||||
class TestFieldDecoding:
|
||||
def test_dist_conversion(self):
|
||||
assert DIST_LSB_M * 1000 + DIST_BIAS_M == pytest.approx(54.12)
|
||||
|
||||
def test_closing_speed_decode_and_domain(self):
|
||||
assert decode_closing_speed(CLOSING_SPEED_RAW_ZERO) == 0.0
|
||||
assert decode_closing_speed(CLOSING_SPEED_RAW_ZERO + 64) == 1.0
|
||||
assert decode_closing_speed(CLOSING_SPEED_RAW_INVALID) is None
|
||||
assert decode_closing_speed(1729) is None
|
||||
assert decode_closing_speed(None) is None
|
||||
|
||||
def test_closing_speed_sigma_veto(self):
|
||||
assert decode_closing_speed(CLOSING_SPEED_RAW_ZERO, CLOSING_SPEED_SIGMA_TRUST_MAX) == 0.0
|
||||
assert decode_closing_speed(CLOSING_SPEED_RAW_ZERO, CLOSING_SPEED_SIGMA_TRUST_MAX + 1) is None
|
||||
|
||||
def test_dist_ratio_decode(self):
|
||||
assert decode_dist_ratio(500) == pytest.approx(1.0)
|
||||
assert decode_dist_ratio(DIST_RATIO_RAW_INVALID) is None
|
||||
assert decode_dist_ratio(None) is None
|
||||
|
||||
def test_bearing_sign_convention(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(bearing_raw=BEARING_ZERO + 100)
|
||||
result = h.object_sweep(bearing_raw=BEARING_ZERO + 100)
|
||||
assert result.points[0].yRel > 0 # left of center is positive
|
||||
dist = result.points[0].dRel
|
||||
assert result.points[0].yRel == pytest.approx(dist * math.tan(100 / 2048))
|
||||
|
||||
def test_bearing_right_of_center_is_negative(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(bearing_raw=BEARING_ZERO - 100)
|
||||
result = h.object_sweep(bearing_raw=BEARING_ZERO - 100)
|
||||
assert result.points[0].yRel < 0
|
||||
|
||||
def test_boresight_is_zero(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(bearing_raw=BEARING_ZERO)
|
||||
result = h.object_sweep(bearing_raw=BEARING_ZERO)
|
||||
assert result.points[0].yRel == 0.0
|
||||
|
||||
|
||||
class TestPublicationRules:
|
||||
def test_birth_is_withheld_until_second_observation(self):
|
||||
h = ScanHarness()
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 0
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 1
|
||||
point = result.points[0]
|
||||
assert point.trackId == 5
|
||||
assert point.measured
|
||||
assert math.isnan(point.aRel) and math.isnan(point.yvRel)
|
||||
|
||||
def test_handle_is_wire_identity_not_synthetic(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(handle=0x22)
|
||||
result = h.object_sweep(handle=0x22)
|
||||
assert result.points[0].trackId == 0x22
|
||||
|
||||
@pytest.mark.parametrize("field,value", [("state", STATE_INVALID), ("dist_raw", DIST_RAW_INVALID),
|
||||
("bearing_raw", BEARING_RAW_INVALID)])
|
||||
def test_sentinels_invalidate_observation(self, field, value):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
kwargs = {field: value}
|
||||
result = h.object_sweep(**kwargs)
|
||||
assert len(result.points) == 0
|
||||
|
||||
def test_age_sentinel_invalidates_observation(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
msgs = quartet(0, cycle, age=AGE_RAW_INVALID, handle=5) + [motion_frame(0, cycle)]
|
||||
result = h.sweep(msgs)
|
||||
assert len(result.points) == 0
|
||||
|
||||
@pytest.mark.parametrize("handle", [0, 0x40, 0xFF])
|
||||
def test_out_of_range_handle_invalidates(self, handle):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
result = h.object_sweep(handle=handle)
|
||||
assert len(result.points) == 0
|
||||
|
||||
def test_incomplete_quartet_is_not_an_observation(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
h._age = (h._age + 2) & 0xFFF
|
||||
msgs = quartet(0, cycle, age=h._age, handle=5)[:3] # drop IDENT
|
||||
result = h.sweep(msgs)
|
||||
# a dropped CAN frame is not a lifecycle event: the published point persists untouched
|
||||
assert len(result.points) == 1
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 1
|
||||
assert result.points[0].measured
|
||||
|
||||
def test_cycle_mismatch_across_quartet_is_incoherent(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
msgs = quartet(0, cycle, age=200, handle=5)
|
||||
bad_life = build_frame(0, "LIFE", CYCLE=(cycle + 1) & 0xF, AGE_RAW=200)
|
||||
msgs[2] = bad_life
|
||||
result = h.sweep(msgs)
|
||||
# an incoherent quartet is not an observation: the published point persists untouched
|
||||
assert len(result.points) == 1
|
||||
assert result.points[0].measured
|
||||
|
||||
|
||||
class TestLifecycle:
|
||||
def test_age_advances_two_per_cycle_keeps_identity(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 1
|
||||
|
||||
def test_continuity_across_skipped_cycles(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
result = h.object_sweep(cycle_step=3, age_step=6)
|
||||
assert len(result.points) == 1
|
||||
|
||||
def test_cycle_and_age_wraparound_stay_same_incarnation(self):
|
||||
h = ScanHarness()
|
||||
h.cycle = 14
|
||||
h._age = 4094
|
||||
h.object_sweep() # cycle 15, age 4094+2 wraps
|
||||
h.object_sweep() # cycle 0
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 1
|
||||
|
||||
def test_lifecycle_break_starts_new_incarnation(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
# same handle, age jumps arbitrarily: history must not carry over, so no publication this sweep
|
||||
result = h.object_sweep(age_step=500)
|
||||
assert len(result.points) == 0
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 1
|
||||
|
||||
def test_death_then_rebirth_reuses_handle_with_clean_history(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
for _ in range(4):
|
||||
h.sweep() # object absent long enough to expire its ledger
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 0
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 1
|
||||
|
||||
|
||||
class TestMotionPolicy:
|
||||
def test_native_speed_is_published(self):
|
||||
h = ScanHarness()
|
||||
speed_raw = CLOSING_SPEED_RAW_ZERO + 128
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
h.sweep(quartet(0, cycle, age=100, handle=5) + [motion_frame(0, cycle, speed_raw=speed_raw)])
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
result = h.sweep(quartet(0, cycle, age=102, handle=5) + [motion_frame(0, cycle, speed_raw=speed_raw)])
|
||||
assert result.points[0].vRel == pytest.approx(2.0)
|
||||
assert result.points[0].measured
|
||||
|
||||
def test_missing_motion_frame_never_invalidates_geometry(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(with_motion=False)
|
||||
result = h.object_sweep(with_motion=False)
|
||||
# without any motion source and no held speed, the point is withheld rather than synthesized
|
||||
assert len(result.points) == 0
|
||||
|
||||
def test_stale_motion_cycle_is_ignored(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
h._age = (h._age + 2) & 0xFFF
|
||||
msgs = quartet(0, cycle, age=h._age, handle=5) + [motion_frame(0, (cycle - 1) & 0xF)]
|
||||
result = h.sweep(msgs)
|
||||
# motion from another cycle contributes nothing: coasts on held speed, unmeasured
|
||||
assert len(result.points) == 1
|
||||
assert not result.points[0].measured
|
||||
|
||||
def test_high_sigma_speed_coasts_instead_of_synthesizing(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
h._age = (h._age + 2) & 0xFFF
|
||||
msgs = quartet(0, cycle, age=h._age, handle=5) + \
|
||||
[motion_frame(0, cycle, sigma_raw=CLOSING_SPEED_SIGMA_TRUST_MAX + 1)]
|
||||
result = h.sweep(msgs)
|
||||
assert len(result.points) == 1
|
||||
assert not result.points[0].measured
|
||||
assert result.points[0].vRel == pytest.approx(0.0) # the held speed, not a derivative
|
||||
|
||||
def test_ratio_field_supplies_speed_when_native_missing(self):
|
||||
h = ScanHarness()
|
||||
dist_raw = 1000
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
h.sweep(quartet(0, cycle, dist_raw=dist_raw, age=100, handle=5) +
|
||||
[motion_frame(0, cycle, speed_raw=CLOSING_SPEED_RAW_INVALID, ratio_raw=490)])
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
result = h.sweep(quartet(0, cycle, dist_raw=dist_raw, age=102, handle=5) +
|
||||
[motion_frame(0, cycle, speed_raw=CLOSING_SPEED_RAW_INVALID, ratio_raw=490)])
|
||||
assert len(result.points) == 1
|
||||
dist = DIST_LSB_M * dist_raw + DIST_BIAS_M
|
||||
dt = SWEEP_DT_NS * 1e-9
|
||||
assert result.points[0].vRel == pytest.approx(dist * (1.0 - 0.99) / dt)
|
||||
assert result.points[0].measured
|
||||
|
||||
def test_fast_clean_range_rate_without_sources_is_withheld(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(with_motion=False, dist_raw=1000)
|
||||
# large clean jump with no motion evidence: raw-rate limit rejects the range outright
|
||||
result = h.object_sweep(with_motion=False, dist_raw=3000)
|
||||
assert len(result.points) == 0
|
||||
|
||||
|
||||
class TestRangeAcceptance:
|
||||
def test_discontinuity_is_rejected_and_never_becomes_baseline(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(dist_raw=1000)
|
||||
h.object_sweep(dist_raw=1002)
|
||||
# jump far beyond the hard innovation gate while claiming zero closing speed
|
||||
result = h.object_sweep(dist_raw=3000)
|
||||
assert len(result.points) == 1
|
||||
assert not result.points[0].measured
|
||||
# the rejected range did not become the derivative baseline: returning to the
|
||||
# consistent range publishes measured again
|
||||
result = h.object_sweep(dist_raw=1004)
|
||||
assert result.points[0].measured
|
||||
|
||||
def test_small_innovation_accepted(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(dist_raw=1000)
|
||||
result = h.object_sweep(dist_raw=1005)
|
||||
assert result.points[0].measured
|
||||
|
||||
|
||||
class TestSlotsAndIdentity:
|
||||
def test_slot_migration_preserves_identity(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(slot=2)
|
||||
h.object_sweep(slot=2)
|
||||
result = h.object_sweep(slot=9)
|
||||
assert len(result.points) == 1
|
||||
assert result.points[0].trackId == 5
|
||||
|
||||
def test_duplicate_identity_prefers_bound_slot(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(slot=2, dist_raw=1000)
|
||||
h.object_sweep(slot=2, dist_raw=1002)
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
h._age = (h._age + 2) & 0xFFF
|
||||
msgs = quartet(2, cycle, dist_raw=1004, age=h._age, handle=5) + [motion_frame(2, cycle)] + \
|
||||
quartet(9, cycle, dist_raw=2000, age=h._age, handle=5) + [motion_frame(9, cycle)]
|
||||
result = h.sweep(msgs)
|
||||
assert len(result.points) == 1
|
||||
assert result.points[0].dRel == pytest.approx(DIST_LSB_M * 1004 + DIST_BIAS_M)
|
||||
|
||||
def test_slot_replacement_hides_old_occupant(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep(slot=3, handle=7)
|
||||
h.object_sweep(slot=3, handle=7)
|
||||
# a different identity takes the slot; the old one is hidden but not destroyed
|
||||
result = h.object_sweep(slot=3, handle=9, age_step=333)
|
||||
assert all(p.trackId != 7 for p in result.points)
|
||||
|
||||
def test_one_identity_never_two_points(self):
|
||||
h = ScanHarness()
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
msgs = quartet(1, cycle, age=100, handle=5) + [motion_frame(1, cycle)] + \
|
||||
quartet(6, cycle, age=100, handle=5) + [motion_frame(6, cycle)]
|
||||
h.sweep(msgs)
|
||||
cycle = (h.cycle + 1) & 0xF
|
||||
msgs = quartet(1, cycle, age=102, handle=5) + [motion_frame(1, cycle)] + \
|
||||
quartet(6, cycle, age=102, handle=5) + [motion_frame(6, cycle)]
|
||||
result = h.sweep(msgs)
|
||||
assert len(result.points) == 1
|
||||
|
||||
|
||||
class TestBusSilence:
|
||||
def test_quiet_bus_publishes_empty_not_none(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
result = None
|
||||
for _ in range(30):
|
||||
result = h.feed([], dt_ns=10_000_000)
|
||||
if result is not None:
|
||||
break
|
||||
assert result is not None
|
||||
assert result.errors.radarUnavailableTemporary
|
||||
assert len(result.points) == 0
|
||||
|
||||
def test_recovery_after_silence_starts_fresh(self):
|
||||
h = ScanHarness()
|
||||
h.object_sweep()
|
||||
h.object_sweep()
|
||||
for _ in range(30):
|
||||
if h.feed([], dt_ns=10_000_000) is not None:
|
||||
break
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 0
|
||||
result = h.object_sweep()
|
||||
assert len(result.points) == 1
|
||||
|
||||
def test_no_stale_publication_before_first_sweep(self):
|
||||
h = ScanHarness()
|
||||
for _ in range(50):
|
||||
assert h.feed([], dt_ns=10_000_000) is None
|
||||
|
||||
|
||||
class TestQuietTimeoutValue:
|
||||
def test_timeout_is_about_three_sweeps(self):
|
||||
assert QUIET_TIMEOUT_S == pytest.approx(3 / 15, abs=0.01)
|
||||
|
||||
|
||||
class TestScanInterfaceGating:
|
||||
def build(self, candidate, alpha_long=False, docs=False):
|
||||
from iqdbc.car import gen_empty_fingerprint
|
||||
from iqdbc.car.honda.interface import CarInterface
|
||||
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], alpha_long, False, docs)
|
||||
return CP
|
||||
|
||||
def test_verified_platform_has_radar(self):
|
||||
from iqdbc.car.honda.values import CAR
|
||||
for car in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_ACCORD, CAR.HONDA_CRV_5G):
|
||||
assert not self.build(car).radarUnavailable
|
||||
|
||||
def test_radar_survives_openpilot_longitudinal(self):
|
||||
from iqdbc.car.honda.values import CAR
|
||||
CP = self.build(CAR.HONDA_CIVIC_BOSCH, alpha_long=True)
|
||||
assert CP.openpilotLongitudinalControl
|
||||
assert not CP.radarUnavailable
|
||||
|
||||
def test_unverified_family_platform_stays_off(self):
|
||||
from iqdbc.car.honda.values import CAR
|
||||
for car in (CAR.HONDA_E, CAR.HONDA_INSIGHT, CAR.HONDA_NBOX_2G, CAR.ACURA_RDX_3G, CAR.HONDA_CRV_HYBRID):
|
||||
assert self.build(car).radarUnavailable
|
||||
|
||||
def test_radarless_and_canfd_stay_off(self):
|
||||
from iqdbc.car.honda.values import CAR
|
||||
assert self.build(CAR.HONDA_CIVIC_2022).radarUnavailable
|
||||
assert self.build(CAR.HONDA_CRV_6G).radarUnavailable
|
||||
|
||||
def test_docs_never_claim_radar(self):
|
||||
from iqdbc.car.honda.values import CAR
|
||||
assert self.build(CAR.HONDA_CIVIC_BOSCH, docs=True).radarUnavailable
|
||||
|
||||
def test_radar_interface_routes_scanner(self):
|
||||
from iqdbc.car import gen_empty_fingerprint
|
||||
from iqdbc.car.honda.interface import CarInterface
|
||||
from iqdbc.car.honda.values import CAR
|
||||
CP = self.build(CAR.HONDA_CIVIC_BOSCH)
|
||||
CP_IQ = CarInterface.get_params_iq(CP, CAR.HONDA_CIVIC_BOSCH, gen_empty_fingerprint(), [], False, False, False)
|
||||
ri = CarInterface.RadarInterface(CP, CP_IQ)
|
||||
assert ri.scanner is not None
|
||||
assert ri.trigger_msg == SWEEP_TRIGGER_ADDR
|
||||
|
||||
def test_radar_interface_keeps_nidec_path(self):
|
||||
from iqdbc.car import gen_empty_fingerprint
|
||||
from iqdbc.car.honda.interface import CarInterface
|
||||
from iqdbc.car.honda.values import CAR
|
||||
CP = self.build(CAR.HONDA_CIVIC)
|
||||
CP_IQ = CarInterface.get_params_iq(CP, CAR.HONDA_CIVIC, gen_empty_fingerprint(), [], False, False, False)
|
||||
ri = CarInterface.RadarInterface(CP, CP_IQ)
|
||||
assert ri.scanner is None
|
||||
assert ri.trigger_msg == 0x445
|
||||
|
||||
def test_radar_interface_sleeps_when_unavailable(self):
|
||||
from iqdbc.car import gen_empty_fingerprint
|
||||
from iqdbc.car.honda.interface import CarInterface
|
||||
from iqdbc.car.honda.values import CAR
|
||||
CP = self.build(CAR.HONDA_E)
|
||||
CP_IQ = CarInterface.get_params_iq(CP, CAR.HONDA_E, gen_empty_fingerprint(), [], False, False, False)
|
||||
ri = CarInterface.RadarInterface(CP, CP_IQ)
|
||||
assert ri.scanner is None and ri.rcp is None
|
||||
@@ -0,0 +1,84 @@
|
||||
from iqdbc.can.dbc import DBC as DbcFile
|
||||
from iqdbc.car import Bus
|
||||
from iqdbc.car.honda.values import CAR, DBC, HONDA_RADAR_SCAN_CAPABLE, HONDA_RADAR_SCAN_VERIFIED
|
||||
from iqdbc.dbc.generator.honda.honda_radar_scan import (FRAME_SIGNALS, QUARTET_KINDS, SCAN_SLOTS,
|
||||
frame_address, motion_address, quartet_base_address)
|
||||
|
||||
SCAN_DBC_NAME = 'honda_radar_scan_generated'
|
||||
|
||||
|
||||
class TestScanAddressing:
|
||||
def test_quartet_bases(self):
|
||||
assert [quartet_base_address(s) for s in range(SCAN_SLOTS)] == \
|
||||
[0x280, 0x284, 0x288, 0x28C, 0x2D0, 0x2D4, 0x2D8, 0x2DC, 0x2E0, 0x2E4, 0x2E8, 0x2EC, 0x2F0, 0x2F4, 0x2F8, 0x2FC]
|
||||
|
||||
def test_motion_addresses(self):
|
||||
assert [motion_address(s) for s in range(SCAN_SLOTS)] == \
|
||||
[0x2C8, 0x2C9, 0x2CA, 0x2CB, 0x2CC, 0x2CD, 0x2CE, 0x2CF, 0x290, 0x291, 0x292, 0x293, 0x294, 0x295, 0x296, 0x297]
|
||||
|
||||
def test_eighty_unique_addresses(self):
|
||||
addrs = [frame_address(s, k) for s in range(SCAN_SLOTS) for k in (*QUARTET_KINDS, "MOTION")]
|
||||
assert len(addrs) == 80
|
||||
assert len(set(addrs)) == 80
|
||||
|
||||
def test_quartet_kind_order(self):
|
||||
for slot in range(SCAN_SLOTS):
|
||||
base = quartet_base_address(slot)
|
||||
assert [frame_address(slot, k) for k in QUARTET_KINDS] == [base, base + 1, base + 2, base + 3]
|
||||
|
||||
|
||||
class TestScanDbcGeometry:
|
||||
def setup_method(self):
|
||||
self.dbc = DbcFile(SCAN_DBC_NAME)
|
||||
|
||||
def geometry(self, addr):
|
||||
msg = self.dbc.addr_to_msg[addr]
|
||||
return {sig.name: (sig.start_bit, sig.size) for sig in msg.sigs.values()}
|
||||
|
||||
def test_every_frame_present_with_size_8(self):
|
||||
for slot in range(SCAN_SLOTS):
|
||||
for kind in (*QUARTET_KINDS, "MOTION"):
|
||||
msg = self.dbc.addr_to_msg[frame_address(slot, kind)]
|
||||
assert msg.name == f"RADAR_SCAN_{slot:02d}_{kind}"
|
||||
assert msg.size == 8
|
||||
|
||||
def test_bit_geometry_matches_spec(self):
|
||||
expected = {kind: {name: (start, size) for name, start, size in sigs} for kind, sigs in FRAME_SIGNALS.items()}
|
||||
for slot in range(SCAN_SLOTS):
|
||||
for kind in (*QUARTET_KINDS, "MOTION"):
|
||||
assert self.geometry(frame_address(slot, kind)) == expected[kind], (slot, kind)
|
||||
|
||||
def test_pos_frame_field_widths(self):
|
||||
geo = self.geometry(frame_address(0, "POS"))
|
||||
assert geo["DIST_RAW"] == (23, 12)
|
||||
assert geo["BEARING_RAW"] == (39, 11)
|
||||
assert geo["SCAN_STATE"] == (15, 4)
|
||||
assert geo["DIST_SIGMA_RAW"] == (7, 7)
|
||||
|
||||
def test_ident_handle_is_byte_six(self):
|
||||
geo = self.geometry(frame_address(0, "IDENT"))
|
||||
assert geo["OBJECT_HANDLE"] == (55, 8)
|
||||
|
||||
def test_motion_field_widths(self):
|
||||
geo = self.geometry(frame_address(0, "MOTION"))
|
||||
assert geo["CLOSING_SPEED_RAW"] == (7, 11)
|
||||
assert geo["CLOSING_SPEED_SIGMA_RAW"] == (23, 10)
|
||||
assert geo["DIST_RATIO_RAW"] == (55, 10)
|
||||
|
||||
def test_cycle_positions_per_kind(self):
|
||||
positions = {"POS": (27, 4), "SHAPE": (28, 4), "LIFE": (11, 4), "IDENT": (12, 4), "MOTION": (12, 4)}
|
||||
for kind, expected in positions.items():
|
||||
assert self.geometry(frame_address(3, kind))["CYCLE"] == expected
|
||||
|
||||
|
||||
class TestScanPlatformWiring:
|
||||
def test_scan_dbc_on_exactly_the_capable_family(self):
|
||||
for car in CAR:
|
||||
has_scan_dbc = DBC[car].get(Bus.radar) == SCAN_DBC_NAME
|
||||
assert has_scan_dbc == (car in HONDA_RADAR_SCAN_CAPABLE), car
|
||||
|
||||
def test_verified_platforms_are_capable(self):
|
||||
assert HONDA_RADAR_SCAN_VERIFIED <= HONDA_RADAR_SCAN_CAPABLE
|
||||
|
||||
def test_verified_set(self):
|
||||
assert HONDA_RADAR_SCAN_VERIFIED == {CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G}
|
||||
@@ -162,7 +162,7 @@ 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.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
)
|
||||
HONDA_ACCORD = HondaBoschPlatformConfig(
|
||||
[
|
||||
@@ -172,7 +172,7 @@ 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.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
flags=HondaFlags.ALLOW_MANUAL_TRANS,
|
||||
)
|
||||
HONDA_ACCORD_11G = HondaBoschCANFDPlatformConfig(
|
||||
@@ -190,13 +190,13 @@ 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.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
flags=HondaFlags.ALLOW_MANUAL_TRANS,
|
||||
)
|
||||
HONDA_CIVIC_BOSCH_DIESEL = HondaBoschPlatformConfig(
|
||||
[], # don't show in docs
|
||||
HONDA_CIVIC_BOSCH.specs,
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'},
|
||||
{Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
)
|
||||
HONDA_CIVIC_2022 = HondaBoschPlatformConfig(
|
||||
[
|
||||
@@ -215,7 +215,8 @@ 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.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.body: 'honda_crv_ex_2017_body_generated',
|
||||
Bus.radar: 'honda_radar_scan_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
HONDA_CRV_6G = HondaBoschCANFDPlatformConfig(
|
||||
@@ -229,7 +230,7 @@ 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.pt: 'honda_civic_hatchback_ex_2017_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
)
|
||||
HONDA_HRV_3G = HondaBoschPlatformConfig(
|
||||
[HondaCarDocs("Honda HR-V 2023-25", "All")],
|
||||
@@ -246,18 +247,18 @@ 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.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
flags=HondaFlags.BOSCH_ALT_BRAKE,
|
||||
)
|
||||
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.pt: 'honda_insight_ex_2019_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
)
|
||||
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.pt: 'acura_rdx_2020_can_generated', Bus.radar: 'honda_radar_scan_generated'},
|
||||
)
|
||||
HONDA_PILOT_4G = HondaBoschCANFDPlatformConfig(
|
||||
[HondaCarDocs("Honda Pilot 2023-25", "All")],
|
||||
@@ -387,6 +388,11 @@ HONDA_BOSCH_CANFD = CAR.with_flags(HondaFlags.BOSCH_CANFD)
|
||||
HONDA_BOSCH_ALT_RADAR = CAR.with_flags(HondaFlags.BOSCH_ALT_RADAR)
|
||||
HONDA_BOSCH_TJA_CONTROL = CAR.with_flags(HondaFlags.BOSCH_TJA_CONTROL)
|
||||
|
||||
# Bosch harness family whose radar broadcasts the decodable 16-slot object scan
|
||||
HONDA_RADAR_SCAN_CAPABLE = HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD - HONDA_BOSCH_ALT_RADAR
|
||||
# scan decode stays off per platform until a real route capture has been validated
|
||||
HONDA_RADAR_SCAN_VERIFIED = frozenset({CAR.HONDA_ACCORD, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G})
|
||||
|
||||
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
VERSION "honda_radar_scan -- Honda Bosch radar object scan: 16 object slots, each a POS/SHAPE/LIFE/IDENT frame quartet plus a synchronized MOTION companion. Raw counts only; physical conversion lives in iqdbc/car/honda/radar_scan.py"
|
||||
|
||||
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_: SCANNER XXX
|
||||
|
||||
|
||||
BO_ 640 RADAR_SCAN_00_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 641 RADAR_SCAN_00_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 642 RADAR_SCAN_00_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 643 RADAR_SCAN_00_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 712 RADAR_SCAN_00_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 644 RADAR_SCAN_01_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 645 RADAR_SCAN_01_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 646 RADAR_SCAN_01_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 647 RADAR_SCAN_01_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 713 RADAR_SCAN_01_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 648 RADAR_SCAN_02_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 649 RADAR_SCAN_02_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 650 RADAR_SCAN_02_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 651 RADAR_SCAN_02_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 714 RADAR_SCAN_02_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 652 RADAR_SCAN_03_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 653 RADAR_SCAN_03_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 654 RADAR_SCAN_03_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 655 RADAR_SCAN_03_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 715 RADAR_SCAN_03_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 720 RADAR_SCAN_04_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 721 RADAR_SCAN_04_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 722 RADAR_SCAN_04_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 723 RADAR_SCAN_04_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 716 RADAR_SCAN_04_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 724 RADAR_SCAN_05_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 725 RADAR_SCAN_05_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 726 RADAR_SCAN_05_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 727 RADAR_SCAN_05_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 717 RADAR_SCAN_05_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 728 RADAR_SCAN_06_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 729 RADAR_SCAN_06_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 730 RADAR_SCAN_06_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 731 RADAR_SCAN_06_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 718 RADAR_SCAN_06_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 732 RADAR_SCAN_07_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 733 RADAR_SCAN_07_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 734 RADAR_SCAN_07_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 735 RADAR_SCAN_07_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 719 RADAR_SCAN_07_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 736 RADAR_SCAN_08_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 737 RADAR_SCAN_08_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 738 RADAR_SCAN_08_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 739 RADAR_SCAN_08_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 656 RADAR_SCAN_08_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 740 RADAR_SCAN_09_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 741 RADAR_SCAN_09_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 742 RADAR_SCAN_09_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 743 RADAR_SCAN_09_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 657 RADAR_SCAN_09_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 744 RADAR_SCAN_10_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 745 RADAR_SCAN_10_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 746 RADAR_SCAN_10_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 747 RADAR_SCAN_10_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 658 RADAR_SCAN_10_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 748 RADAR_SCAN_11_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 749 RADAR_SCAN_11_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 750 RADAR_SCAN_11_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 751 RADAR_SCAN_11_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 659 RADAR_SCAN_11_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 752 RADAR_SCAN_12_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 753 RADAR_SCAN_12_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 754 RADAR_SCAN_12_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 755 RADAR_SCAN_12_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 660 RADAR_SCAN_12_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 756 RADAR_SCAN_13_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 757 RADAR_SCAN_13_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 758 RADAR_SCAN_13_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 759 RADAR_SCAN_13_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 661 RADAR_SCAN_13_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 760 RADAR_SCAN_14_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 761 RADAR_SCAN_14_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 762 RADAR_SCAN_14_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 763 RADAR_SCAN_14_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 662 RADAR_SCAN_14_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
|
||||
BO_ 764 RADAR_SCAN_15_POS: 8 SCANNER
|
||||
SG_ SCAN_STATE : 15|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CYCLE : 27|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ DIST_RAW : 23|12@0+ (1,0) [0|4095] "" XXX
|
||||
SG_ BEARING_RAW : 39|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ DIST_SIGMA_RAW : 7|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 765 RADAR_SCAN_15_SHAPE: 8 SCANNER
|
||||
SG_ CYCLE : 28|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ PRESENCE_RAW : 46|7@0+ (1,0) [0|127] "" XXX
|
||||
|
||||
BO_ 766 RADAR_SCAN_15_LIFE: 8 SCANNER
|
||||
SG_ CYCLE : 11|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ AGE_RAW : 7|12@0+ (1,0) [0|4095] "" XXX
|
||||
|
||||
BO_ 767 RADAR_SCAN_15_IDENT: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ OBJECT_HANDLE : 55|8@0+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 663 RADAR_SCAN_15_MOTION: 8 SCANNER
|
||||
SG_ CYCLE : 12|4@0+ (1,0) [0|15] "" XXX
|
||||
SG_ CLOSING_SPEED_RAW : 7|11@0+ (1,0) [0|2047] "" XXX
|
||||
SG_ CLOSING_SPEED_SIGMA_RAW : 23|10@0+ (1,0) [0|1023] "" XXX
|
||||
SG_ DIST_RATIO_RAW : 55|10@0+ (1,0) [0|1023] "" XXX
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import os
|
||||
|
||||
SCAN_SLOTS = 16
|
||||
|
||||
FRAME_SIGNALS = {
|
||||
"POS": [
|
||||
("SCAN_STATE", 15, 4),
|
||||
("CYCLE", 27, 4),
|
||||
("DIST_RAW", 23, 12),
|
||||
("BEARING_RAW", 39, 11),
|
||||
("DIST_SIGMA_RAW", 7, 7),
|
||||
],
|
||||
"SHAPE": [
|
||||
("CYCLE", 28, 4),
|
||||
("PRESENCE_RAW", 46, 7),
|
||||
],
|
||||
"LIFE": [
|
||||
("CYCLE", 11, 4),
|
||||
("AGE_RAW", 7, 12),
|
||||
],
|
||||
"IDENT": [
|
||||
("CYCLE", 12, 4),
|
||||
("OBJECT_HANDLE", 55, 8),
|
||||
],
|
||||
"MOTION": [
|
||||
("CYCLE", 12, 4),
|
||||
("CLOSING_SPEED_RAW", 7, 11),
|
||||
("CLOSING_SPEED_SIGMA_RAW", 23, 10),
|
||||
("DIST_RATIO_RAW", 55, 10),
|
||||
],
|
||||
}
|
||||
|
||||
QUARTET_KINDS = ("POS", "SHAPE", "LIFE", "IDENT")
|
||||
|
||||
|
||||
def quartet_base_address(slot: int) -> int:
|
||||
return 0x280 + 4 * slot if slot < 4 else 0x2D0 + 4 * (slot - 4)
|
||||
|
||||
|
||||
def motion_address(slot: int) -> int:
|
||||
return 0x2C8 + slot if slot < 8 else 0x290 + (slot - 8)
|
||||
|
||||
|
||||
def frame_address(slot: int, kind: str) -> int:
|
||||
if kind == "MOTION":
|
||||
return motion_address(slot)
|
||||
return quartet_base_address(slot) + QUARTET_KINDS.index(kind)
|
||||
|
||||
|
||||
HEADER = """VERSION "honda_radar_scan -- Honda Bosch radar object scan: 16 object slots, each a POS/SHAPE/LIFE/IDENT \
|
||||
frame quartet plus a synchronized MOTION companion. Raw counts only; physical conversion lives in \
|
||||
iqdbc/car/honda/radar_scan.py"
|
||||
|
||||
NS_ :
|
||||
\tNS_DESC_
|
||||
\tCM_
|
||||
\tBA_DEF_
|
||||
\tBA_
|
||||
\tVAL_
|
||||
\tCAT_DEF_
|
||||
\tCAT_
|
||||
\tFILTER
|
||||
\tBA_DEF_DEF_
|
||||
\tEV_DATA_
|
||||
\tENVVAR_DATA_
|
||||
\tSGTYPE_
|
||||
\tSGTYPE_VAL_
|
||||
\tBA_DEF_SGTYPE_
|
||||
\tBA_SGTYPE_
|
||||
\tSIG_TYPE_REF_
|
||||
\tVAL_TABLE_
|
||||
\tSIG_GROUP_
|
||||
\tSIG_VALTYPE_
|
||||
\tSIGTYPE_VALTYPE_
|
||||
\tBO_TX_BU_
|
||||
\tBA_DEF_REL_
|
||||
\tBA_REL_
|
||||
\tBA_DEF_DEF_REL_
|
||||
\tBU_SG_REL_
|
||||
\tBU_EV_REL_
|
||||
\tBU_BO_REL_
|
||||
\tSG_MUL_VAL_
|
||||
|
||||
BS_:
|
||||
|
||||
BU_: SCANNER XXX
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def render() -> str:
|
||||
parts = [HEADER]
|
||||
for slot in range(SCAN_SLOTS):
|
||||
for kind in (*QUARTET_KINDS, "MOTION"):
|
||||
addr = frame_address(slot, kind)
|
||||
parts.append(f"\nBO_ {addr} RADAR_SCAN_{slot:02d}_{kind}: 8 SCANNER\n")
|
||||
for name, start_bit, size in FRAME_SIGNALS[kind]:
|
||||
parts.append(f" SG_ {name} : {start_bit}|{size}@0+ (1,0) [0|{(1 << size) - 1}] \"\" XXX\n")
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
out_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "honda_radar_scan.dbc")
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write(render())
|
||||
Reference in New Issue
Block a user