Skip to content

Commit

Permalink
card parses radar points (commaai#33443)
Browse files Browse the repository at this point in the history
* interfaces returns radarinterface

old-commit-hash: 9ad1f09

* bump

old-commit-hash: 20334a8

* get RI from opendbc

old-commit-hash: b5f6d0c

* stash so far

old-commit-hash: 5aa2c84

* new liveTracks message (radard expects and needs RadarData)

* this should just work?

* whoops

* fix that

* rm liveTracks from radard pm

* fix proceess replay

* lol fcw diff, something's not right

* actually there's fcw in original route. it's pretty close

* no tracks!

* fix test_leads

* CPU moved across procs

* fix not engageable from onroadEvents

* bump

* fixes

* bump to master

* radard publishes w/ modelV2 now, so it will always be sent. check valid which radard sets using liveTracks avg freq

* fix that (it works!)

* combine

join

* bump

* bump

* deprecate

* why

* fix incorrect args

* remove cumLagMs from process_replay

* update refs
  • Loading branch information
sshane authored Sep 6, 2024
1 parent aef6500 commit 922348f
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 75 deletions.
1 change: 1 addition & 0 deletions cereal/car.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ struct CarParams {
carFw @44 :List(CarFw);

radarTimeStep @45: Float32 = 0.05; # time delta between radar updates, 20Hz is very standard
radarDelay @74 :Float32;
fingerprintSource @49: FingerprintSource;
networkLocation @50 :NetworkLocation; # Where Panda/C2 is integrated into the car's CAN network

Expand Down
7 changes: 4 additions & 3 deletions cereal/log.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,6 @@ struct RadarState @0x9a185389d6fdd05f {

leadOne @3 :LeadData;
leadTwo @4 :LeadData;
cumLagMs @5 :Float32;

struct LeadData {
dRel @0 :Float32;
Expand Down Expand Up @@ -641,6 +640,7 @@ struct RadarState @0x9a185389d6fdd05f {
calCycleDEPRECATED @8 :Int32;
calPercDEPRECATED @9 :Int8;
canMonoTimesDEPRECATED @10 :List(UInt64);
cumLagMsDEPRECATED @5 :Float32;
}

struct LiveCalibrationData {
Expand Down Expand Up @@ -671,7 +671,7 @@ struct LiveCalibrationData {
}
}

struct LiveTracks {
struct LiveTracksDEPRECATED {
trackId @0 :Int32;
dRel @1 :Float32;
yRel @2 :Float32;
Expand Down Expand Up @@ -2335,7 +2335,7 @@ struct Event {
pandaStates @81 :List(PandaState);
peripheralState @80 :PeripheralState;
radarState @13 :RadarState;
liveTracks @16 :List(LiveTracks);
liveTracks @131 :Car.RadarData;
sendcan @17 :List(CanData);
liveCalibration @19 :LiveCalibrationData;
carState @22 :Car.CarState;
Expand Down Expand Up @@ -2465,5 +2465,6 @@ struct Event {
navModelDEPRECATED @104 :NavModelData;
uiPlanDEPRECATED @106 :UiPlan;
liveLocationKalmanDEPRECATED @72 :LiveLocationKalman;
liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED);
}
}
37 changes: 26 additions & 11 deletions selfdrive/car/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
from opendbc.car import DT_CTRL, carlog, structs
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
from opendbc.car.fw_versions import ObdCallback
from opendbc.car.car_helpers import get_car
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.car_helpers import get_car, get_radar_interface
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
from openpilot.selfdrive.car.cruise import VCruiseHelper
from openpilot.selfdrive.car.car_specific import CarSpecificEvents, MockCarState
Expand Down Expand Up @@ -63,13 +63,14 @@ def can_send(msgs: list[CanData]) -> None:

class Car:
CI: CarInterfaceBase
RI: RadarInterfaceBase
CP: structs.CarParams
CP_capnp: car.CarParams

def __init__(self, CI=None) -> None:
def __init__(self, CI=None, RI=None) -> None:
self.can_sock = messaging.sub_sock('can', timeout=20)
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents'])
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput'])
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks'])

self.can_rcv_cum_timeout_counter = 0

Expand Down Expand Up @@ -101,12 +102,14 @@ def __init__(self, CI=None) -> None:
cached_params = structs.CarParams(carName=_cached_params.carName, carFw=_cached_params.carFw, carVin=_cached_params.carVin)

self.CI = get_car(*self.can_callbacks, obd_callback(self.params), experimental_long_allowed, num_pandas, cached_params)
self.RI = get_radar_interface(self.CI.CP)
self.CP = self.CI.CP

# continue onto next fingerprinting step in pandad
self.params.put_bool("FirmwareQueryDone", True)
else:
self.CI, self.CP = CI, CI.CP
self.RI = RI

# set alternative experiences from parameters
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
Expand Down Expand Up @@ -149,7 +152,7 @@ def __init__(self, CI=None) -> None:
# card is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None)

def state_update(self) -> car.CarState:
def state_update(self) -> tuple[car.CarState, structs.RadarData | None]:
"""carState update loop, driven by can"""

# Update carState from CAN
Expand All @@ -159,6 +162,9 @@ def state_update(self) -> car.CarState:
if self.CP.carName == 'mock':
CS = self.mock_carstate.update(CS)

# Update radar tracks from CAN
RD: structs.RadarData | None = self.RI.update(can_capnp_to_list(can_strs))

self.sm.update(0)

can_rcv_valid = len(can_strs) > 0
Expand All @@ -175,9 +181,9 @@ def state_update(self) -> car.CarState:
CS.vCruise = float(self.v_cruise_helper.v_cruise_kph)
CS.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)

return CS
return CS, RD

def update_events(self, CS: car.CarState):
def update_events(self, CS: car.CarState, RD: structs.RadarData | None):
self.events.clear()

CS.events = self.car_events.update(self.CI.CS, self.CS_prev, self.CI.CC, self.CC_prev).to_msg()
Expand All @@ -196,9 +202,12 @@ def update_events(self, CS: car.CarState):
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)

if RD is not None and len(RD.errors):
self.events.add(EventName.radarFault)

CS.events = self.events.to_msg()

def state_publish(self, CS: car.CarState):
def state_publish(self, CS: car.CarState, RD: structs.RadarData | None):
"""carState and carParams publish loop"""

# carParams - logged every 50 seconds (> 1 per segment)
Expand All @@ -222,6 +231,12 @@ def state_publish(self, CS: car.CarState):
cs_send.carState.cumLagMs = -self.rk.remaining * 1000.
self.pm.send('carState', cs_send)

if RD is not None:
tracks_msg = messaging.new_message('liveTracks')
tracks_msg.valid = CS.canValid and len(RD.errors) == 0
tracks_msg.liveTracks = convert_to_capnp(RD)
self.pm.send('liveTracks', tracks_msg)

def controls_update(self, CS: car.CarState, CC: car.CarControl):
"""control update loop, driven by carControl"""

Expand All @@ -241,14 +256,14 @@ def controls_update(self, CS: car.CarState, CC: car.CarControl):
self.CC_prev = CC

def step(self):
CS = self.state_update()
CS, RD = self.state_update()

self.update_events(CS)
self.update_events(CS, RD)

if not self.sm['carControl'].enabled and self.events.contains(ET.ENABLE):
self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode)

self.state_publish(CS)
self.state_publish(CS, RD)

initialized = (not any(e.name == EventName.controlsInitializing for e in self.sm['onroadEvents']) and
self.sm.seen['onroadEvents'])
Expand Down
4 changes: 3 additions & 1 deletion selfdrive/car/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def asdictref(obj) -> dict[str, Any]:
return _asdictref_inner(obj)


def convert_to_capnp(struct: structs.CarParams | structs.CarState | structs.CarControl.Actuators) -> capnp.lib.capnp._DynamicStructBuilder:
def convert_to_capnp(struct: structs.CarParams | structs.CarState | structs.CarControl.Actuators | structs.RadarData) -> capnp.lib.capnp._DynamicStructBuilder:
struct_dict = asdictref(struct)

if isinstance(struct, structs.CarParams):
Expand All @@ -51,6 +51,8 @@ def convert_to_capnp(struct: structs.CarParams | structs.CarState | structs.CarC
struct_capnp = car.CarState.new_message(**struct_dict)
elif isinstance(struct, structs.CarControl.Actuators):
struct_capnp = car.CarControl.Actuators.new_message(**struct_dict)
elif isinstance(struct, structs.RadarData):
struct_capnp = car.RadarData.new_message(**struct_dict)
else:
raise ValueError(f"Unsupported struct type: {type(struct)}")

Expand Down
2 changes: 1 addition & 1 deletion selfdrive/car/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ def test_panda_safety_carstate(self):
checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev()
else:
# Check for enable events on rising edge of controls allowed
card.update_events(CS)
card.update_events(CS, None)
card.CS_prev = CS
button_enable = (any(evt.enable for evt in CS.events) and
not any(evt == EventName.pedalPressed for evt in card.events.names))
Expand Down
4 changes: 2 additions & 2 deletions selfdrive/controls/controlsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def __init__(self, CI=None):
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'testJoystick'] + self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore+['radarState', 'testJoystick'], ignore_valid=['testJoystick', ],
ignore_alive=ignore, ignore_avg_freq=ignore+['testJoystick'], ignore_valid=['testJoystick', ],
frequency=int(1/DT_CTRL))

self.joystick_mode = self.params.get_bool("JoystickDebugMode")
Expand Down Expand Up @@ -301,7 +301,7 @@ def update_events(self, CS):
self.events.add(EventName.cameraFrameRate)
if not REPLAY and self.rk.lagging:
self.events.add(EventName.controlsdLagging)
if len(self.sm['radarState'].radarErrors) or ((not self.rk.lagging or REPLAY) and not self.sm.all_checks(['radarState'])):
if not self.sm.valid['radarState']:
self.events.add(EventName.radarFault)
if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError)
Expand Down
54 changes: 12 additions & 42 deletions selfdrive/controls/radard.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
#!/usr/bin/env python3
import importlib
import math
from collections import deque
from typing import Any

