mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-21 10:22:10 +08:00
more dbc updates
This commit is contained in:
+1
-1
Submodule opendbc_repo updated: a81c9fdb9f...e4360b039f
@@ -7,7 +7,7 @@ See the LICENSE.md file in the root directory for more details.
|
||||
from dataclasses import dataclass
|
||||
import math
|
||||
import pyray as rl
|
||||
from opendbc.car.hyundai.radar_interface import RADAR_3A5_3C4
|
||||
from opendbc.car.hyundai.radar_interface import RADAR_235_248, RADAR_3A5_3C4
|
||||
from openpilot.system.ui.lib.application import FontWeight
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
|
||||
@@ -31,6 +31,7 @@ class ProjectedRadarTrack:
|
||||
radius: float
|
||||
color: object
|
||||
source_index: int
|
||||
camera_object: bool
|
||||
track_id: int
|
||||
|
||||
|
||||
@@ -62,7 +63,31 @@ def radar_track_source_index(track, sources) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def draw_radar_source_marker(center: rl.Vector2, radius: float, color: rl.Color, source_index: int) -> None:
|
||||
def is_camera_object_source(source) -> bool:
|
||||
return source.startAddress == RADAR_235_248.start_addr and source.endAddress == RADAR_235_248.end_addr
|
||||
|
||||
|
||||
def radar_track_source(track, sources):
|
||||
address = int(track.sourceAddress)
|
||||
bus = int(track.sourceBus)
|
||||
if address == 0:
|
||||
return None
|
||||
return next((
|
||||
source for source in sources
|
||||
if source.startAddress <= address <= source.endAddress and source.bus == bus
|
||||
), None)
|
||||
|
||||
|
||||
def radar_source_label(source) -> str:
|
||||
prefix = "CAM " if is_camera_object_source(source) else ""
|
||||
return f"{prefix}{source.startAddress:X}-{source.endAddress:X}"
|
||||
|
||||
|
||||
def draw_radar_source_marker(center: rl.Vector2, radius: float, color: rl.Color, source_index: int,
|
||||
camera_object: bool = False) -> None:
|
||||
if camera_object:
|
||||
rl.draw_poly(center, 3, radius, -90.0, color)
|
||||
return
|
||||
if source_index <= 0:
|
||||
rl.draw_circle(int(center.x), int(center.y), radius, color)
|
||||
return
|
||||
@@ -113,7 +138,7 @@ def format_radar_tracks_onroad_columns(live_tracks, v_ego: float = 0.0) -> tuple
|
||||
if not sources:
|
||||
return "", "none", "", "", "", ""
|
||||
|
||||
range_text = "\n".join(f"{source.startAddress:X}-{source.endAddress:X}" for source in sources)
|
||||
range_text = "\n".join(radar_source_label(source) for source in sources)
|
||||
count_text = "\n".join(str(source.trackCount) for source in sources)
|
||||
motion_states = [int(track.motionState) for track in live_tracks.points]
|
||||
|
||||
@@ -283,12 +308,14 @@ class RadarTracks:
|
||||
radius = max(1, track_size - 5) if stationary else track_size
|
||||
if not measured:
|
||||
radius = max(1, radius - COASTED_RADIUS_REDUCTION)
|
||||
source = radar_track_source(track, sources)
|
||||
projected_tracks.append(ProjectedRadarTrack(
|
||||
x=pt[0],
|
||||
y=pt[1],
|
||||
radius=radius,
|
||||
color=color,
|
||||
source_index=radar_track_source_index(track, sources),
|
||||
camera_object=source is not None and is_camera_object_source(source),
|
||||
track_id=int(track.trackId),
|
||||
))
|
||||
|
||||
@@ -307,7 +334,7 @@ class RadarTracks:
|
||||
rl.draw_ring(center, track.radius + 2, track.radius + 5, 0, 360, 24, highlight_color)
|
||||
highlighted_positions[track.track_id] = (x, y)
|
||||
draw_radar_source_marker(
|
||||
rl.Vector2(x, y), track.radius, track.color, track.source_index,
|
||||
rl.Vector2(x, y), track.radius, track.color, track.source_index, track.camera_object,
|
||||
)
|
||||
|
||||
return highlighted_positions
|
||||
|
||||
@@ -78,6 +78,16 @@ def test_format_radar_tracks_columns_range_and_count():
|
||||
assert format_radar_tracks_onroad_columns(live_tracks) == ("3A5-3C4", "2", "1", "1", "0", "")
|
||||
|
||||
|
||||
def test_format_camera_objects_are_not_labeled_as_radar():
|
||||
live_tracks = car.RadarData.new_message()
|
||||
live_tracks.trackSources = [{"startAddress": 0x235, "endAddress": 0x248, "bus": 1, "trackCount": 3}]
|
||||
points = live_tracks.init("points", 3)
|
||||
for point in points:
|
||||
point.motionState = 2
|
||||
|
||||
assert format_radar_tracks_onroad_columns(live_tracks) == ("CAM 235-248", "3", "3", "0", "0", "")
|
||||
|
||||
|
||||
def test_format_radar_tracks_columns_stacks_all_ranges_with_preferred_first():
|
||||
live_tracks = car.RadarData.new_message()
|
||||
live_tracks.trackSources = [
|
||||
@@ -114,6 +124,13 @@ def test_format_radar_tracks_columns_shows_non_motion_source():
|
||||
assert format_radar_tracks_onroad_columns(live_tracks, v_ego=20.0) == ("500-51F", "4", "0", "0", "4", "")
|
||||
|
||||
|
||||
def test_format_radar_tracks_columns_shows_64_track_source():
|
||||
live_tracks = car.RadarData.new_message()
|
||||
live_tracks.trackSources = [{"startAddress": 0x500, "endAddress": 0x53F, "bus": 1, "trackCount": 7}]
|
||||
|
||||
assert format_radar_tracks_onroad_columns(live_tracks) == ("500-53F", "7", "0", "0", "0", "")
|
||||
|
||||
|
||||
def test_draw_radar_tracks_applies_screen_offset(monkeypatch):
|
||||
live_tracks = car.RadarData.new_message()
|
||||
points = live_tracks.init("points", 1)
|
||||
@@ -203,6 +220,30 @@ def test_draw_radar_tracks_uses_source_shapes_with_preferred_circle(monkeypatch)
|
||||
assert polygons == [(0x500, 4, 6, 45.0)]
|
||||
|
||||
|
||||
def test_draw_camera_objects_uses_triangle(monkeypatch):
|
||||
live_tracks = car.RadarData.new_message()
|
||||
live_tracks.trackSources = [{"startAddress": 0x235, "endAddress": 0x248, "bus": 1, "trackCount": 1}]
|
||||
point = live_tracks.init("points", 1)[0]
|
||||
point.dRel = 25
|
||||
point.yRel = 1
|
||||
point.vRel = 2
|
||||
point.motionState = radar_tracks.DBC_MOTION_MOVING
|
||||
point.measured = True
|
||||
point.sourceAddress = 0x235
|
||||
point.sourceBus = 1
|
||||
polygons = []
|
||||
monkeypatch.setattr(
|
||||
radar_tracks.rl, "draw_poly",
|
||||
lambda center, sides, radius, rotation, color: polygons.append((center.x, sides, radius, rotation)),
|
||||
)
|
||||
|
||||
radar_tracks.RadarTracks().draw_radar_tracks(
|
||||
live_tracks, lambda d_rel, y_rel, z: (d_rel, 30), path_offset_z=1.2, track_size=6,
|
||||
)
|
||||
|
||||
assert polygons == [(25, 3, 6, -90.0)]
|
||||
|
||||
|
||||
def test_draw_radar_tracks_shrinks_stationary_dots(monkeypatch):
|
||||
live_tracks = car.RadarData.new_message()
|
||||
point = live_tracks.init("points", 1)[0]
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Hyundai/Kia/Genesis radar range matrix
|
||||
|
||||
This is a forum-ready summary of the routes analyzed for the sunnypilot radar
|
||||
tracks work. It describes observed protocol families, not guaranteed equipment
|
||||
for every model year, market, trim, or harness. A range can be present but empty
|
||||
when the radar is inactive. Raw bus number is also not a layout identifier:
|
||||
follow the sustained logical A-CAN source and reject brief forwarded copies.
|
||||
|
||||
Status terms:
|
||||
|
||||
- **Confirmed**: complete range, checksum/cadence, and tracked-object layout
|
||||
were validated across route data.
|
||||
- **Observed/optional**: verified on at least one qualifying route, but not
|
||||
every route or trim carrying the main front-radar family.
|
||||
- **Candidate**: source association or exact semantics remain incomplete.
|
||||
- **Not radar**: camera or fused-output traffic overlapping radar-like ranges.
|
||||
|
||||
## Platform matrix
|
||||
|
||||
| Platform or platform group | Front tracked-object range | Front layout | Additional radar traffic | Camera/fused traffic that is not another radar | Status and notes |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Kia K7 2017; older Hyundai Ioniq; sampled Ioniq PHEV | `0x500–0x53F` | 64 slots, 8 B each, about 20 Hz | `0x4E0`, `0x4E1`, `0x4E3` status/path messages | Legacy `0x238–0x24F` may coexist on applicable cars | **Confirmed** 64-slot legacy/Delphi-ESR dialect. |
|
||||
| Kia Niro EV first generation; legacy Hyundai Palisade; Sonata; Sonata Hybrid | `0x500–0x51F` | 32 slots, 8 B each, about 20 Hz | `0x4E0–0x4E5` status/build/health/alignment family | Legacy `0x238–0x255` or related camera candidates may coexist by platform | **Confirmed** 32-slot legacy dialect. The upper `0x520–0x53F` bank is absent. |
|
||||
| Hyundai Kona EV 2022; Kia Ceed PHEV | `0x602–0x617` | Two packed tracks in implemented messages | None established | None established | **Observed/candidate** legacy front family; the relationship between `0x602–0x611` and `0x612–0x617` remains incomplete. |
|
||||
| Hyundai Elantra HEV 2024 | `0x210–0x21F` | Detailed dialect, 32 tracks in 16 × 32 B messages, 20 Hz | No paired corner family established | Platform-dependent camera traffic | **Confirmed** detailed `210` front radar. |
|
||||
| Hyundai Ioniq 5; Hyundai Palisade 2023; Kia EV6, older HDA2 samples | `0x210–0x21F` | Detailed dialect, 32 tracks | Right-front candidate: `0x240–0x24F` objects plus `0x270–0x277` compact records. Left-front candidate: `0x278–0x287` objects plus `0x288–0x28F` compact records. | Isolated `0x235` traffic is not a complete camera-object family | **Confirmed** front radar. Paired front corners are **observed/optional** only on qualifying routes; A=right and B=left are strongly inferred but still need physical occlusion confirmation. |
|
||||
| Hyundai Santa Cruz 2025; Tucson 4th generation; Tucson HEV 2025; Kia Sportage 5th generation | `0x210–0x21F` | Compact dialect, 32 tracks | No corner family established in checked routes | Platform-dependent camera traffic | **Confirmed** compact `210` front radar. Compact and detailed `210` share core kinematics but use different lifecycle fields. |
|
||||
| Hyundai Ioniq 6 | `0x3A5–0x3C4` | Common 32-track layout, 24 B each, 20 Hz | No independent corner range established | `0x235–0x248` camera objects and `0x360–0x366` camera lane/path may be present | **Confirmed** common `3A5` front radar. |
|
||||
| Hyundai Kona/Kona HEV/Kona EV 2nd generation; Santa Fe HEV 5th generation; Sonata 2024 | `0x3A5–0x3C4` | Common 32-track layout | No independent corner range established | Platform-dependent `0x235`, `0x360`, and CCNC `0x162` fused traffic | **Confirmed** common `3A5` front radar. CCNC/HDA generation does not imply rich radar extensions. |
|
||||
| Kia Carnival HEV 2026; EV6 2025; K4 2025; K5 2025; Niro EV/HEV 2nd generation | `0x3A5–0x3C4` | Common 32-track layout | No independent corner range established | Platform-dependent `0x235`, `0x360`, and CCNC `0x162` fused traffic | **Confirmed** common `3A5` front radar. Bus 0/1 varies by architecture; a bus-2 copy can be forwarding. |
|
||||
| Kia EV9; Hyundai Ioniq 9 | `0x3A5–0x3C4` | Rich 32-track layout with optional width, length, absolute speed, and orientation | `0x3D0–0x3D4` synchronized 32-position auxiliary scan array | `0x235–0x248` camera objects; `0x360–0x366` camera lane/path; CCNC `0x162` fused traffic | **Confirmed** rich `3A5`; `3D0` is **observed/optional** radar auxiliary traffic. Former `3D0` speed/azimuth interpretations were falsified, so it is counted but not projected as geometry. |
|
||||
| Kia Sportage HEV 2026 forum sample | Overlaps `0x3A5–0x3C4` addresses | Payload is incompatible with the common `3A5` layout | Unknown | Unknown | **Excluded** from the common decoder; address overlap alone is not layout compatibility. |
|
||||
|
||||
## Other observed ranges
|
||||
|
||||
| Range | Applicable examples | Classification |
|
||||
| --- | --- | --- |
|
||||
| `0x235–0x248`, 32 B at about 33 Hz | Newer CAN-FD platforms, including rich EV9/Ioniq 9 captures | **Not radar:** forward-camera object list with class, motion, ID, geometry, velocity, and acceleration. |
|
||||
| `0x360–0x366`, 32 B | Newer CAN-FD platforms carrying camera lane/path output | **Not radar:** forward-camera lane/path family. |
|
||||
| `0x180–0x184`, `0x1B6–0x1B9`, `0x2BB–0x2BE` | Sampled Kia Sorento/Sorento HEV and Genesis GV70 candidates | **Not radar:** forward-camera auxiliary lists according to downloaded Hyundai DBCs. |
|
||||
| `0x238–0x24F`, 8 B | Ioniq PHEV/HEV 2022, legacy Palisade, Niro PHEV candidates | **Not established as radar:** strong legacy forward-camera-perception candidate. |
|
||||
| `0x238–0x255`, 8 B, often with `0x25A–0x25E` | Custin, Elantra/Elantra HEV 2021, Sonata/Sonata Hybrid candidates | **Not established as radar:** extended legacy forward-camera-perception candidate. |
|
||||
| `CCNC_0x162` | CCNC platforms | **Not raw radar:** stock fused/cluster lead, side, and rear slots with unknown upstream fusion. |
|
||||
| Bus-9 `0x300/0x400/0x500/0x600` blocks | Historical EV6 prototype capture | **Historical candidate:** four-corner FL/FR/RL/RR prototype; not reconfirmed in the current production-route corpus. |
|
||||
|
||||
## Supported combinations observed in the corpus
|
||||
|
||||
| Combination | Front | Corners or radar auxiliary |
|
||||
| --- | --- | --- |
|
||||
| Legacy front, 32 slots | `0x500–0x51F` | `0x4E0–0x4E5` companion/status |
|
||||
| Legacy front, 64 slots | `0x500–0x53F` | `0x4E0`, `0x4E1`, `0x4E3` companion/status |
|
||||
| Legacy alternate front | `0x602–0x617` | None established |
|
||||
| CAN-FD detailed front only | `0x210–0x21F` | None established |
|
||||
| CAN-FD detailed front plus paired front corners | `0x210–0x21F` | A/right candidate `0x240/0x270` families plus B/left candidate `0x278/0x288` families |
|
||||
| CAN-FD compact front only | `0x210–0x21F` | None established |
|
||||
| CAN-FD common front | `0x3A5–0x3C4` | Camera/fused families may coexist but are not independent radar tracks |
|
||||
| CAN-FD rich front plus auxiliary scan | `0x3A5–0x3C4` | Optional `0x3D0–0x3D4` auxiliary radar array |
|
||||
|
||||
No checked route proves a combination containing both the paired
|
||||
`0x240/0x270–0x28F` corner family and `0x3D0–0x3D4`. No distinct production
|
||||
rear-radar tracked-object range has been confirmed. Rear objects in
|
||||
`CCNC_0x162` are fused outputs and do not prove a raw rear-radar range.
|
||||
+207
-210
@@ -1,231 +1,228 @@
|
||||
# Hyundai/Kia/Genesis radar research roadmap
|
||||
# Hyundai/Kia/Genesis radar source inventory
|
||||
|
||||
This document tracks which object sources exist on each platform, what role
|
||||
they appear to serve, and what must still be proved. Detailed signal evidence
|
||||
for the best-understood family is kept in [SIGNALS.md](SIGNALS.md).
|
||||
This document separates physical radar sources from camera and fused-object
|
||||
traffic. Signal-level evidence is in [SIGNALS.md](SIGNALS.md).
|
||||
The copy-ready vehicle/range summary is in
|
||||
[PLATFORM_MATRIX.md](PLATFORM_MATRIX.md).
|
||||
|
||||
The intended end state is an inventory for every platform with four slots:
|
||||
## Confidence and safety rules
|
||||
|
||||
1. **Front** — the forward long-range radar used for longitudinal driving.
|
||||
2. **Corner** — one CAN message family representing the car's corner-radar
|
||||
system. A family may contain two or four physical sensors; "one corner
|
||||
source" does not mean one sensor.
|
||||
3. **Extras/fused** — ADAS or cluster object outputs that may combine radar,
|
||||
camera, and other inputs. These must not be called raw radar without proof.
|
||||
4. **Rear** — a distinct rear-facing track source, if the car exposes one.
|
||||
Rear tracks may instead be part of the corner family.
|
||||
- **Confirmed layout** means that bit positions, scales, and behavior agree
|
||||
across multiple routes. It does not automatically prove the physical ECU.
|
||||
- **Strong role** means that topology, cadence, geometry, and related messages
|
||||
support the assignment, but a controlled sensor-occlusion test is still
|
||||
desirable.
|
||||
- **Candidate** means useful research data only. Candidate fields must not
|
||||
affect control or production filtering.
|
||||
- Identify a source by the complete address set, payload sizes, cadence,
|
||||
checksum behavior, and logical CAN path. An address or raw bus number alone
|
||||
is not enough.
|
||||
- A short copy on another bus is normally a forwarding echo. It is not a
|
||||
second sensor unless timestamps and payload differences prove independence.
|
||||
|
||||
Each slot should eventually name a confirmed source or say **verified absent**.
|
||||
The inventory must not assume that every platform has every type of hardware.
|
||||
## Current source families
|
||||
|
||||
## Confidence labels
|
||||
|
||||
- **Confirmed** — decoded geometry and behavior agree across multiple routes
|
||||
or controlled observations.
|
||||
- **Implemented** — a runtime layout exists and produces plausible tracks, but
|
||||
its physical role or platform coverage still needs systematic confirmation.
|
||||
- **Candidate** — a route/post/code clue identifies a likely source, but its
|
||||
role or layout is not yet established.
|
||||
- **Prototype** — useful prior reverse-engineering evidence from code that was
|
||||
never merged or completed.
|
||||
- **Unknown** — detected traffic with no reliable role yet.
|
||||
- **Incompatible** — the address range overlaps a known family but its bytes
|
||||
do not use that layout.
|
||||
- **Verified absent** — the relevant buses were observed under suitable
|
||||
conditions and no source exists.
|
||||
|
||||
## Research order
|
||||
|
||||
### 1. Finish the front-radar inventory
|
||||
|
||||
Front radar comes first because it is the source that can affect openpilot
|
||||
driving. For each existing layout, validate its bus, message size, cadence,
|
||||
track lifecycle, distance, lateral position, relative velocity, and duplicate
|
||||
forwarding behavior across multiple platforms.
|
||||
|
||||
Then investigate the unimplemented route families in this order:
|
||||
|
||||
1. `0x238–0x24F`
|
||||
2. `0x238–0x255`
|
||||
3. the combined `0x180–0x184`, `0x1B6–0x1B9`, and `0x2BB–0x2BF` sets
|
||||
4. the incomplete `0x500–?` Ioniq PHEV 2019 observation
|
||||
|
||||
These are front-radar candidates because of how they were grouped in the
|
||||
supplied route post, not because their physical role has been proved.
|
||||
|
||||
A front source should show forward-centred geometry, useful range ahead of the
|
||||
car, longitudinal relative speed, stable lead-vehicle continuity, and the
|
||||
expected response when ego speed changes. Video alignment and route maneuvers
|
||||
must confirm that it is not a corner or fused-object source.
|
||||
|
||||
### 2. Identify corner-radar systems
|
||||
|
||||
There are two separate clues and they must not be conflated:
|
||||
|
||||
- The supplied route post labels `0x235–0x248` as "corner radar available on
|
||||
some HDA2 vehicles." The branch already has a one-track-per-message decoder
|
||||
for this range, but its physical coverage still needs route-level proof.
|
||||
- The closed [EV6 corner-radar prototype PR](https://github.com/commaai/openpilot/pull/24221)
|
||||
used bus 9 and four eight-address blocks: `0x300–0x307`,
|
||||
`0x400–0x407`, `0x500–0x507`, and `0x600–0x607`. Each frame contained
|
||||
eight subtracks. The code mapped the four blocks to front-left, front-right,
|
||||
rear-left, and rear-right sensors, but relative speed and status decoding
|
||||
were unfinished.
|
||||
|
||||
The EV6 prototype is evidence for a four-corner architecture, not evidence
|
||||
that every HDA2 car or every `0x235–0x248` source uses that architecture.
|
||||
|
||||
Corner confirmation should use scenes with passing traffic, blind-spot
|
||||
occupancy, cross traffic, and targets moving around the car. Expected evidence
|
||||
includes short-to-medium range, strong lateral coverage, stable left/right
|
||||
sectors, and — if rear corners are included — tracks behind the rear axle.
|
||||
Bus/address blocks should be correlated with individual sectors and physical
|
||||
sensor occlusion where possible.
|
||||
|
||||
### 3. Trace extras and fused objects
|
||||
|
||||
Stock `CCNC_0x162` lead, left, right, and rear tofu slots are a separate fused
|
||||
cluster/ADAS output. Existing route evidence shows that these slots can be
|
||||
active when the optional `3A5–3C4` geometry fields are unavailable, so they
|
||||
cannot be reconstructed by treating the rich `3A5` bytes as extra tracks.
|
||||
|
||||
The next step is to locate the inputs upstream of `CCNC_0x162`, correlate each
|
||||
slot with front and corner tracks, and determine whether the output is radar,
|
||||
camera, or multi-sensor fusion. Unknown extra address groups and unknown DBC
|
||||
fields come after the front and corner inventories because their behavior is
|
||||
easier to interpret once the physical radar sources are known.
|
||||
|
||||
Fused outputs may be useful for cluster visualization, but they must not be
|
||||
fed into driving as independent radar measurements unless duplication,
|
||||
latency, lifecycle, and provenance are understood.
|
||||
|
||||
### 4. Confirm a rear source only when tracks exist
|
||||
|
||||
Do not create a separate rear-radar category merely because the cluster has
|
||||
rear tofu slots. Record a rear source only when a distinct message family
|
||||
outputs repeatable rear-facing tracks. If the rear tracks come from the same
|
||||
four-corner family, document that under **Corner** and mark **Rear** as
|
||||
contained in that family.
|
||||
|
||||
## Known and candidate source families
|
||||
|
||||
| Source | Runtime layout | Current role | Status and next proof |
|
||||
| Source | Layout | Role | Current conclusion |
|
||||
| --- | --- | --- | --- |
|
||||
| `0x3A5–0x3C4` | 32 × 24-byte messages at 20 Hz | Front | Confirmed across 21 common-layout routes and 817,852 checksum-valid frames. `TRACK_QUALITY`, `RCS`, lifecycle, kinematics, and the optional rich extension are decoded. No dedicated oncoming value was found. Sportage HEV 2026 has an incompatible overlapping layout. |
|
||||
| `0x210–0x21F` | 16 × 32-byte messages at 20 Hz; two tracks per message | Front | Confirmed across 16 active routes and two dialects. Detailed and compact layouts share the core; compact adds persistent object IDs and uses motion code 3 for stopped and 4 for oncoming, while detailed has no dedicated oncoming value. Three additional streams were empty and one non-HDA2 Telluride route had no 210 range. |
|
||||
| `0x500–0x51F` | 32 × 8-byte messages at 20 Hz | Likely front | Implemented. Confirm special behavior around `0x501/0x502` and validate lateral conversion from azimuth. |
|
||||
| `0x602–0x617` | Generator has double-track `0x602–0x611` and alternate single-track `0x612–0x617` formats | Likely front | Partially implemented: runtime currently consumes only `0x602–0x611`. Confirm whether `0x612–0x617` is the same objects, an alternate view, or a separate output before expanding it. |
|
||||
| `0x235–0x248` | 20 × 32-byte messages at 33 Hz; one track per message | Corner candidate | Implemented geometry, but the post's corner classification still needs HDA2 route/video confirmation. |
|
||||
| `0x238–0x24F` | Not implemented | Front candidate | Reverse layout and confirm physical role on the four listed platforms. It overlaps `0x235–0x248`, so detection must use the complete range, size, and layout rather than address membership alone. |
|
||||
| `0x238–0x255` | Not implemented | Front candidate | Reverse layout and distinguish it from both `0x238–0x24F` and `0x235–0x248`. |
|
||||
| `0x180–0x184`, `0x1B6–0x1B9`, `0x2BB–0x2BF` | Not implemented | Unknown multi-range source | Determine whether the three blocks are one radar protocol, multiple sensors, or fused/metadata outputs before assigning a role. |
|
||||
| bus 9: `0x300/0x400/0x500/0x600` eight-address blocks | Old EV6 prototype; eight subtracks per message | Four-corner prototype | Search HDA2 logs for the same topology and finish status/relative-speed decoding if found. Do not confuse its `0x500` block with the legacy front `0x500–0x51F` family on another bus/layout. |
|
||||
| `CCNC_0x162` | Fixed cluster-object slots | Extras/fused | Confirmed as a separate stock output, but its upstream sensor inputs are unknown. |
|
||||
| `0x500–0x51F` or `0x500–0x53F` | 32 or 64 × 8 B at about 20 Hz | Front radar | Shared legacy core with a Delphi ESR extension on the 64-slot dialect. K7, older Ioniq, and Ioniq PHEV samples carry 64 slots; Niro EV, Palisade, Sonata, and Sonata Hybrid samples carry 32. Implemented as standardized `RADAR_500_53F` with an optional upper half, per-source cycle end, and runtime 32-to-64 upgrade. Corrected 0.1-degree geometry is shared; 64-slot-only ESR fields use explicit aliases for 0.05 m/s² acceleration, lateral rate, width, oncoming, grouping, bridge-object, and range mode, while 32-slot acceleration retains 0.02 m/s². |
|
||||
| `0x4E0–0x4E5` | 8 B at the `500` radar cadence | Front-radar companion/status | Strong source association across every sampled `500` route. Shared `4E0` is decoded as ESR vehicle speed, yaw, curvature, 16-bit scan index, timestamp, counter, and communication health. The 64-slot radar's `4E1` matches ESR Status2; 32-slot `4E1` retains only the shared counter plus unresolved static metadata. `4E3` path IDs are shared and route-validated. The 32-slot-only `4E2`, `4E4`, and `4E5` carry BCD build metadata, protocol-derived raw ADC channels, and factory/vertical alignment status. |
|
||||
| `0x602–0x617` | 8 B, two packed tracks in the implemented messages | Front radar | Implemented legacy front layout. The relationship between `602–611` and the alternate `612–617` definitions remains incomplete. |
|
||||
| `0x210–0x21F` | 16 × 32 B at 20 Hz, two tracks per message | Front radar | Confirmed 32-track front layout. Detailed and compact dialects share the same core. |
|
||||
| `0x3A5–0x3C4` | 32 × 24 B at 20 Hz, one track per message | Front radar | Confirmed front layout. EV9 and Ioniq 9 populate its optional object-size/orientation extension. |
|
||||
| `0x235–0x248` | 20 × 32 B at about 33 Hz | **Forward-camera objects** | Corrected classification. Its first 124 bits exactly match the downloaded `FR_CMR_Obj` schema and carry camera quality, class, motion, ID, geometry, velocity, and acceleration. It is not corner radar. |
|
||||
| `0x240–0x24F` + `0x270–0x277` | status + 15 × 24 B objects + 8 × 32 B scan blocks | Candidate right-front corner channel A | Strong two-sensor topology on older HDA2 Ioniq 5, Palisade 2023, and EV6 routes. A=right is route/video-inferred; tracked-object position and velocity are decoded. Compact scan semantics remain unresolved. |
|
||||
| `0x278–0x28F` | status + 15 × 24 B objects + 8 × 32 B scan blocks | Candidate left-front corner channel B | Symmetric partner to channel A. B=left is route/video-inferred. The compact low field is only a distance candidate; property/auxiliary bits are unresolved. |
|
||||
| `0x3D0–0x3D4` | 5 × 32 B, seven packed records per message | Front-radar auxiliary scan list | Optional companion to `3A5–3C4` on sampled EV9/Ioniq 9 routes. It has 32 usable record positions and shares the radar cycle counter, but synchronized testing falsified the former radial-speed and azimuth interpretations. The low 12 bits remain only a distance candidate. |
|
||||
| `0x360–0x366` | 7 × 32 B | **Forward-camera lane/path** | Camera lane/path geometry, not radar. `362` contains lane confidence; `360/361/363/364` are active geometry, while `365/366` are often default-filled optional slots. |
|
||||
| legacy `0x238–0x24F` or `0x238–0x255` | 24 or 30 × 8 B at about 32 Hz | **Candidate forward-camera perception** | All 13 checked legacy routes carry one of these exact variants. Several carry a simultaneous `500` front radar at a different cadence, arguing strongly against this being a second radar. The 30-message variant also carries synchronized `25A–25E`; exact fields remain unparsed. Do not confuse these 8-byte messages with the CAN-FD `235–248` camera layout or `240/270` corner layout. |
|
||||
| CAN-FD `0x180–0x184`, `0x1B6–0x1B9`, `0x2BB–0x2BE` | 32 B at about 30 Hz | **Forward-camera auxiliary lists** | Downloaded Hyundai DBCs identify the transmitters/messages as camera traffic, and route payloads have camera-list cadence and slot behavior. Not a radar family. |
|
||||
| `CCNC_0x162` | fixed lead/left/right/rear slots | Fused/cluster output | Stock ADAS/cluster output with unknown upstream fusion. Do not publish it as independent raw radar. |
|
||||
| bus 9 `0x300/0x400/0x500/0x600` eight-address blocks | old prototype, eight subtracks per message | Four-corner prototype | Prior EV6 reverse-engineering mapped four blocks to FL/FR/RL/RR, but status and relative speed were unfinished and the family has not been reconfirmed in the current corpus. |
|
||||
|
||||
## Platform inventory
|
||||
The confirmed `3A5–3C4` layout was checked across 21 compatible forum routes
|
||||
and 817,852 checksum-valid frames. All 726 advertised segments from the 20
|
||||
unique `210–21F` routes were scanned: 16 routes had active targets, three had
|
||||
a complete but empty stream, and one non-HDA2 Telluride route had no 210
|
||||
range. The 16 active routes split evenly between detailed and compact
|
||||
dialects, and all 13,210,543 checked 210 frames passed the HKG checksum.
|
||||
|
||||
This is the investigation queue transcribed from the supplied route post and
|
||||
augmented only where current branch evidence is explicit. "HDA2 candidate"
|
||||
means to check both the `0x235–0x248` clue and the older four-block EV6
|
||||
topology; it does not claim that either is present.
|
||||
The forum candidates are now classified at the family level. The legacy
|
||||
`238–24F` and `238–255` variants are strong forward-camera candidates but
|
||||
remain unparsed. The `180/1B6/2BB` groups are CAN-FD camera auxiliary lists.
|
||||
The incomplete Ioniq PHEV `500–?` observation is a complete `500–53F`
|
||||
64-slot front radar. None must be conflated with the CAN-FD `235` camera or
|
||||
`240/270` corner layouts merely because their addresses overlap.
|
||||
|
||||
### Platforms with `0x3A5–0x3C4`
|
||||
The `4E0/4E1` 2-bit counters matched in all 2,552 paired frames checked across
|
||||
seven routes. On the three 64-slot routes, `4E3` matched the same counter in
|
||||
all 1,133 paired frames. Every one of the 2,369 distinct `4E0` 16-bit
|
||||
`SCAN_INDEX` steps incremented by one. In a fresh six-route full-segment pass,
|
||||
decoded `4E0` vehicle speed correlated with `carState.vEgo` at
|
||||
0.9976–0.99996, with 0.02–0.19 m/s mean absolute error. Both checked 64-slot
|
||||
routes held `MAXIMUM_TRACKS_ACK=64`; all four 32-slot routes decoded that ESR
|
||||
field as 1, confirming a different `4E1` dialect. The four sampled 32-slot
|
||||
variants carried plausible BCD build timestamps in `4E2`; their remaining
|
||||
`4E1` metadata stayed platform-static. All 7,347 nonzero `4E3` path IDs
|
||||
referenced active one-based target slots. The 32-slot `4E4/4E5` payloads match
|
||||
Hyundai-relocated ESR Status5/6 layouts: raw ADC health channels and alignment
|
||||
status. Across 4,698 `4E5` frames, both factory-alignment fields remained
|
||||
within the documented 0–3 enum subset; unexercised flags and physical ADC or
|
||||
misalignment scales remain research-only.
|
||||
|
||||
| Platform | Front | Corner | Extras/fused | Rear |
|
||||
Two other consecutive lookalikes are currently excluded from radar work:
|
||||
downloaded Hyundai DBCs identify `630–63D` as head-unit/amplifier traffic, and
|
||||
`5ED–5EF` occurs on routes without `500` tracks and does not follow the
|
||||
otherwise strong `4E0`/`500` source association.
|
||||
|
||||
### Outstanding forum platform queue
|
||||
|
||||
| Platform | Remaining candidate |
|
||||
| --- | --- |
|
||||
| Hyundai Ioniq PHEV / Ioniq HEV 2022 / Palisade / Kia Niro PHEV 2022 | legacy camera candidate `238–24F`, plus its synchronized `201/20A/266/26D` companion groups |
|
||||
| Hyundai Custin / Elantra 2021 / Elantra HEV 2021 / Sonata / Sonata Hybrid | extended legacy camera candidate `238–255`, plus `25A–25E` and the same companion groups |
|
||||
| Kia Sorento / Sorento HEV 4th gen / Genesis GV70 | confirm semantics within the classified CAN-FD camera `180/1B6/2BB` auxiliary groups |
|
||||
| Legacy `500` front-radar platforms | identify static 32-slot `4E1` metadata and calibrate/actively exercise the decoded `4E4/4E5` health and alignment fields |
|
||||
|
||||
The forum also listed Acura MDX and Honda Clarity examples. They remain out of
|
||||
this Hyundai/Kia/Genesis inventory because the Acura range was unrelated and
|
||||
the Honda routes did not expose tracks.
|
||||
|
||||
## Confirmed and possible vehicle combinations
|
||||
|
||||
These are distinct source combinations supported by the corpus. “Possible”
|
||||
describes protocol combinations, not a promise that every trim exposes them.
|
||||
|
||||
| Combination | Front tracked objects | Corner source | Camera/auxiliary source | Known examples or status |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Hyundai Ioniq 6 | `3A5–3C4` confirmed | HDA2 candidate | Unknown | Unknown |
|
||||
| Hyundai Kona 2nd gen | `3A5–3C4` confirmed | Unknown | Check CCNC fused output | Unknown |
|
||||
| Hyundai Kona EV 2nd gen | `3A5–3C4` confirmed | HDA2 candidate | `CCNC_0x162` observed on sampled CCNC routes | Unknown |
|
||||
| Hyundai Kona HEV 2nd gen | `3A5–3C4` confirmed | Unknown | Check CCNC fused output | Unknown |
|
||||
| Hyundai Santa Fe HEV 5th gen | `3A5–3C4` confirmed | Unknown | Check CCNC fused output | Unknown |
|
||||
| Hyundai Sonata 2024 | `3A5–3C4` confirmed | Unknown | Check CCNC fused output | Unknown |
|
||||
| Kia Carnival HEV 4th gen (2026) | `3A5–3C4` confirmed | Unknown | Unknown | Unknown |
|
||||
| Kia EV6 2025 | `3A5–3C4` confirmed | HDA2 candidate | Check CCNC fused output | Unknown |
|
||||
| Kia EV9 | `3A5–3C4` confirmed; rich extension | HDA2/corner candidate | Check `CCNC_0x162` and upstream inputs | Unknown |
|
||||
| Kia K4 2025 | `3A5–3C4` confirmed | Check HDA2 routes | Check CCNC fused output | Unknown |
|
||||
| Kia K5 2025 | `3A5–3C4` confirmed | Unknown | Check CCNC fused output | Unknown |
|
||||
| Kia Niro EV 2nd gen | `3A5–3C4` confirmed | Check HDA2 routes | Unknown | Unknown |
|
||||
| Kia Niro HEV 2nd gen | `3A5–3C4` confirmed | Unknown | Unknown | Unknown |
|
||||
| Kia Sportage HEV 2026 | **Incompatible overlapping layout** | Unknown | Unknown | Unknown |
|
||||
| Hyundai Ioniq 9 | `3A5–3C4` confirmed; rich extension | HDA2 candidate | Check `CCNC_0x162` and upstream inputs | Unknown |
|
||||
| Legacy front only A | `500–51F` or `500–53F` | none observed | `4E0–4E5` radar companion/status | 32- and 64-slot platform variants |
|
||||
| Legacy front only B | `602–617` | none observed | none established | Kona EV 2022, Ceed PHEV candidates |
|
||||
| CAN-FD front only A | `210–21F` | none observed | none established | Elantra HEV 2024 and compact-dialect Tucson/Santa Cruz/Sportage routes |
|
||||
| CAN-FD front + paired front corners | `210–21F` | inferred right A `240–24F` + `270–277`; inferred left B `278–28F` | none required | Older HDA2 Ioniq 5, Palisade 2023, and EV6 observations |
|
||||
| CAN-FD front only B | `3A5–3C4` | none observed | camera objects/lanes may be absent or not logged | Compatible HDA1 routes |
|
||||
| CAN-FD front + camera perception | `3A5–3C4` | none established | camera objects `235–248` and lanes `360–366` | Common newer HDA2 pattern; camera traffic is not another radar |
|
||||
| Rich front + camera perception + auxiliary radar scan | rich `3A5–3C4` | none established | `235–248`, `360–366`, plus radar auxiliary records `3D0–3D4` | EV9 and Ioniq 9 sampled routes |
|
||||
| Front + four-corner prototype | layout depends on the old EV6 capture | bus-9 FL/FR/RL/RR blocks | unknown | Historical prototype only; not current-production support |
|
||||
|
||||
### Platforms with `0x210–0x21F`
|
||||
No current evidence proves a combination containing both the paired
|
||||
`240/270` corner family and the newer `3D0` auxiliary scan family. No distinct
|
||||
rear-radar tracked-object range has been confirmed in the current routes.
|
||||
Rear objects shown by `CCNC_0x162` may be fused from other sensors and do not
|
||||
prove a rear radar source.
|
||||
|
||||
| Platform | Front | Corner | Extras/fused | Rear |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Hyundai Elantra HEV 2024 | `210–21F` detailed confirmed | Unknown | Unknown | Unknown |
|
||||
| Hyundai Ioniq 5 | `210–21F` detailed confirmed on two active routes; one route empty | HDA2 candidate | Unknown | Unknown |
|
||||
| Hyundai Palisade 2023 | `210–21F` detailed confirmed on one active route; one route empty | HDA2 candidate | Unknown | Unknown |
|
||||
| Kia EV6 | `210–21F` detailed confirmed on four active routes; one route empty | Four-block prototype candidate | Unknown | Prototype family may include rear corners |
|
||||
| Kia K8 HEV 1st gen | Historical supplied route empty; outside the 20-route validation set | Unknown | Unknown | Unknown |
|
||||
| Hyundai Santa Cruz 2025 | `210–21F` compact confirmed | Unknown | Unknown | Unknown |
|
||||
| Hyundai Tucson 4th gen | `210–21F` compact confirmed on four routes | Unknown | Unknown | Unknown |
|
||||
| Hyundai Tucson HEV 2025 | `210–21F` compact confirmed | Unknown | Unknown | Unknown |
|
||||
| Kia Sportage 5th gen | `210–21F` compact confirmed on two routes | Unknown | Unknown | Unknown |
|
||||
## Platform observations
|
||||
|
||||
### Platforms with legacy implemented layouts
|
||||
| Platform group | Front | Additional observed traffic |
|
||||
| --- | --- | --- |
|
||||
| Ioniq 5 / Palisade 2023 / EV6 older HDA2 samples | detailed `210–21F` | paired `240/270–28F` front corners; A=right and B=left strongly inferred |
|
||||
| Elantra HEV 2024 | detailed `210–21F` | no corner family established |
|
||||
| Santa Cruz 2025 / Tucson family / Sportage 5th gen | compact `210–21F` | no corner family established in the checked samples |
|
||||
| Ioniq 6 / Kona 2 / Kona EV 2 / Santa Fe 5 / Sonata 2024 / Carnival HEV / EV6 2025 / K4 / K5 / Niro 2 | common `3A5–3C4` where compatible | platform-dependent camera `235` and `360` traffic; no decoded corner family established |
|
||||
| EV9 / Ioniq 9 | rich `3A5–3C4` | camera objects `235`, camera lanes `360`, optional front-radar auxiliary scan `3D0` |
|
||||
| Sportage HEV 2026 forum sample | overlapping but incompatible `3A5` bytes | excluded from the common-layout decoder |
|
||||
|
||||
| Platform | Front | Corner | Extras/fused | Rear |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Hyundai Ioniq | `500–51F` implemented; verify | Unknown | Unknown | Unknown |
|
||||
| Kia K7 2017 | `500–51F` implemented; verify | Unknown | Unknown | Unknown |
|
||||
| Kia Niro EV | `500–51F` implemented; verify | Unknown | Unknown | Unknown |
|
||||
| Hyundai Kona EV 2022 | `602–617` partially implemented; verify | Unknown | Unknown | Unknown |
|
||||
| Kia Ceed PHEV | `602–617` partially implemented; verify | Unknown | Unknown | Unknown |
|
||||
HDA2 changes where traffic is exposed, but raw bus number is not a reliable
|
||||
layout discriminator. Follow sustained logical A-CAN and reject short
|
||||
forwarded copies.
|
||||
|
||||
### Unimplemented front candidates
|
||||
## What remains to prove
|
||||
|
||||
| Platform | Candidate front source | Corner | Extras/fused | Rear |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Hyundai Ioniq PHEV | `238–24F` | Unknown | Unknown | Unknown |
|
||||
| Kia Niro PHEV 2022 | `238–24F` | Unknown | Unknown | Unknown |
|
||||
| Hyundai Palisade | `238–24F` | Unknown | Unknown | Unknown |
|
||||
| Hyundai Ioniq HEV 2022 | `238–24F` | Unknown | Unknown | Unknown |
|
||||
| Hyundai Custin 1st gen | `238–255` | Unknown | Unknown | Unknown |
|
||||
| Hyundai Elantra 2021 | `238–255` | Unknown | Unknown | Unknown |
|
||||
| Hyundai Sonata | `238–255` | Unknown | Unknown | Unknown |
|
||||
| Hyundai Sonata Hybrid | `238–255` | Unknown | Unknown | Unknown |
|
||||
| Hyundai Elantra HEV 2021 | `238–255` | Unknown | Unknown | Unknown |
|
||||
| Kia Sorento 4th gen | `180/1B6/2BB` groups; role unproved | Unknown | Unknown | Unknown |
|
||||
| Kia Sorento HEV 4th gen | `180/1B6/2BB` groups; role unproved | Unknown | Unknown | Unknown |
|
||||
| Genesis GV70 1st gen | `180/1B6/2BB` groups; role unproved | Unknown | Unknown | Unknown |
|
||||
| Hyundai Ioniq PHEV 2019 | Incomplete `500–?` observation | Unknown | Unknown | Unknown |
|
||||
1. Confirm the route/video-derived A=right and B=left assignment with a
|
||||
controlled sensor occlusion or authoritative service mapping.
|
||||
2. Finish both corner status messages and the remaining 24-byte object
|
||||
fields: the stable 2-bit unknown category, persistent ID, classification,
|
||||
dimensions, and sensor-health flags. The former acceleration candidate was
|
||||
falsified and returned to raw bits. Position and longitudinal/lateral
|
||||
velocity are resolved, and `0x278` byte 3 is the confirmed pre-truncation
|
||||
channel-A object count: exported A slots equal `min(count, 15)`.
|
||||
Preserve all remaining unknown bits until they correlate with the compact
|
||||
scan bins.
|
||||
3. Decode the 56 fixed-position `270–277` / `288–28F` scan candidates,
|
||||
including the low 13-bit distance candidate, seven-bit property, and
|
||||
nine-bit auxiliary fields. Direct same-cycle range association and a linear
|
||||
record-index-to-angle map both failed time-shift controls, so do not project
|
||||
these records as geometry until an independent mapping is found.
|
||||
4. Decode the `3D0` auxiliary scan array without restoring the falsified
|
||||
radial-speed or azimuth names. Determine whether the low 12 bits are
|
||||
distance, identify the role and physical angular ordering of the 32 record
|
||||
positions, and decode the four flag bits plus both unknown bytes. Two rich
|
||||
routes confirm exact counter synchronization and the sentinel/zero empty
|
||||
forms, but reject range-only, candidate-geometry, and direct slot-to-slot
|
||||
associations with `3A5`. Production RadarData must continue to exclude it.
|
||||
5. Decode the active polynomial/path fields in `360/361/363/364`. Treat
|
||||
`365/366` constant payloads as unavailable lane slots, not missing radar.
|
||||
6. Resolve `235–248` `UNKNOWN_1/2/3`, identify every checksum variant, and
|
||||
validate native camera classification/motion values against video. Confirm
|
||||
the camera-source assignment and forwarding path on each platform carrying
|
||||
the range.
|
||||
7. Search explicitly for the old four-corner topology and for a distinct rear
|
||||
tracked-object source. Record verified absence when the correct buses and
|
||||
maneuvers were covered.
|
||||
8. Resolve the static 32-slot `4E1` metadata, physically calibrate the
|
||||
protocol-derived `4E4` ADC channels, and capture alignment events to verify
|
||||
the inactive `4E5` flags and misalignment scales. Exercise the currently
|
||||
zero FCW path IDs and low `4E3` status flags on a 32-slot platform. Shared
|
||||
`4E0` and path IDs are resolved; do not transfer unsupported 64-slot
|
||||
Status2 fields onto the 32-slot payload.
|
||||
9. Decode the legacy camera candidates `238–24F` and `238–255`, including
|
||||
their `201/20A/25A/266/26D` companion groups. Determine whether `250–255`
|
||||
and `25A–25E` are extra objects, path/lane data, or metadata, and correlate
|
||||
classification and geometry against video. Keep these out of production
|
||||
RadarData unless source evidence overturns the current camera assignment.
|
||||
10. Complete the per-platform combination matrix across HDA1/HDA2 and
|
||||
CCNC/non-CCNC cars. Record the sustained logical CAN source, forwarded
|
||||
echoes, empty-but-present ranges, and verified absences rather than relying
|
||||
on raw bus numbers.
|
||||
11. Add representative replay fixtures and debugger/on-road UI tests for each
|
||||
confirmed source combination. Promote candidate names or fields only after
|
||||
multi-route and controlled-scene validation.
|
||||
|
||||
The post also included Acura MDX and Honda Clarity examples. They are excluded
|
||||
from this Hyundai/Kia/Genesis roadmap: the Acura range was unrelated, and the
|
||||
Honda routes showed no tracks.
|
||||
## Useful work that does not require video
|
||||
|
||||
## Definition of done for one platform
|
||||
1. Cluster every unknown field by platform, object lifecycle, motion class,
|
||||
address/slot, and empty/default state. Stable cross-platform enums can be
|
||||
separated from platform configuration bytes.
|
||||
2. Use consecutive-cycle derivatives and ego-motion compensation to test
|
||||
velocity, acceleration, yaw, and sensor-frame hypotheses. Always compare
|
||||
against opposite-channel and time-shifted controls.
|
||||
3. Search counter phase and bounded latency, not merely nearest timestamps,
|
||||
when testing associations between tracked objects, compact scan arrays,
|
||||
camera objects, and status messages.
|
||||
4. Build transition matrices for the corner unknown category and remaining
|
||||
flags. The first pass proved the 2-bit category is
|
||||
stable within a continuous object but is not lifecycle or validity: all four
|
||||
values occur on active targets, while empty slots use `LONG_DIST=204.7 m`.
|
||||
Continue searching the remaining fields for birth, coast, replacement, and
|
||||
deletion behavior without assuming the visual object class.
|
||||
5. Compare the same physical field across detailed `210`, compact `210`,
|
||||
`3A5`, corner objects, and legacy `500` tracks after normalizing coordinates
|
||||
and lifecycle. Shared scaling should survive route and platform changes.
|
||||
6. Mine downloaded and public DBC/source comments for transmitter identity and
|
||||
bit boundaries, but require route evidence before adopting physical names or
|
||||
scales.
|
||||
7. Measure source dropouts, checksum/counter discontinuities, and status-bit
|
||||
transitions together. This can identify communication health, blockage,
|
||||
alignment, and reset fields without video.
|
||||
8. Generate debugger capture instructions for later physical confirmation:
|
||||
cover one corner sensor at a time, use a single stationary target at a
|
||||
measured distance, and record straight approach/recede passes at known
|
||||
speed. These tests can settle side assignment and physical scan fields with
|
||||
much less ambiguity than unconstrained road scenes.
|
||||
|
||||
A platform row is complete only after the following are recorded:
|
||||
## Implementation policy
|
||||
|
||||
- exact fingerprint, trim/architecture flags, route segments, and test scenes;
|
||||
- source bus, address range, payload size, cadence, and counter behavior;
|
||||
- whether duplicate copies on other buses are forwarding or independent
|
||||
sensors;
|
||||
- physical coverage: front, front corners, rear corners, or rear;
|
||||
- track lifecycle and validity states;
|
||||
- longitudinal/lateral position, relative velocities, acceleration, and
|
||||
motion classification when available;
|
||||
- behavior for moving, stationary, crossing, oncoming, overtaking, cut-in,
|
||||
and curved-road targets;
|
||||
- correlation with road/wide camera and, for fused outputs, with every known
|
||||
raw source;
|
||||
- explicit control-versus-visualization policy;
|
||||
- DBC comments, parser tests, representative replay tests, and known
|
||||
unavailable/default signal values.
|
||||
|
||||
## Safety and implementation rules
|
||||
|
||||
- A consecutive address range is not sufficient identification. Match the
|
||||
complete range, bus, payload length, cadence, and decoded invariants.
|
||||
- Do not merge two buses into two radars until byte/timestamp comparison rules
|
||||
out forwarding.
|
||||
- Do not use an unknown signal for filtering or control.
|
||||
- Do not send fused cluster objects to driving as if they were independent raw
|
||||
tracks.
|
||||
- Keep all discovered sources available to `tools/radar/ui.py` for research,
|
||||
even when the production radar interface deliberately filters what is sent
|
||||
to openpilot.
|
||||
- Record negative results. A verified absence is more useful than leaving a
|
||||
platform perpetually marked unknown.
|
||||
- Production RadarData contains the confirmed tracked-object layouts only.
|
||||
- `235–248` remains available through RadarData as a camera-object source for
|
||||
research, with explicit source labeling and its native object-ID validity.
|
||||
- The corner and `3D0` generators are research DBCs. Their unresolved fields
|
||||
are named unknown or candidate in comments and are not used for control.
|
||||
- `4E0–4E5` status fields are present in the `RADAR_500_53F` DBC for research,
|
||||
and inventoried by the desktop radar debugger, but are not required for
|
||||
source detection or production track parsing.
|
||||
- The on-road UI labels `235–248` as `CAM` and uses triangle markers.
|
||||
- The desktop radar debugger plots confirmed production tracks, the
|
||||
debugger-only `612–617` alternate objects, and the decoded `240/278` corner
|
||||
objects. Its `SIGNALS` table exposes the compact `270/288` records, `3D0`
|
||||
records, `4E0–4E5` status, `360–366` raw camera payloads, and all six
|
||||
`CCNC_0x162` fused slots. Compact scans and `3D0` remain table-only and are
|
||||
never projected as geometry. `0x162` is explicitly labeled fused and is not
|
||||
published as an independent radar source.
|
||||
|
||||
+266
-2
@@ -1,10 +1,14 @@
|
||||
# Hyundai 3A5-3C4 and 210-21F radar signal notes
|
||||
# Hyundai radar, camera-object, and auxiliary signal notes
|
||||
|
||||
This document tracks the evidence behind the 24-byte `RADAR_3A5_3C4` layout
|
||||
and its shared signal semantics with the 32-byte `RADAR_210_21F` layout.
|
||||
It deliberately separates a signal being active from its meaning being known.
|
||||
It also records the now-identified `0x235–0x248` camera objects and the
|
||||
research-only corner and raw-detection families. It deliberately separates a
|
||||
signal being active from its meaning being known.
|
||||
The cross-family and per-platform investigation plan lives in
|
||||
[RADAR_RESEARCH.md](RADAR_RESEARCH.md).
|
||||
The forum-ready platform/range summary lives in
|
||||
[PLATFORM_MATRIX.md](PLATFORM_MATRIX.md).
|
||||
|
||||
## Status terms
|
||||
|
||||
@@ -56,6 +60,109 @@ An optional field that is dead on one platform is not globally dead.
|
||||
ID and `0/1/2/3 = unknown/stationary/moving/stopped`; the HMVS4 object DBC
|
||||
independently defines value 3 as stopped, a 7-bit quality level, and alive
|
||||
age/lifetime. Neither DBC directly contains the 210 or 3A5 radar layout.
|
||||
- The same archive's `FR_CMR_Obj` definition matches the first 124 bits of
|
||||
`0x235–0x248` exactly. Together with its 33 Hz cadence and co-location with
|
||||
`0x360–0x366`, this corrects the former corner-radar classification:
|
||||
`0x235–0x248` is a forward-camera object list.
|
||||
- The repository's Delphi `ESR.dbc` exactly matches the `0x500–0x53F` target
|
||||
packing and the 64-slot radar's `0x4E0/0x4E1/0x4E3` status packing. A fresh
|
||||
six-route full-segment check covered two 64-slot and four 32-slot variants.
|
||||
`0x4E0` radar speed correlated with `carState.vEgo` at 0.9976–0.99996 with
|
||||
0.02–0.19 m/s mean absolute error. Both 64-slot variants acknowledged 64
|
||||
tracks in `0x4E1`; the four 32-slot payloads decoded as 1, proving that
|
||||
their `0x4E1` uses a different dialect.
|
||||
|
||||
## 500-53F legacy front-radar layout
|
||||
|
||||
Every eight-byte message is one target slot. The lower 32 slots
|
||||
`0x500–0x51F` are required; some radar variants add an identically packed
|
||||
upper bank at `0x520–0x53F`. Inter-arrival timing on all six freshly checked
|
||||
full segments is about 50 ms, confirming the established 20 Hz cadence. A gap
|
||||
inside these logs makes frame count divided by total segment span misleading.
|
||||
|
||||
| Signal | Start bit | Size | Scale / offset | Status |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `UNKNOWN_1` | 7 | 8 signed | raw | Preserve on 32-slot variants. Its low bits are active but do not behave like ESR target flags. |
|
||||
| `ONCOMING_ESR` | 0 | 1 | boolean | 64-slot only. Every flagged target in both full-segment checks had negative ground-frame speed. Clear does not distinguish stationary from same-direction moving. |
|
||||
| `GROUPING_CHANGED_ESR` | 1 | 1 | boolean | 64-slot ESR identity; exact tracker behavior remains to be correlated. |
|
||||
| `REL_LAT_SPEED_ESR` | 7 | 6 signed | 0.25 / 0 m/s | 64-slot ESR lateral target rate. It is not published for 32-slot variants. |
|
||||
| `AZIMUTH` | 12 | 10 signed | 0.1 / 0 deg | Corrected from the former 0.2-degree scale. Lateral projection now uses the angle directly rather than a compensating half-scale. |
|
||||
| `STATE` | 15 | 3 | enum | Track lifecycle; values 3 and 4 are the measured/coasted valid states used by the parser. |
|
||||
| `LONG_DIST` | 18 | 11 | 0.1 / 0 m | Target range. |
|
||||
| `REL_ACCEL` | 33 | 10 signed | 0.02 / 0 m/s² | 32-slot dialect scale retained from route behavior. |
|
||||
| `REL_ACCEL_ESR` | 33 | 10 signed | 0.05 / 0 m/s² | 64-slot ESR scale. Separate overlapping names prevent applying it to the 32-slot dialect. |
|
||||
| `WIDTH_ESR` | 37 | 4 | 0.5 / 0 m | 64-slot ESR target width. It was populated on both checked 64-slot routes and identically zero on all four 32-slot routes. |
|
||||
| `COUNTER` | 38 | 1 | 1 / 0 | Per-target rolling count. |
|
||||
| `BRIDGE_OBJECT_ESR` | 39 | 1 | boolean | 64-slot ESR identity; exact tracker behavior remains to be correlated. |
|
||||
| `REL_SPEED` | 53 | 14 signed | 0.01 / 0 m/s | Radial range rate. |
|
||||
| `MED_RANGE_MODE_ESR` | 55 | 2 | enum | 64-slot ESR medium-range operating-mode field. |
|
||||
|
||||
### 4E0-4E5 companion messages
|
||||
|
||||
`0x4E0` has the shared ESR status layout on both 32- and 64-slot variants:
|
||||
|
||||
| Signal | Start bit | Size | Scale / offset |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `DSP_TIMESTAMP` | 5 | 7 | 2 / 0 ms |
|
||||
| `GROUP_COUNTER` | 6 | 2 | 1 / 0 |
|
||||
| `COMM_ERROR` | 14 | 1 | boolean |
|
||||
| `RADIUS_CURVATURE` | 13 | 14 signed | 1 / 0 m |
|
||||
| `SCAN_INDEX` | 31 | 16 | 1 / 0 |
|
||||
| `YAW_RATE` | 47 | 12 signed | 0.0625 / 0 deg/s |
|
||||
| `VEHICLE_SPEED` | 50 | 11 | 0.0625 / 0 m/s |
|
||||
|
||||
The 64-slot variant exactly matches ESR Status2 at `0x4E1`. On 32-slot
|
||||
variants only its low two-bit rolling counter is active; the other 62 bits
|
||||
are platform-static and do not acknowledge 32 tracks, so the ESR Status2
|
||||
names remain 64-slot-only. The `0x4E3` in-path path-ID bytes are shared across
|
||||
both variants, while its low status flags, counter, range mode, and alignment
|
||||
angle are confirmed only on the 64-slot variant.
|
||||
|
||||
| `0x4E1` 64-slot signal | Start bit | Size | Scale / offset |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `GROUP_COUNTER` | 1 | 2 | 1 / 0 |
|
||||
| `MAXIMUM_TRACKS_ACK` | 7 | 6 | 1 / 1 |
|
||||
| `STEERING_ANGLE_ACK` | 10 | 11 | 1 / 0 deg |
|
||||
| `RAW_DATA_MODE`, `TRANSCEIVER_OPERATIONAL`, `INTERNAL_ERROR`, `RANGE_PERFORMANCE_ERROR`, `OVERHEAT_ERROR` | 11–15 | 1 each | boolean |
|
||||
| `TEMPERATURE` | 31 | 8 signed | 1 / 0 °C |
|
||||
| `GROUPING_MODE` | 33 | 2 | 1 / 0 |
|
||||
| `VEHICLE_SPEED_COMP_FACTOR` | 39 | 6 signed | 0.00195 / 1 |
|
||||
| `YAW_RATE_BIAS` | 47 | 8 signed | 0.125 / 0 deg/s |
|
||||
| `DSP_SOFTWARE_VERSION` | 55 | 16 | 1 / 0 |
|
||||
|
||||
| `0x4E3` signal | Start bit | Size | Scale / offset |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `GROUP_COUNTER` | 1 | 2 | 1 / 0; 64-slot confirmed |
|
||||
| `MEDIUM_LONG_RANGE_MODE` | 3 | 2 | 1 / 0; 64-slot confirmed |
|
||||
| `PARTIAL_BLOCKAGE`, `SIDELOBE_BLOCKAGE`, `LONG_RANGE_GRATING_LOBE_DETECTED`, `TRUCK_TARGET_DETECTED` | 4–7 | 1 each; 64-slot confirmed |
|
||||
| `ACC_MOVING_PATH_ID` | 15 | 8 | one-based target slot; shared |
|
||||
| `CMBB_MOVING_PATH_ID`, `CMBB_STATIONARY_PATH_ID` | 23, 31 | 8 each | one-based target slots; shared |
|
||||
| `FCW_MOVING_PATH_ID`, `FCW_STATIONARY_PATH_ID` | 39, 47 | 8 each | protocol-defined; unexercised on sampled 32-slot routes |
|
||||
| `AUTO_ALIGN_ANGLE` | 55 | 8 signed | 0.0625 / 0 deg; 64-slot confirmed |
|
||||
| `ACC_STATIONARY_PATH_ID` | 63 | 8 | one-based target slot; shared |
|
||||
|
||||
All 7,347 nonzero `0x4E3` path-ID observations across the four 32-slot routes
|
||||
referenced an active `0x500–0x51F` target slot. IDs reached 32, proving
|
||||
one-based indexing. ACC and CMBB moving selectors commonly referenced the
|
||||
same slot, as did their stationary selectors.
|
||||
|
||||
The 32-slot variants additionally send `0x4E2`, `0x4E4`, and `0x4E5`:
|
||||
|
||||
- `0x4E2` consistently encodes plausible BCD build timestamps across all four
|
||||
sampled platforms.
|
||||
- `0x4E4` matches the byte order and address-shifted envelope of Delphi ESR
|
||||
Status5: switched-battery, ignition, two temperature, and four supply ADC
|
||||
channels. The identities are protocol-derived and the raw ADC values are
|
||||
intentionally uncalibrated; they must not drive a health decision.
|
||||
- `0x4E5` matches the similarly relocated ESR Status6 alignment payload. In
|
||||
4,698 checked frames its two factory-alignment fields used only documented
|
||||
enum values 0–3. The other flags and alignment-value bytes remained zero,
|
||||
so their identities are protocol-derived but not independently exercised.
|
||||
|
||||
Although generic ESR defines eight-byte input messages at `0x4F0/0x4F1`, no
|
||||
such messages were present on these source buses. The observed Hyundai
|
||||
`0x4F1` was four bytes on other/forwarded buses and is not assigned the ESR
|
||||
input schema.
|
||||
|
||||
## Signal matrix
|
||||
|
||||
@@ -165,6 +272,163 @@ kinematics with persistence, hysteresis, and lane plausibility.
|
||||
but they cannot be reconstructed merely by reading the 3A5 rich extension.
|
||||
Rear tofus in particular need a source beyond the forward 3A5 radar.
|
||||
|
||||
## 235-248 forward-camera object layout
|
||||
|
||||
Each 32-byte message is one camera-object slot. `OBJECT_ID=0` is unused in the
|
||||
observed routes. The production parser normalizes the native camera motion
|
||||
enum to the shared `unknown/stationary/moving` display enum, but preserves the
|
||||
raw DBC value.
|
||||
|
||||
| Signal | Start bit | Size | Scale / offset | Status |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `CHECKSUM` | 0 | 16 | 1 / 0 | Checksum field; platform variants exist, so the generator does not force the standard HKG algorithm. |
|
||||
| `COUNTER` | 16 | 8 | 1 / 0 | Transmit-cycle counter. |
|
||||
| `QUALITY` | 24 | 7 | 1 / 0 | Camera-object quality/reliability level. |
|
||||
| `AGE` | 32 | 8 | 1 / 0 | Object alive age. |
|
||||
| `MOTION_STATE` | 40 | 4 | 1 / 0 | Native camera motion class. |
|
||||
| `OBJECT_ID` | 44 | 7 | 1 / 0 | Persistent object ID; zero is unused. |
|
||||
| `WIDTH` | 52 | 7 | 0.05 / 0 m | Estimated width. |
|
||||
| `CLASSIFICATION` | 60 | 3 | 1 / 0 | Unknown, truck, car, motorcycle, bicycle, pedestrian, undecided. |
|
||||
| `LONG_DIST` | 64 | 13 | 0.05 / 0 m | Longitudinal relative position. |
|
||||
| `LAT_DIST` | 78 | 12 | 0.05 / -102.4 m | Lateral relative position. |
|
||||
| `REL_SPEED` | 91 | 12 | 0.05 / -100 m/s | Longitudinal relative velocity. |
|
||||
| `REL_LAT_SPEED` | 104 | 10 | 0.05 / -25 m/s | Lateral relative velocity. |
|
||||
| `REL_ACCEL` | 115 | 9 signed | 0.05 / 0 m/s² | Longitudinal relative acceleration. |
|
||||
| `UNKNOWN_1` | 125 | 12 | raw | Active extension; semantics unknown. |
|
||||
| `UNKNOWN_2` | 138 | 12 | raw | Active extension; semantics unknown. |
|
||||
| `UNKNOWN_3` | 151 | 10 | raw | Active extension; semantics unknown. |
|
||||
| `AZIMUTH` | 176 | 14 | 360/16384 / -180 deg | Route-validated high-resolution object azimuth. |
|
||||
|
||||
Native motion values are: 0 undefined, 1 standing, 2 parked, 3 stopped,
|
||||
4 unknown movable, 5 moving, 6 stopped oncoming, 7 unknown oncoming,
|
||||
8 moving oncoming, and 9 crossing bicycle. This is the only newly decoded
|
||||
family with explicit oncoming moving and stopped-oncoming values.
|
||||
|
||||
## 240/270-28F candidate corner-radar layout
|
||||
|
||||
The address sizes and symmetry form two complete sensor channels:
|
||||
|
||||
| Channel | Inferred physical side | Status | Object slots | Compact scan records |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| A | right front | `240`, 16 B | `241–24F`, 15 × 24 B | `270–277`, 8 × 32 B, seven records each |
|
||||
| B | left front | `278`, 8 B | `279–287`, 15 × 24 B | `288–28F`, 8 × 32 B, seven records each |
|
||||
|
||||
Both channels run at about 20 Hz and validate with the HKG CAN-FD checksum in
|
||||
the sampled Ioniq 5, Palisade 2023, and EV6 data. All 288,021 checked frames
|
||||
from five complete segment samples passed. A synchronized Palisade
|
||||
parking-garage sweep strongly assigns A to the right-front sensor and B to
|
||||
the left-front sensor: A alone retained close objects at large negative
|
||||
lateral position, while B alone retained the corresponding positive-side
|
||||
objects. This is strong route/video evidence, but a controlled sensor
|
||||
occlusion remains the final physical confirmation.
|
||||
|
||||
The 24-byte tracked-object record is:
|
||||
|
||||
| Signal | Bit | Size | Scale / offset | Status |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `UNKNOWN_CATEGORY` | 24 | 2 | raw | Stable within continuous same-slot tracks and changes mainly when a spatially different target replaces the slot. All values 0–3 occur on active objects, so this is not lifecycle or validity. |
|
||||
| `RCS` | 56 | 7 signed | 1 / 0 | Strong shared-core identification; raw unit uncalibrated. |
|
||||
| `LONG_DIST` | 63 | 13 | 0.05 / 0 m | Confirmed vehicle-frame longitudinal position. |
|
||||
| `LAT_DIST` | 76 | 11 signed | 0.05 / 0 m | Confirmed vehicle-frame lateral position. |
|
||||
| `UNKNOWN_BIT_87` | 87 | 1 | raw | Active unknown. |
|
||||
| `REL_SPEED` | 88 | 10 signed | 0.2 / 0 m/s | Confirmed longitudinal relative velocity. Four held-out Palisade samples exercised bit 97 independently of bit 96 at 351–357 m; consecutive distance changes matched approximately -52 m/s rather than the false positive speed produced by the former 9-bit decode. |
|
||||
| `REL_LAT_SPEED` | 98 | 10 signed | 0.05 / 0 m/s | Confirmed lateral relative velocity. |
|
||||
| `UNKNOWN_BITS_108_117` | 108 | 10 | raw | Former centered acceleration candidate. Its correlations with consecutive `REL_SPEED` derivatives were -0.445, -0.043, -0.009, -0.111, and -0.124 across five Ioniq 5, Palisade, and EV6 samples, so the physical name and scale were falsified. |
|
||||
|
||||
Empty object slots use the exact `LONG_DIST=204.7 m` (`0xFFE`) sentinel;
|
||||
`UNKNOWN_CATEGORY=0` must not be used as an empty test. This correction was
|
||||
validated on five Ioniq 5, Palisade 2023, and EV6 samples. Including
|
||||
category-zero objects produced longitudinal position-derivative correlations
|
||||
of 0.949, 0.603, 0.962, 0.948, and 0.967 across those samples.
|
||||
|
||||
The position decode was independently checked against simultaneous
|
||||
`210–21F` front-radar targets. The 116 unambiguous A matches and 86 B matches
|
||||
had longitudinal/lateral correlations of 0.999/0.991 and 0.999/0.993,
|
||||
respectively, with about 0.3 m mean absolute error. Across 14,939 consecutive
|
||||
same-slot updates, decoded longitudinal and lateral velocity correlated with
|
||||
position derivatives at 0.964 and 0.944. No lifecycle field has been
|
||||
identified, and the former acceleration candidate failed derivative checks.
|
||||
The remaining header, classification, dimension, ID, and health fields stay
|
||||
bit-preserved rather than borrowing names from the related front-radar core.
|
||||
|
||||
The first payload byte after the counter in `0x278` is
|
||||
`CHANNEL_A_OBJECT_COUNT`. Across 5,999 synchronized cycles on five Ioniq 5,
|
||||
Palisade, and EV6 samples, the number of non-sentinel `0x241–0x24F` slots equaled
|
||||
`min(CHANNEL_A_OBJECT_COUNT, 15)` exactly. It ranged from 0–22; values above
|
||||
15 prove it is the pre-truncation internal candidate count rather than merely
|
||||
the number of exported slots. The `0x240` status payload remains unresolved;
|
||||
notably, it was entirely zero after checksum/counter on one Palisade and one
|
||||
EV6 sample while being active on the other three routes.
|
||||
|
||||
The compact arrays are not alternate encodings of that object count. Depending
|
||||
on route and channel, 11–55 of their 56 bins were non-sentinel in a cycle,
|
||||
versus at most 15 exported tracked objects; count correlations ranged from
|
||||
weak to moderate and were never exact.
|
||||
|
||||
The compact 32-bit scan record is:
|
||||
|
||||
| Record field | Relative bit | Size | Scale | Status |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `DISTANCE_CANDIDATE` | 0 | 13 | 0.05 m | Range-like distribution and 400 m endpoint; no synchronized object association, so physical distance is not confirmed. |
|
||||
| `RESERVED` | 13 | 3 | raw | Preserve. |
|
||||
| `PROPERTY` | 16 | 7 | raw | Active categorical unknown; sampled values were quantized. |
|
||||
| `AUX` | 23 | 9 | raw | Active unknown; velocity and split flag/state hypotheses were falsified. |
|
||||
|
||||
Record 0 starts at message bit 24, message bit 56 is a frame-status byte, and
|
||||
records 1–6 start at bits 64, 96, 128, 160, 192, and 224. Unused records
|
||||
commonly contain `0x010D1F40`, with AUX variants such as `0x018D1F40`; its
|
||||
low-field component is the 400 m endpoint. Contemporaneous range matching was no
|
||||
better than the opposite channel or a 500 ms-shifted control. Each of the 56
|
||||
record positions instead has a characteristic occupancy/range profile, making
|
||||
this a strong fixed scan/bin-array candidate rather than 56 freely assigned
|
||||
tracked detections. A direct linear record-index-to-angle search on Ioniq 5
|
||||
and Palisade data also performed no better than a 500 ms-shifted control.
|
||||
Decoding the low field, physical ordering, `PROPERTY`, and `AUX` remains open.
|
||||
|
||||
## 3D0-3D4 front-radar auxiliary scan records
|
||||
|
||||
This optional list accompanies `3A5–3C4` on sampled EV9 and Ioniq 9 routes.
|
||||
Five 32-byte messages contain seven record positions each; the final three
|
||||
positions of `3D4` are outside the 32-position list. The repeated
|
||||
`0xC8782EE0` value and, on EV9, zero are unused records. All 12,090 checked
|
||||
message frames passed the HKG CAN-FD checksum and carried the same cycle
|
||||
counter as `3A5–3C4`.
|
||||
|
||||
| Record field | Relative bit | Size | Scale / offset | Status |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `DISTANCE_CANDIDATE` | 0 | 12 | 0.05 / 0 m | Range-like distribution and endpoint, but no synchronized tracked-object match; not confirmed. |
|
||||
| `FLAGS_UNKNOWN` | 12 | 4 | raw | Active values 0–2 in the sampled routes; semantics unknown. |
|
||||
| `UNKNOWN_BYTE_16` | 16 | 8 | raw | Former radial-speed candidate was falsified. |
|
||||
| `UNKNOWN_BYTE_24` | 24 | 8 | raw | Former azimuth candidate was falsified. |
|
||||
|
||||
Packing positions match the corner scan records: record 0 at message bit 24,
|
||||
one alignment byte at bit 56, then records 1–6 at bits 64 through 224. However,
|
||||
the exact corner `13/3/7/9` field split is not transferred onto this
|
||||
`12/4/8/8` layout without evidence.
|
||||
|
||||
The stronger synchronized test falsified three tempting interpretations:
|
||||
|
||||
- Range-only, candidate polar-geometry, and direct record-index-to-track-index
|
||||
associations were no better than opposite or 500 ms-shifted controls.
|
||||
- `UNKNOWN_BYTE_16` had essentially zero correlation with `3A5` longitudinal
|
||||
or radial relative speed.
|
||||
- `UNKNOWN_BYTE_24` had essentially zero correlation with `3A5` target angle.
|
||||
|
||||
The 32 positions do show stable position-dependent occupancy and low-field
|
||||
profiles, so a fixed scan/bin or intermediate radar-array role remains
|
||||
plausible. They are not independent tracked objects, remain excluded from
|
||||
production RadarData, and are shown only as raw records in the debugger's
|
||||
`SIGNALS` table, never projected as geometry.
|
||||
|
||||
## 360-366 forward-camera lane/path family
|
||||
|
||||
These seven 32-byte messages belong to the forward camera. `362` already
|
||||
contains left/right lane-line confidence. `360`, `361`, `363`, and `364`
|
||||
carry changing lane/path polynomial-like geometry; their field boundaries are
|
||||
not yet decoded. `365` and `366` are commonly constant/default-filled,
|
||||
consistent with unavailable extra lane slots. Their presence or emptiness
|
||||
must not be interpreted as a radar hardware combination.
|
||||
|
||||
## Reserved/dead bits
|
||||
|
||||
Bits `26`, `29`, `39`, `55`, `117`, `136-137`, `143`, `171`, and
|
||||
|
||||
+498
-45
@@ -75,23 +75,62 @@ WHITE = rl.Color(255, 255, 255, 255)
|
||||
PURPLE = rl.Color(190, 125, 255, 255)
|
||||
|
||||
SPEC_BY_RANGE = {
|
||||
(spec.start_addr, spec.end_addr): spec
|
||||
(spec.start_addr, end_addr): spec
|
||||
for spec in HYUNDAI_RADAR_TRACK_SPECS
|
||||
for end_addr in {spec.required_end_addr, spec.end_addr}
|
||||
}
|
||||
SPEC_BY_NAME = {spec.name: spec for spec in HYUNDAI_RADAR_TRACK_SPECS}
|
||||
PREFERRED_RADAR_SOURCE = "RADAR_3A5_3C4"
|
||||
DEFAULT_SOURCE_FILTERS = (False, True, True) # hide moving, stationary, unknown
|
||||
DEFAULT_SOURCE_FILTERS = (False, False, False) # hide moving, stationary, unknown
|
||||
RadarSourceKey = tuple[int, int, int]
|
||||
RADAR_DETAIL_SIGNALS = {
|
||||
"MOTION_STATE", "REL_LAT_SPEED", "ABS_SPEED", "WIDTH", "LENGTH", "ORIENTATION_ANGLE",
|
||||
"AGE", "COAST_AGE", "STATE_ALT", "TRACK_COUNTER",
|
||||
"RADAR_210_21F": {
|
||||
f"{prefix}{signal}"
|
||||
for prefix in ("1_", "2_")
|
||||
for signal in (
|
||||
"MOTION_STATE", "TRACK_QUALITY", "AGE", "COAST_AGE", "STATE", "STATE_ALT", "RCS",
|
||||
"REL_LAT_SPEED", "NEW_SIGNAL_4", "NEW_SIGNAL_18", "OBJECT_ID",
|
||||
)
|
||||
},
|
||||
"RADAR_3A5_3C4": {
|
||||
"MOTION_STATE", "TRACK_QUALITY", "AGE", "COAST_AGE", "STATE", "STATE_ALT", "RCS",
|
||||
"REL_LAT_SPEED", "ABS_SPEED", "WIDTH", "LENGTH", "ORIENTATION_ANGLE", "TRACK_COUNTER",
|
||||
"NEW_SIGNAL_4", "NEW_SIGNAL_5", "NEW_SIGNAL_12", "NEW_SIGNAL_13", "NEW_SIGNAL_14",
|
||||
"NEW_SIGNAL_15", "NEW_SIGNAL_16", "NEW_SIGNAL_17", "NEW_SIGNAL_18",
|
||||
},
|
||||
}
|
||||
TABLE_MODES = ("motion", "kinematics", "object")
|
||||
TABLE_MODES = ("motion", "kinematics", "object", "signals")
|
||||
PLAYBACK_SPEEDS = (0.2, 0.5, 1.0, 2.0, 4.0, 8.0)
|
||||
TRACK_COUNT_FIELD_WIDTH = 3
|
||||
SOURCE_CIRCLE_RADIUS_SCALE = 0.8
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ResearchSourceSpec:
|
||||
name: str
|
||||
start_address: int
|
||||
end_address: int
|
||||
message_sizes: tuple[int, ...]
|
||||
details: str
|
||||
|
||||
|
||||
RESEARCH_SOURCE_SPECS = (
|
||||
ResearchSourceSpec("CCNC_FUSED_OBJECTS", 0x162, 0x162, (32,), "fused cluster objects, not raw radar 32 B"),
|
||||
ResearchSourceSpec("RADAR_500_STATUS_CORE", 0x4E0, 0x4E1, (8,) * 2, "front-radar status 20 Hz ESR/shared 4E0"),
|
||||
ResearchSourceSpec("RADAR_500_BUILD", 0x4E2, 0x4E2, (8,), "optional build metadata 20 Hz"),
|
||||
ResearchSourceSpec("RADAR_500_PATHS", 0x4E3, 0x4E3, (8,), "front-radar path selectors/status 20 Hz"),
|
||||
ResearchSourceSpec("RADAR_500_HEALTH", 0x4E4, 0x4E5, (8,) * 2, "optional ADC/alignment status 20 Hz"),
|
||||
ResearchSourceSpec("RADAR_602_ALT", 0x612, 0x617, (8,) * 6, "alternate legacy front objects 8 B"),
|
||||
ResearchSourceSpec("CORNER_A_OBJECTS", 0x240, 0x24F, (16, *([24] * 15)), "right-front corner objects (inferred) 20 Hz"),
|
||||
ResearchSourceSpec("CORNER_A_DETECTIONS", 0x270, 0x277, (32,) * 8, "right-front compact scan bins (inferred) 20 Hz"),
|
||||
ResearchSourceSpec("CORNER_B_OBJECTS", 0x278, 0x287, (8, *([24] * 15)), "left-front corner objects (inferred) 20 Hz"),
|
||||
ResearchSourceSpec("CORNER_B_DETECTIONS", 0x288, 0x28F, (32,) * 8, "left-front compact scan bins (inferred) 20 Hz"),
|
||||
ResearchSourceSpec("RADAR_RAW_DETECTIONS", 0x3D0, 0x3D4, (32,) * 5, "front-radar auxiliary scan records 20 Hz 32 B"),
|
||||
ResearchSourceSpec("CAMERA_LANE_PATH", 0x360, 0x366, (32,) * 7, "forward-camera lane/path geometry 32 B"),
|
||||
)
|
||||
RESEARCH_SPEC_BY_RANGE = {(spec.start_address, spec.end_address): spec for spec in RESEARCH_SOURCE_SPECS}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ManagedReplay:
|
||||
process: subprocess.Popen
|
||||
@@ -401,6 +440,17 @@ class DisplayTrackSignals:
|
||||
state: int
|
||||
stateAlt: int
|
||||
trackCounter: int
|
||||
signalSummary: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DisplayRawSignal:
|
||||
startAddress: int
|
||||
endAddress: int
|
||||
address: int
|
||||
bus: int
|
||||
label: str
|
||||
signalSummary: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -408,10 +458,305 @@ class RadarSnapshot:
|
||||
points: tuple[DisplayTrack, ...] = ()
|
||||
trackSources: tuple[DisplaySource, ...] = ()
|
||||
trackSignals: tuple[DisplayTrackSignals, ...] = ()
|
||||
rawSignals: tuple[DisplayRawSignal, ...] = ()
|
||||
radarTracksAvailable: bool = False
|
||||
|
||||
|
||||
class ResearchCanDecoder:
|
||||
"""Inventory auxiliary families without publishing them as control-facing radar tracks."""
|
||||
|
||||
def __init__(self):
|
||||
self.payloads: dict[tuple[str, int], dict[int, bytes]] = {}
|
||||
|
||||
def update(self, can_messages) -> None:
|
||||
for address, dat, bus in can_messages:
|
||||
for spec in RESEARCH_SOURCE_SPECS:
|
||||
if not spec.start_address <= address <= spec.end_address:
|
||||
continue
|
||||
expected_size = spec.message_sizes[address - spec.start_address]
|
||||
if len(dat) == expected_size:
|
||||
self.payloads.setdefault((spec.name, int(bus)), {})[int(address)] = bytes(dat)
|
||||
|
||||
@staticmethod
|
||||
def _packed_detection_records(payload: bytes) -> tuple[int, ...]:
|
||||
return tuple(
|
||||
int.from_bytes(payload[offset:offset + 4], "little")
|
||||
for offset in (3, 8, 12, 16, 20, 24, 28)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _signed(raw: int, size: int) -> int:
|
||||
return raw - (1 << size) if raw & (1 << (size - 1)) else raw
|
||||
|
||||
@classmethod
|
||||
def _little(cls, raw: int, start: int, size: int, *, signed: bool = False) -> int:
|
||||
value = (raw >> start) & ((1 << size) - 1)
|
||||
return cls._signed(value, size) if signed else value
|
||||
|
||||
@classmethod
|
||||
def _big(cls, raw: int, start: int, size: int, *, signed: bool = False) -> int:
|
||||
value = 0
|
||||
bit = start
|
||||
for _ in range(size):
|
||||
value = (value << 1) | ((raw >> bit) & 1)
|
||||
bit = bit + 15 if bit % 8 == 0 else bit - 1
|
||||
return cls._signed(value, size) if signed else value
|
||||
|
||||
@classmethod
|
||||
def _corner_object(cls, payload: bytes) -> tuple[float, float, float] | None:
|
||||
raw = int.from_bytes(payload, "little")
|
||||
raw_dist = (raw >> 63) & 0x1FFF
|
||||
if raw_dist == 0xFFE:
|
||||
return None
|
||||
|
||||
d_rel = raw_dist * 0.05
|
||||
# Exclude the high-range default/sentinel region and points outside the debugger plot.
|
||||
if not 0 < d_rel <= MAX_FORWARD_DISTANCE:
|
||||
return None
|
||||
|
||||
y_rel = cls._signed((raw >> 76) & 0x7FF, 11) * 0.05
|
||||
v_rel = cls._signed((raw >> 88) & 0x3FF, 10) * 0.2
|
||||
return d_rel, y_rel, v_rel
|
||||
|
||||
@classmethod
|
||||
def _radar_500_status_summary(cls, address: int, raw: int) -> str:
|
||||
if address == 0x4E0:
|
||||
return " ".join((
|
||||
f"CTR:{cls._little(raw, 6, 2)}",
|
||||
f"TS:{cls._big(raw, 5, 7) * 2}ms",
|
||||
f"SCAN:{cls._big(raw, 31, 16)}",
|
||||
f"SPD:{cls._big(raw, 50, 11) * 0.0625:.2f}m/s",
|
||||
f"YAW:{cls._big(raw, 47, 12, signed=True) * 0.0625:.2f}deg/s",
|
||||
f"CURV:{cls._big(raw, 13, 14, signed=True)}m",
|
||||
f"COMM:{cls._big(raw, 14, 1)}",
|
||||
))
|
||||
if address == 0x4E1:
|
||||
return " ".join((
|
||||
f"CTR:{cls._big(raw, 1, 2)}",
|
||||
f"MAX:{cls._big(raw, 7, 6) + 1}",
|
||||
f"STEER:{cls._big(raw, 10, 11)}",
|
||||
f"RAW:{cls._big(raw, 11, 1)}",
|
||||
f"OP:{cls._big(raw, 12, 1)}",
|
||||
f"ERR:{cls._big(raw, 13, 1)}/{cls._big(raw, 14, 1)}/{cls._big(raw, 15, 1)}",
|
||||
f"TEMP:{cls._big(raw, 31, 8, signed=True)}C",
|
||||
f"GROUP:{cls._big(raw, 33, 2)}",
|
||||
f"VER:{cls._big(raw, 55, 16):04X}",
|
||||
))
|
||||
if address == 0x4E2:
|
||||
values = tuple(cls._little(raw, byte * 8, 8) for byte in range(8))
|
||||
return " ".join((
|
||||
f"BUILD:{values[0]:02X}-{values[1]:02X}-{values[2]:02X}",
|
||||
f"{values[3]:02X}:{values[4]:02X}",
|
||||
f"U5-7:{values[5]:02X}/{values[6]:02X}/{values[7]:02X}",
|
||||
))
|
||||
if address == 0x4E3:
|
||||
return " ".join((
|
||||
f"CTR:{cls._big(raw, 1, 2)}",
|
||||
f"MODE:{cls._big(raw, 3, 2)}",
|
||||
f"BLOCK:{cls._big(raw, 4, 1)}/{cls._big(raw, 5, 1)}/{cls._big(raw, 6, 1)}",
|
||||
f"TRUCK:{cls._big(raw, 7, 1)}",
|
||||
f"PATHS:{'/'.join(str(cls._big(raw, start, 8)) for start in (15, 23, 31, 39, 47, 63))}",
|
||||
f"ALIGN:{cls._big(raw, 55, 8, signed=True) * 0.0625:.2f}deg",
|
||||
))
|
||||
if address == 0x4E4:
|
||||
names = ("BAT", "IGN", "T1", "T2", "5VA", "5VDX", "3V3", "10V")
|
||||
return " ".join(f"{name}:{cls._little(raw, byte * 8, 8):02X}" for byte, name in enumerate(names))
|
||||
if address == 0x4E5:
|
||||
return " ".join((
|
||||
f"1V8:{cls._little(raw, 0, 8):02X}",
|
||||
f"N5V:{cls._little(raw, 8, 8):02X}",
|
||||
f"WAVE:{cls._little(raw, 16, 8):02X}",
|
||||
f"PWR:{cls._little(raw, 24, 3)}",
|
||||
f"VUP:{cls._little(raw, 27, 1)}",
|
||||
f"FACT:{cls._little(raw, 32, 3)}/{cls._little(raw, 35, 3)}",
|
||||
f"FLAGS:{cls._little(raw, 38, 1)}/{cls._little(raw, 39, 1)}",
|
||||
f"MIS:{cls._little(raw, 40, 8, signed=True)}/{cls._little(raw, 56, 8, signed=True)}",
|
||||
f"UPDATES:{cls._little(raw, 48, 8)}",
|
||||
))
|
||||
return f"RAW:{raw:016X}"
|
||||
|
||||
def snapshot(
|
||||
self,
|
||||
) -> tuple[
|
||||
tuple[DisplaySource, ...],
|
||||
tuple[DisplayTrack, ...],
|
||||
tuple[DisplayTrackSignals, ...],
|
||||
tuple[DisplayRawSignal, ...],
|
||||
dict[int, tuple[int, int]],
|
||||
]:
|
||||
sources = []
|
||||
tracks = []
|
||||
track_signals = []
|
||||
raw_signals = []
|
||||
track_locations = {}
|
||||
for spec in RESEARCH_SOURCE_SPECS:
|
||||
for (name, bus), payloads in self.payloads.items():
|
||||
if name != spec.name or any(address not in payloads for address in range(spec.start_address, spec.end_address + 1)):
|
||||
continue
|
||||
|
||||
track_count = 0
|
||||
if spec.name == "RADAR_602_ALT":
|
||||
for address in range(spec.start_address, spec.end_address + 1):
|
||||
raw = int.from_bytes(payloads[address], "little")
|
||||
d_rel = self._little(raw, 0, 10) * 0.25
|
||||
if not 0 < d_rel < 255.75:
|
||||
continue
|
||||
y_rel = self._little(raw, 10, 11) * 0.03 - 30.705
|
||||
v_rel = self._little(raw, 21, 10) * 0.25 - 128
|
||||
object_id = self._little(raw, 31, 9)
|
||||
track_id = 800_000 + bus * 0x10000 + address
|
||||
tracks.append(DisplayTrack(
|
||||
trackId=track_id,
|
||||
dRel=d_rel,
|
||||
yRel=y_rel,
|
||||
vRel=v_rel,
|
||||
aRel=float("nan"),
|
||||
canAddress=address,
|
||||
canBus=bus,
|
||||
))
|
||||
track_signals.append(DisplayTrackSignals(
|
||||
trackId=track_id,
|
||||
relLatSpeed=float("nan"),
|
||||
absSpeed=float("nan"),
|
||||
width=float("nan"),
|
||||
length=float("nan"),
|
||||
orientationAngle=float("nan"),
|
||||
age=-1,
|
||||
coastAge=-1,
|
||||
state=-1,
|
||||
stateAlt=-1,
|
||||
trackCounter=-1,
|
||||
signalSummary=" ".join((
|
||||
f"ID:{object_id}",
|
||||
f"S1:{self._little(raw, 42, 8) - 128}",
|
||||
f"S2:{self._little(raw, 50, 6) - 32}",
|
||||
f"CTR:{self._little(raw, 56, 4)}",
|
||||
f"CRC:{self._little(raw, 60, 4):X}",
|
||||
)),
|
||||
))
|
||||
track_locations[track_id] = (address, bus)
|
||||
track_count += 1
|
||||
elif spec.name in ("CORNER_A_OBJECTS", "CORNER_B_OBJECTS"):
|
||||
object_start = 0x241 if spec.name == "CORNER_A_OBJECTS" else 0x279
|
||||
for address in range(object_start, object_start + 15):
|
||||
decoded = self._corner_object(payloads[address])
|
||||
if decoded is None:
|
||||
continue
|
||||
track_id = 900_000 + bus * 0x10000 + address
|
||||
d_rel, y_rel, v_rel = decoded
|
||||
tracks.append(DisplayTrack(
|
||||
trackId=track_id,
|
||||
dRel=d_rel,
|
||||
yRel=y_rel,
|
||||
vRel=v_rel,
|
||||
aRel=float("nan"),
|
||||
canAddress=address,
|
||||
canBus=bus,
|
||||
))
|
||||
raw = int.from_bytes(payloads[address], "little")
|
||||
category = (raw >> 24) & 0x3
|
||||
rcs = self._signed((raw >> 56) & 0x7F, 7)
|
||||
unknown_87 = (raw >> 87) & 0x1
|
||||
unknown_108 = (raw >> 108) & 0x3FF
|
||||
unknown_118 = (raw >> 118) & 0x3FF
|
||||
unknown_128 = (raw >> 128) & 0xFFFFFFFFFFFFFFFF
|
||||
track_signals.append(DisplayTrackSignals(
|
||||
trackId=track_id,
|
||||
relLatSpeed=self._signed((raw >> 98) & 0x3FF, 10) * 0.05,
|
||||
absSpeed=float("nan"),
|
||||
width=float("nan"),
|
||||
length=float("nan"),
|
||||
orientationAngle=float("nan"),
|
||||
age=-1,
|
||||
coastAge=-1,
|
||||
state=-1,
|
||||
stateAlt=-1,
|
||||
trackCounter=-1,
|
||||
signalSummary=" ".join((
|
||||
f"CAT:{category}", f"RCS:{rcs}", f"U87:{unknown_87}",
|
||||
f"U108:{unknown_108:03X}", f"U118:{unknown_118:03X}", f"U128:{unknown_128:016X}",
|
||||
)),
|
||||
))
|
||||
track_locations[track_id] = (address, bus)
|
||||
track_count += 1
|
||||
elif spec.name == "CCNC_FUSED_OBJECTS":
|
||||
raw = int.from_bytes(payloads[0x162], "little")
|
||||
fused_slots = (
|
||||
("FRONT", self._little(raw, 64, 5), self._little(raw, 69, 11) * 0.1, self._little(raw, 80, 7) * 0.1),
|
||||
("FRONT_ALT", self._little(raw, 88, 5), self._little(raw, 93, 11) * 0.1, self._little(raw, 104, 7) * 0.1),
|
||||
("LEFT", self._little(raw, 112, 5), self._little(raw, 117, 11) * 0.1, self._little(raw, 128, 7) * 0.1),
|
||||
("RIGHT", self._little(raw, 136, 5), self._little(raw, 141, 11) * 0.1, self._little(raw, 152, 7) * 0.1),
|
||||
("LEFT_REAR", self._big(raw, 167, 5), self._big(raw, 175, 8) * 0.1, self._big(raw, 182, 7) * 0.1),
|
||||
("RIGHT_REAR", self._big(raw, 196, 5), self._little(raw, 197, 8) * 0.1, self._little(raw, 205, 7) * 0.1),
|
||||
)
|
||||
for label, status, distance, lateral in fused_slots:
|
||||
raw_signals.append(DisplayRawSignal(
|
||||
spec.start_address, spec.end_address, 0x162, bus, label,
|
||||
f"STATUS:{status} DIST:{distance:.1f}m LAT:{lateral:.1f}m",
|
||||
))
|
||||
track_count += status != 0
|
||||
raw_signals.append(DisplayRawSignal(
|
||||
spec.start_address, spec.end_address, 0x162, bus, "FAULTS",
|
||||
" ".join((
|
||||
f"FSS:{self._little(raw, 213, 3)}", f"FCA:{self._little(raw, 216, 3)}",
|
||||
f"LSS:{self._little(raw, 219, 3)}", f"SLA:{self._little(raw, 222, 3)}",
|
||||
f"DAW:{self._little(raw, 225, 3)}", f"HBA:{self._little(raw, 228, 3)}",
|
||||
f"SCC:{self._little(raw, 231, 3)}", f"LFA:{self._little(raw, 234, 3)}",
|
||||
)),
|
||||
))
|
||||
elif spec.name in ("CORNER_A_DETECTIONS", "CORNER_B_DETECTIONS", "RADAR_RAW_DETECTIONS"):
|
||||
for address in range(spec.start_address, spec.end_address + 1):
|
||||
for record, raw in enumerate(self._packed_detection_records(payloads[address])):
|
||||
if raw == 0:
|
||||
continue
|
||||
if spec.name in ("CORNER_A_DETECTIONS", "CORNER_B_DETECTIONS"):
|
||||
if (raw & 0x1FFF) == 8000:
|
||||
continue
|
||||
raw_signals.append(DisplayRawSignal(
|
||||
spec.start_address, spec.end_address, address, bus, f"REC{record}",
|
||||
" ".join((
|
||||
f"DIST?:{(raw & 0x1FFF) * 0.05:.2f}",
|
||||
f"RSV:{(raw >> 13) & 0x7}",
|
||||
f"PROP:{(raw >> 16) & 0x7F}",
|
||||
f"AUX:{(raw >> 23) & 0x1FF}",
|
||||
f"RAW:{raw:08X}",
|
||||
)),
|
||||
))
|
||||
track_count += 1
|
||||
continue
|
||||
if raw == 0xC8782EE0:
|
||||
continue
|
||||
raw_signals.append(DisplayRawSignal(
|
||||
spec.start_address, spec.end_address, address, bus, f"REC{record}",
|
||||
" ".join((
|
||||
f"DIST?:{(raw & 0xFFF) * 0.05:.2f}",
|
||||
f"FLAGS:{(raw >> 12) & 0xF}",
|
||||
f"U16:{(raw >> 16) & 0xFF:02X}",
|
||||
f"U24:{(raw >> 24) & 0xFF:02X}",
|
||||
f"RAW:{raw:08X}",
|
||||
)),
|
||||
))
|
||||
track_count += 1
|
||||
elif spec.name.startswith("RADAR_500_"):
|
||||
for address in range(spec.start_address, spec.end_address + 1):
|
||||
raw = int.from_bytes(payloads[address], "little")
|
||||
raw_signals.append(DisplayRawSignal(
|
||||
spec.start_address, spec.end_address, address, bus, f"{address:X}",
|
||||
self._radar_500_status_summary(address, raw),
|
||||
))
|
||||
elif spec.name == "CAMERA_LANE_PATH":
|
||||
for address in range(spec.start_address, spec.end_address + 1):
|
||||
payload = payloads[address]
|
||||
raw_signals.append(DisplayRawSignal(
|
||||
spec.start_address, spec.end_address, address, bus, f"{address:X}",
|
||||
f"CTR:{payload[2]} DATA:{payload[3:].hex().upper()}",
|
||||
))
|
||||
sources.append(DisplaySource(spec.start_address, spec.end_address, bus, track_count))
|
||||
return tuple(sources), tuple(tracks), tuple(track_signals), tuple(raw_signals), track_locations
|
||||
|
||||
|
||||
def make_radar_snapshot(radar_data, track_signals: tuple[DisplayTrackSignals, ...] = (),
|
||||
raw_signals: tuple[DisplayRawSignal, ...] = (),
|
||||
track_locations: dict[int, tuple[int, int]] | None = None) -> RadarSnapshot:
|
||||
track_locations = track_locations or {}
|
||||
return RadarSnapshot(
|
||||
@@ -431,20 +776,26 @@ def make_radar_snapshot(radar_data, track_signals: tuple[DisplayTrackSignals, ..
|
||||
trackCount=int(source.trackCount),
|
||||
) for source in radar_data.trackSources),
|
||||
trackSignals=track_signals,
|
||||
rawSignals=raw_signals,
|
||||
radarTracksAvailable=bool(radar_data.radarTracksAvailable),
|
||||
)
|
||||
|
||||
|
||||
def source_name(start_address: int, end_address: int) -> str:
|
||||
spec = SPEC_BY_RANGE.get((start_address, end_address))
|
||||
return spec.name if spec is not None else f"RADAR_{start_address:X}_{end_address:X}"
|
||||
if spec is not None:
|
||||
return "CAMERA_OBJECTS_235_248" if spec.source_kind == "camera" else spec.name
|
||||
research_spec = RESEARCH_SPEC_BY_RANGE.get((start_address, end_address))
|
||||
return research_spec.name if research_spec is not None else f"RADAR_{start_address:X}_{end_address:X}"
|
||||
|
||||
|
||||
def source_details(start_address: int, end_address: int) -> str:
|
||||
spec = SPEC_BY_RANGE.get((start_address, end_address))
|
||||
if spec is None:
|
||||
return "unknown format"
|
||||
return f"{spec.frequency} Hz {spec.message_size} B"
|
||||
if spec is not None:
|
||||
prefix = "forward-camera objects " if spec.source_kind == "camera" else ""
|
||||
return f"{prefix}{spec.frequency} Hz {spec.message_size} B"
|
||||
research_spec = RESEARCH_SPEC_BY_RANGE.get((start_address, end_address))
|
||||
return research_spec.details if research_spec is not None else "unknown format"
|
||||
|
||||
|
||||
def radar_source_sort_key(source) -> tuple[bool, int, int, int]:
|
||||
@@ -463,6 +814,8 @@ def radar_source_key(source) -> RadarSourceKey:
|
||||
|
||||
def source_filter_state(source_filters: dict[RadarSourceKey, tuple[bool, bool, bool]],
|
||||
source_key: RadarSourceKey) -> tuple[bool, bool, bool]:
|
||||
if source_key[:2] == (0x3D0, 0x3D4):
|
||||
return source_filters.get(source_key, (False, True, False))
|
||||
return source_filters.get(source_key, DEFAULT_SOURCE_FILTERS)
|
||||
|
||||
|
||||
@@ -476,7 +829,7 @@ def toggle_source_filter(source_filters: dict[RadarSourceKey, tuple[bool, bool,
|
||||
def dbc_motion_class(motion_state: int | None) -> str:
|
||||
if motion_state is None:
|
||||
return "unknown"
|
||||
return {0: "unknown (raw 0)", 1: "stationary", 2: "moving"}.get(
|
||||
return {0: "unknown (raw 0)", 1: "stationary", 2: "moving", 3: "stopped", 4: "oncoming"}.get(
|
||||
motion_state, f"unknown (raw {motion_state})",
|
||||
)
|
||||
|
||||
@@ -518,7 +871,8 @@ def enable_dbc_detail_signals(radar_interface: RadarInterface, enhanced_parsers:
|
||||
"""Add visualization-only signals to the branch parser without maintaining a second CAN decoder."""
|
||||
for radar_parser in radar_interface.radar_parsers:
|
||||
parser_key = (radar_parser.spec.name, radar_parser.bus)
|
||||
if parser_key in enhanced_parsers or radar_parser.spec.name != "RADAR_3A5_3C4":
|
||||
detail_signals = RADAR_DETAIL_SIGNALS.get(radar_parser.spec.name)
|
||||
if parser_key in enhanced_parsers or detail_signals is None:
|
||||
continue
|
||||
|
||||
# This UI can intentionally run slower than replay and uses conflated CAN, so don't apply parser liveness checks here.
|
||||
@@ -527,7 +881,7 @@ def enable_dbc_detail_signals(radar_interface: RadarInterface, enhanced_parsers:
|
||||
radar_parser.spec.dbc_name,
|
||||
messages,
|
||||
radar_parser.bus,
|
||||
signals={*radar_parser.spec.signals, *RADAR_DETAIL_SIGNALS},
|
||||
signals={*radar_parser.spec.signals, *detail_signals},
|
||||
)
|
||||
for message_state in parser.message_states.values():
|
||||
message_state.ignore_alive = True
|
||||
@@ -535,6 +889,10 @@ def enable_dbc_detail_signals(radar_interface: RadarInterface, enhanced_parsers:
|
||||
enhanced_parsers.add(parser_key)
|
||||
|
||||
|
||||
def dbc_signal_value(message, prefix: str, name: str, default=0):
|
||||
return message.get(f"{prefix}{name}", default)
|
||||
|
||||
|
||||
def get_dbc_track_details(
|
||||
radar_interface: RadarInterface,
|
||||
) -> tuple[dict[int, int], tuple[DisplayTrackSignals, ...], dict[int, tuple[int, int]]]:
|
||||
@@ -553,7 +911,7 @@ def get_dbc_track_details(
|
||||
active_bus = radar_interface.active_radar_buses.get(track_key[0])
|
||||
locations[int(point.trackId)] = (can_address, active_bus if active_bus is not None else -1)
|
||||
|
||||
if track_key[0] != "RADAR_3A5_3C4":
|
||||
if track_key[0] not in RADAR_DETAIL_SIGNALS:
|
||||
continue
|
||||
|
||||
radar_parser = next((parser for parser in radar_interface.radar_parsers
|
||||
@@ -561,21 +919,45 @@ def get_dbc_track_details(
|
||||
if radar_parser is None:
|
||||
continue
|
||||
|
||||
message = radar_parser.parser.vl[f"RADAR_TRACK_{track_key[1]:x}"]
|
||||
if "MOTION_STATE" in message:
|
||||
states[int(point.trackId)] = int(message["MOTION_STATE"])
|
||||
prefix = f"{stored_address % 2 + 1}_" if spec.name == "RADAR_210_21F" else ""
|
||||
message = radar_parser.parser.vl[f"RADAR_TRACK_{can_address:x}"]
|
||||
motion_signal = f"{prefix}MOTION_STATE"
|
||||
if motion_signal in message:
|
||||
states[int(point.trackId)] = int(message[motion_signal])
|
||||
|
||||
if spec.name == "RADAR_210_21F":
|
||||
signal_summary = " ".join((
|
||||
f"Q:{int(dbc_signal_value(message, prefix, 'TRACK_QUALITY'))}",
|
||||
f"RCS:{int(dbc_signal_value(message, prefix, 'RCS'))}",
|
||||
f"U4:{int(dbc_signal_value(message, prefix, 'NEW_SIGNAL_4'))}",
|
||||
f"U18:{int(dbc_signal_value(message, prefix, 'NEW_SIGNAL_18'))}",
|
||||
f"ID:{int(dbc_signal_value(message, prefix, 'OBJECT_ID'))}",
|
||||
))
|
||||
else:
|
||||
geometry = "/".join(
|
||||
str(int(dbc_signal_value(message, prefix, f"NEW_SIGNAL_{index}"))) for index in range(12, 18)
|
||||
)
|
||||
signal_summary = " ".join((
|
||||
f"Q:{int(dbc_signal_value(message, prefix, 'TRACK_QUALITY'))}",
|
||||
f"RCS:{int(dbc_signal_value(message, prefix, 'RCS'))}",
|
||||
f"U4:{int(dbc_signal_value(message, prefix, 'NEW_SIGNAL_4'))}",
|
||||
f"U5:{int(dbc_signal_value(message, prefix, 'NEW_SIGNAL_5'))}",
|
||||
f"U18:{int(dbc_signal_value(message, prefix, 'NEW_SIGNAL_18'))}",
|
||||
f"G12-17:{geometry}",
|
||||
))
|
||||
details.append(DisplayTrackSignals(
|
||||
trackId=int(point.trackId),
|
||||
relLatSpeed=float(message["REL_LAT_SPEED"]),
|
||||
absSpeed=float(message["ABS_SPEED"]),
|
||||
width=float(message["WIDTH"]),
|
||||
length=float(message["LENGTH"]),
|
||||
orientationAngle=float(message["ORIENTATION_ANGLE"]),
|
||||
age=int(message["AGE"]),
|
||||
coastAge=int(message["COAST_AGE"]),
|
||||
state=int(message["STATE"]),
|
||||
stateAlt=int(message["STATE_ALT"]),
|
||||
trackCounter=int(message["TRACK_COUNTER"]),
|
||||
relLatSpeed=float(dbc_signal_value(message, prefix, "REL_LAT_SPEED", float("nan"))),
|
||||
absSpeed=float(dbc_signal_value(message, prefix, "ABS_SPEED", float("nan"))),
|
||||
width=float(dbc_signal_value(message, prefix, "WIDTH", float("nan"))),
|
||||
length=float(dbc_signal_value(message, prefix, "LENGTH", float("nan"))),
|
||||
orientationAngle=float(dbc_signal_value(message, prefix, "ORIENTATION_ANGLE", float("nan"))),
|
||||
age=int(dbc_signal_value(message, prefix, "AGE", -1)),
|
||||
coastAge=int(dbc_signal_value(message, prefix, "COAST_AGE", -1)),
|
||||
state=int(dbc_signal_value(message, prefix, "STATE", -1)),
|
||||
stateAlt=int(dbc_signal_value(message, prefix, "STATE_ALT", -1)),
|
||||
trackCounter=int(dbc_signal_value(message, prefix, "TRACK_COUNTER", -1)),
|
||||
signalSummary=signal_summary,
|
||||
))
|
||||
return states, tuple(details), locations
|
||||
|
||||
@@ -620,8 +1002,10 @@ def radar_decoder_worker(addr: str, output_queue) -> None:
|
||||
radar_cp.flags = 0
|
||||
radar_cp_sp = structs.CarParamsSP(flags=HyundaiFlagsSP.RADAR_FULL_RADAR.value)
|
||||
radar_interface = RadarInterface(radar_cp, radar_cp_sp)
|
||||
research_decoder = ResearchCanDecoder()
|
||||
enhanced_parsers: set[tuple[str, int]] = set()
|
||||
snapshot = RadarSnapshot()
|
||||
radar_snapshot = RadarSnapshot()
|
||||
snapshot = radar_snapshot
|
||||
motion_states: dict[int, int] = {}
|
||||
last_publish_time = 0.0
|
||||
last_can_message_time = 0.0
|
||||
@@ -632,11 +1016,20 @@ def radar_decoder_worker(addr: str, output_queue) -> None:
|
||||
if sm.updated["can"]:
|
||||
last_can_message_time = time.monotonic()
|
||||
can_messages = [(message.address, bytes(message.dat), message.src) for message in sm["can"]]
|
||||
research_decoder.update(can_messages)
|
||||
radar_data = radar_interface.update([(sm.logMonoTime["can"], can_messages)])
|
||||
enable_dbc_detail_signals(radar_interface, enhanced_parsers)
|
||||
if radar_data is not None:
|
||||
motion_states, track_signals, track_locations = get_dbc_track_details(radar_interface)
|
||||
snapshot = make_radar_snapshot(radar_data, track_signals, track_locations)
|
||||
radar_snapshot = make_radar_snapshot(radar_data, track_signals, track_locations=track_locations)
|
||||
research_sources, research_tracks, research_signals, raw_signals, _ = research_decoder.snapshot()
|
||||
snapshot = RadarSnapshot(
|
||||
points=(*radar_snapshot.points, *research_tracks),
|
||||
trackSources=(*radar_snapshot.trackSources, *research_sources),
|
||||
trackSignals=(*radar_snapshot.trackSignals, *research_signals),
|
||||
rawSignals=raw_signals,
|
||||
radarTracksAvailable=radar_snapshot.radarTracksAvailable,
|
||||
)
|
||||
|
||||
now = time.monotonic()
|
||||
if radar_data is not None or now - last_publish_time >= 0.1:
|
||||
@@ -1092,11 +1485,54 @@ def dbc_track_state(state: int) -> str:
|
||||
def draw_track_table(font, rect: rl.Rectangle, tracks, motion_states: dict[int, int], scroll: int,
|
||||
selected_id: int | None, hovered_id: int | None,
|
||||
track_signals: dict[int, DisplayTrackSignals], track_locations: dict[int, tuple[int, int]],
|
||||
table_mode: str) -> None:
|
||||
table_mode: str, raw_signals: tuple[DisplayRawSignal, ...] = ()) -> None:
|
||||
rl.draw_rectangle_rec(rect, BACKGROUND)
|
||||
rl.draw_line_ex(rl.Vector2(rect.x, rect.y), rl.Vector2(rect.x + rect.width, rect.y), 2.0, GRID)
|
||||
sorted_tracks = sorted(tracks, key=lambda track: (track.dRel, track.trackId))
|
||||
capacity = table_capacity(rect)
|
||||
|
||||
if table_mode == "signals":
|
||||
rows = [
|
||||
(
|
||||
"{:X}/B{}".format(*track_locations.get(int(track.trackId), (-1, -1))),
|
||||
f"TRACK {track.trackId}",
|
||||
track_signals[int(track.trackId)].signalSummary,
|
||||
int(track.trackId),
|
||||
)
|
||||
for track in sorted_tracks
|
||||
if int(track.trackId) in track_signals
|
||||
]
|
||||
rows.extend(
|
||||
(
|
||||
f"{signal.address:X}/B{signal.bus}",
|
||||
f"{source_name(signal.startAddress, signal.endAddress).removeprefix('RADAR_')} {signal.label}",
|
||||
signal.signalSummary,
|
||||
None,
|
||||
)
|
||||
for signal in sorted(raw_signals, key=lambda item: (
|
||||
item.startAddress, item.bus, item.address, item.label,
|
||||
))
|
||||
)
|
||||
visible_rows = rows[scroll:scroll + capacity]
|
||||
draw_text(font, "SIGNALS", rect.x + 12, rect.y + 8, 21, TEXT)
|
||||
if rows:
|
||||
draw_text(font, f"{scroll + 1}-{scroll + len(visible_rows)} / {len(rows)}",
|
||||
rect.x + rect.width - 112, rect.y + 11, 16, MUTED)
|
||||
columns = (("CAN", 0.02), ("SOURCE / ITEM", 0.12), ("DECODED / RAW SIGNALS", 0.36))
|
||||
header_y = rect.y + 36
|
||||
for title, offset in columns:
|
||||
draw_text(font, title, rect.x + rect.width * offset, header_y, 15, MUTED)
|
||||
for row, (can_location, label, summary, track_id) in enumerate(visible_rows):
|
||||
y = header_y + 23 + row * 24
|
||||
if track_id == selected_id:
|
||||
rl.draw_rectangle_rec(rl.Rectangle(rect.x, y - 3, rect.width, 24), rl.Color(CYAN.r, CYAN.g, CYAN.b, 42))
|
||||
elif row % 2:
|
||||
rl.draw_rectangle_rec(rl.Rectangle(rect.x, y - 3, rect.width, 24), rl.Color(255, 255, 255, 8))
|
||||
draw_text(font, can_location, rect.x + rect.width * 0.02, y, 15, TEXT)
|
||||
draw_text(font, label, rect.x + rect.width * 0.12, y, 15, CYAN if track_id is None else TEXT)
|
||||
draw_text(font, summary, rect.x + rect.width * 0.36, y, 15, TEXT)
|
||||
return
|
||||
|
||||
visible = sorted_tracks[scroll:scroll + capacity]
|
||||
|
||||
draw_text(font, f"TRACK {table_mode.upper()}", rect.x + 12, rect.y + 8, 21, TEXT)
|
||||
@@ -1143,21 +1579,29 @@ def draw_track_table(font, rect: rl.Rectangle, tracks, motion_states: dict[int,
|
||||
(f"{track.dRel:.1f}", 0.19, TEXT),
|
||||
(f"{track.yRel:+.1f}", 0.29, TEXT),
|
||||
(f"{track.vRel:+.1f}", 0.39, TEXT),
|
||||
(f"{detail.relLatSpeed:+.1f}" if detail else "n/a", 0.50, TEXT if detail else MUTED),
|
||||
(f"{detail.relLatSpeed:+.1f}" if detail and math.isfinite(detail.relLatSpeed) else "n/a",
|
||||
0.50, TEXT if detail and math.isfinite(detail.relLatSpeed) else MUTED),
|
||||
(f"{track.aRel:+.1f}" if math.isfinite(track.aRel) else "n/a", 0.61, TEXT),
|
||||
(f"{detail.absSpeed:.1f}" if detail else "n/a", 0.73, TEXT if detail else MUTED),
|
||||
(str(detail.age) if detail else "n/a", 0.88, TEXT if detail else MUTED),
|
||||
(f"{detail.absSpeed:.1f}" if detail and math.isfinite(detail.absSpeed) else "n/a",
|
||||
0.73, TEXT if detail and math.isfinite(detail.absSpeed) else MUTED),
|
||||
(str(detail.age) if detail and detail.age >= 0 else "n/a", 0.88, TEXT if detail and detail.age >= 0 else MUTED),
|
||||
)
|
||||
elif table_mode == "object":
|
||||
values = (
|
||||
(str(track.trackId), 0.02, TEXT),
|
||||
(can_location, 0.09, TEXT if can_address >= 0 else MUTED),
|
||||
(f"{detail.width:.1f}" if detail else "n/a", 0.21, TEXT if detail else MUTED),
|
||||
(f"{detail.length:.1f}" if detail else "n/a", 0.33, TEXT if detail else MUTED),
|
||||
(f"{detail.orientationAngle:+.0f}" if detail else "n/a", 0.45, TEXT if detail else MUTED),
|
||||
(dbc_track_state(detail.state) if detail else "n/a", 0.57, TEXT if detail else MUTED),
|
||||
(str(detail.coastAge) if detail else "n/a", 0.74, TEXT if detail else MUTED),
|
||||
(str(detail.trackCounter) if detail else "n/a", 0.87, TEXT if detail else MUTED),
|
||||
(f"{detail.width:.1f}" if detail and math.isfinite(detail.width) else "n/a",
|
||||
0.21, TEXT if detail and math.isfinite(detail.width) else MUTED),
|
||||
(f"{detail.length:.1f}" if detail and math.isfinite(detail.length) else "n/a",
|
||||
0.33, TEXT if detail and math.isfinite(detail.length) else MUTED),
|
||||
(f"{detail.orientationAngle:+.0f}" if detail and math.isfinite(detail.orientationAngle) else "n/a",
|
||||
0.45, TEXT if detail and math.isfinite(detail.orientationAngle) else MUTED),
|
||||
(dbc_track_state(detail.state) if detail and detail.state >= 0 else "n/a",
|
||||
0.57, TEXT if detail and detail.state >= 0 else MUTED),
|
||||
(str(detail.coastAge) if detail and detail.coastAge >= 0 else "n/a",
|
||||
0.74, TEXT if detail and detail.coastAge >= 0 else MUTED),
|
||||
(str(detail.trackCounter) if detail and detail.trackCounter >= 0 else "n/a",
|
||||
0.87, TEXT if detail and detail.trackCounter >= 0 else MUTED),
|
||||
)
|
||||
else:
|
||||
values = (
|
||||
@@ -1542,8 +1986,8 @@ def draw_source_status(font, rect: rl.Rectangle, live_tracks, valid: bool, alive
|
||||
spec = SPEC_BY_RANGE.get((source.startAddress, source.endAddress))
|
||||
compact_name = source_name(source.startAddress, source.endAddress).removeprefix("RADAR_")
|
||||
source_parts = [
|
||||
(compact_name, f"Radar CAN address range 0x{source.startAddress:X}-0x{source.endAddress:X}"),
|
||||
(f"B{source.bus}", f"CAN bus {source.bus} carrying this radar source"),
|
||||
(compact_name, f"CAN address range 0x{source.startAddress:X}-0x{source.endAddress:X}"),
|
||||
(f"B{source.bus}", f"CAN bus {source.bus} carrying this source"),
|
||||
]
|
||||
if spec is not None:
|
||||
source_parts.extend((
|
||||
@@ -1551,7 +1995,8 @@ def draw_source_status(font, rect: rl.Rectangle, live_tracks, valid: bool, alive
|
||||
(f"{spec.message_size} B", "Radar CAN message payload size"),
|
||||
))
|
||||
else:
|
||||
source_parts.append(("unknown format", "No supported radar format matches this address range"))
|
||||
details = source_details(source.startAddress, source.endAddress)
|
||||
source_parts.append((details, details))
|
||||
source_hovered_tooltip = draw_status_parts(
|
||||
font, tuple(source_parts), source_x, row_y, 16, CYAN, mouse_position, " / ",
|
||||
)
|
||||
@@ -2043,6 +2488,7 @@ def ui_thread(addr: str, start_wide: bool = False, start_fused: bool = False, st
|
||||
if use_can_source:
|
||||
selected_tracks = can_tracks
|
||||
tracks = decoded_tracks
|
||||
raw_signals = can_tracks.rawSignals
|
||||
data_valid = can_data_seen
|
||||
data_alive = can_data_alive
|
||||
motion_states = decoded_motion_states
|
||||
@@ -2054,6 +2500,7 @@ def ui_thread(addr: str, start_wide: bool = False, start_fused: bool = False, st
|
||||
data_valid = live_data_seen
|
||||
data_alive = sm.alive["liveTracks"]
|
||||
tracks = list(selected_tracks.points) if data_valid else []
|
||||
raw_signals = ()
|
||||
motion_states = match_decoded_track_values(tracks, decoded_tracks, decoded_motion_states)
|
||||
track_signals = match_decoded_track_values(tracks, decoded_tracks, decoded_track_signals)
|
||||
track_locations = match_decoded_track_values(tracks, decoded_tracks, decoded_track_locations)
|
||||
@@ -2069,12 +2516,18 @@ def ui_thread(addr: str, start_wide: bool = False, start_fused: bool = False, st
|
||||
tracks = filter_tracks(
|
||||
tracks, motion_states, track_locations, selected_tracks.trackSources, source_filters,
|
||||
)
|
||||
current_table_mode = TABLE_MODES[table_mode_index]
|
||||
capacity = table_capacity(table_rect)
|
||||
max_scroll = max(0, len(tracks) - capacity)
|
||||
table_row_count = (
|
||||
sum(int(track.trackId) in track_signals for track in tracks) + len(raw_signals)
|
||||
if current_table_mode == "signals"
|
||||
else len(tracks)
|
||||
)
|
||||
max_scroll = max(0, table_row_count - capacity)
|
||||
if rl.check_collision_point_rec(rl.get_mouse_position(), table_rect):
|
||||
table_scroll -= int(rl.get_mouse_wheel_move() * 3)
|
||||
table_scroll = int(np.clip(table_scroll, 0, max_scroll))
|
||||
table_hover_id = None if composite_mode else table_hovered_track_id(
|
||||
table_hover_id = None if composite_mode or current_table_mode == "signals" else table_hovered_track_id(
|
||||
tracks, table_rect, table_scroll, rl.get_mouse_position(),
|
||||
)
|
||||
if (table_hover_id is not None
|
||||
@@ -2183,7 +2636,7 @@ def ui_thread(addr: str, start_wide: bool = False, start_fused: bool = False, st
|
||||
current_hovered_id = camera_hovered_id if camera_hovered_id is not None else top_down_hovered_id
|
||||
selected_track_id = retain_selected_track_id(selected_track_id, current_hovered_id, tracks)
|
||||
|
||||
if selected_track_id is not None:
|
||||
if selected_track_id is not None and current_table_mode != "signals":
|
||||
sorted_tracks = sorted(tracks, key=lambda track: (track.dRel, track.trackId))
|
||||
hovered_row = next((index for index, track in enumerate(sorted_tracks)
|
||||
if int(track.trackId) == selected_track_id), None)
|
||||
@@ -2223,7 +2676,7 @@ def ui_thread(addr: str, start_wide: bool = False, start_fused: bool = False, st
|
||||
)
|
||||
table_mode = TABLE_MODES[table_mode_index]
|
||||
draw_track_table(font, table_rect, tracks, motion_states, table_scroll, selected_track_id, table_hover_id,
|
||||
track_signals, track_locations, table_mode)
|
||||
track_signals, track_locations, table_mode, raw_signals)
|
||||
clicked_action = draw_source_status(
|
||||
font, status_rect, selected_tracks, data_valid, data_alive,
|
||||
replay_process is not None or can_data_seen or live_data_seen, show_labels, data_source,
|
||||
|
||||
Reference in New Issue
Block a user