import capnp
from cereal import messaging, log, car
from opendbc.car import structs
from openpilot.common.numpy_fast import interp
from openpilot.common.params import Params
from openpilot.common.realtime import DT_CTRL, Ratekeeper, Priority, config_realtime_process
from openpilot.common.realtime import DT_MDL, Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog
from openpilot.common.simple_kalman import KF1D
from openpilot.selfdrive.pandad import can_capnp_to_list


# Default lead acceleration decay set to 50% at 1s
Expand Down Expand Up @@ -194,22 +191,22 @@ def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capn


class RadarD:
def __init__(self, radar_ts: float, delay: int = 0):
def __init__(self, delay: float = 0.0):
self.current_time = 0.0

self.tracks: dict[int, Track] = {}
self.kalman_params = KalmanParams(radar_ts)
self.kalman_params = KalmanParams(DT_MDL)

self.v_ego = 0.0
self.v_ego_hist = deque([0.0], maxlen=delay+1)
self.v_ego_hist = deque([0.0], maxlen=int(round(delay / DT_MDL))+1)
self.last_v_ego_frame = -1

self.radar_state: capnp._DynamicStructBuilder | None = None
self.radar_state_valid = False

self.ready = False

def update(self, sm: messaging.SubMaster, rr: structs.RadarData):
def update(self, sm: messaging.SubMaster, rr: car.RadarData):
self.ready = sm.seen['modelV2']
self.current_time = 1e-9*max(sm.logMonoTime.values())

Expand Down Expand Up @@ -255,27 +252,14 @@ def update(self, sm: messaging.SubMaster, rr: structs.RadarData):
self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True)
self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False)

def publish(self, pm: messaging.PubMaster, lag_ms: float):
def publish(self, pm: messaging.PubMaster):
assert self.radar_state is not None

radar_msg = messaging.new_message("radarState")
radar_msg.valid = self.radar_state_valid
radar_msg.radarState = self.radar_state
radar_msg.radarState.cumLagMs = lag_ms
pm.send("radarState", radar_msg)

# publish tracks for UI debugging (keep last)
tracks_msg = messaging.new_message('liveTracks', len(self.tracks))
tracks_msg.valid = self.radar_state_valid
for index, tid in enumerate(sorted(self.tracks.keys())):
tracks_msg.liveTracks[index] = {
"trackId": tid,
"dRel": float(self.tracks[tid].dRel),
"yRel": float(self.tracks[tid].yRel),
"vRel": float(self.tracks[tid].vRel),
}
pm.send('liveTracks', tracks_msg)


# fuses camera and radar data for best lead detection
def main() -> None:
Expand All @@ -286,31 +270,17 @@ def main() -> None:
CP = messaging.log_from_bytes(Params().get("CarParams", block=True), car.CarParams)
cloudlog.info("radard got CarParams")

# import the radar from the fingerprint
cloudlog.info("radard is importing %s", CP.carName)
RadarInterface = importlib.import_module(f'opendbc.car.{CP.carName}.radar_interface').RadarInterface

# *** setup messaging
can_sock = messaging.sub_sock('can')
sm = messaging.SubMaster(['modelV2', 'carState'], frequency=int(1./DT_CTRL))
pm = messaging.PubMaster(['radarState', 'liveTracks'])
sm = messaging.SubMaster(['modelV2', 'carState', 'liveTracks'], poll='modelV2')
pm = messaging.PubMaster(['radarState'])

RI = RadarInterface(CP)

rk = Ratekeeper(1.0 / CP.radarTimeStep, print_delay_threshold=None)
RD = RadarD(CP.radarTimeStep, RI.delay)
RD = RadarD(CP.radarDelay)

while 1:
can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True)
rr: structs.RadarData | None = RI.update(can_capnp_to_list(can_strings))
sm.update(0)
if rr is None:
continue

RD.update(sm, rr)
RD.publish(pm, -rk.remaining*1000.0)
sm.update()

rk.monitor_time()
RD.update(sm, sm['liveTracks'])
RD.publish(pm)


if __name__ == "__main__":
Expand Down
10 changes: 6 additions & 4 deletions selfdrive/controls/tests/test_leads.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ def test_radar_fault(self):
def single_iter_pkg():
# single iter package, with meaningless cans and empty carState/modelV2
msgs = []
for _ in range(5):
for _ in range(500):
can = messaging.new_message("can", 1)
cs = messaging.new_message("carState")
cp = messaging.new_message("carParams")
msgs.append(can.as_reader())
msgs.append(cs.as_reader())
msgs.append(cp.as_reader())
model = messaging.new_message("modelV2")
msgs.append(model.as_reader())

return msgs

msgs = [m for _ in range(3) for m in single_iter_pkg()]
out = replay_process_with_name("radard", msgs, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2)
states = [m for m in out if m.which() == "radarState"]
failures = [not state.valid and len(state.radarState.radarErrors) for state in states]
out = replay_process_with_name("card", msgs, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2)
states = [m for m in out if m.which() == "liveTracks"]
failures = [not state.valid and len(state.liveTracks.errors) for state in states]

assert len(states) == 0 or all(failures)
Loading

0 comments on commit 922348f

Please sign in to comment.