From 811c096e6454dc2bbdd1e15a08952979c3fb7be1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 11 Nov 2022 18:53:48 -0800 Subject: [PATCH 001/185] controlsd: add cruise speed helper class (#26472) * fix runaway set speed for GM * fix runaway set speed for GM * Handle resuming to exit standstill generically * clean that up * ugh i want to fix all the formatting * class that manages v_cruise * better name * move around * add depressed_state * fine to update on pressed change, better name * revert gm stuff * revert standstill stuff * remove * revert that * we can put this in here now! * below update * actually only used here * one line --- selfdrive/controls/controlsd.py | 52 ++------- selfdrive/controls/lib/drive_helpers.py | 141 +++++++++++++++--------- 2 files changed, 102 insertions(+), 91 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 3b241045b1..f69e9e7fd1 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -16,8 +16,7 @@ from system.version import is_tested_branch, get_short_branch from selfdrive.boardd.boardd import can_list_to_can_capnp from selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can from selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET -from selfdrive.controls.lib.drive_helpers import V_CRUISE_INITIAL, update_v_cruise, initialize_v_cruise -from selfdrive.controls.lib.drive_helpers import get_lag_adjusted_curvature +from selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature from selfdrive.controls.lib.latcontrol import LatControl from selfdrive.controls.lib.longcontrol import LongControl from selfdrive.controls.lib.latcontrol_pid import LatControlPID @@ -49,7 +48,6 @@ Desire = log.LateralPlan.Desire LaneChangeState = log.LateralPlan.LaneChangeState LaneChangeDirection = log.LateralPlan.LaneChangeDirection EventName = car.CarEvent.EventName -ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel @@ -173,9 +171,6 @@ class Controls: self.active = False self.can_rcv_timeout = False self.soft_disable_timer = 0 - self.v_cruise_kph = V_CRUISE_INITIAL - self.v_cruise_cluster_kph = V_CRUISE_INITIAL - self.v_cruise_kph_last = 0 self.mismatch_counter = 0 self.cruise_mismatch_counter = 0 self.can_rcv_timeout_counter = 0 @@ -185,11 +180,11 @@ class Controls: self.events_prev = [] self.current_alert_types = [ET.PERMANENT] self.logged_comm_issue = None - self.button_timers = {ButtonEvent.Type.decelCruise: 0, ButtonEvent.Type.accelCruise: 0} self.last_actuators = car.CarControl.Actuators.new_message() self.steer_limited = False self.desired_curvature = 0.0 self.desired_curvature_rate = 0.0 + self.v_cruise_helper = VCruiseHelper(self.CP) # TODO: no longer necessary, aside from process replay self.sm['liveParameters'].valid = True @@ -219,7 +214,7 @@ class Controls: controls_state = Params().get("ReplayControlsState") if controls_state is not None: controls_state = log.ControlsState.from_bytes(controls_state) - self.v_cruise_kph = controls_state.vCruise + self.v_cruise_helper.v_cruise_kph = controls_state.vCruise if any(ps.controlsAllowed for ps in self.sm['pandaStates']): self.state = State.enabled @@ -245,7 +240,7 @@ class Controls: # Block resume if cruise never previously enabled resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents) - if not self.CP.pcmCruise and self.v_cruise_kph == V_CRUISE_INITIAL and resume_pressed: + if not self.CP.pcmCruise and not self.v_cruise_helper.v_cruise_initialized and resume_pressed: self.events.add(EventName.resumeBlocked) # Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0 @@ -478,20 +473,7 @@ class Controls: def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" - self.v_cruise_kph_last = self.v_cruise_kph - - if CS.cruiseState.available: - # if stock cruise is completely disabled, then we can use our own set speed logic - if not self.CP.pcmCruise: - self.v_cruise_kph = update_v_cruise(self.v_cruise_kph, CS.vEgo, CS.gasPressed, CS.buttonEvents, - self.button_timers, self.enabled, self.is_metric) - self.v_cruise_cluster_kph = self.v_cruise_kph - else: - self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH - self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH - else: - self.v_cruise_kph = V_CRUISE_INITIAL - self.v_cruise_cluster_kph = V_CRUISE_INITIAL + self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric) # decrement the soft disable timer at every step, as it's reset on # entrance in SOFT_DISABLING state @@ -569,9 +551,7 @@ class Controls: else: self.state = State.enabled self.current_alert_types.append(ET.ENABLE) - if not self.CP.pcmCruise: - self.v_cruise_kph = initialize_v_cruise(CS.vEgo, CS.buttonEvents, self.v_cruise_kph_last) - self.v_cruise_cluster_kph = self.v_cruise_kph + self.v_cruise_helper.initialize_v_cruise(CS) # Check if openpilot is engaged and actuators are enabled self.enabled = self.state in ENABLED_STATES @@ -619,7 +599,7 @@ class Controls: if not self.joystick_mode: # accel PID loop - pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_kph * CV.KPH_TO_MS) + pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_helper.v_cruise_kph * CV.KPH_TO_MS) t_since_plan = (self.sm.frame - self.sm.rcv_frame['longitudinalPlan']) * DT_CTRL actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan) @@ -683,16 +663,6 @@ class Controls: return CC, lac_log - def update_button_timers(self, buttonEvents): - # increment timer for buttons still pressed - for k in self.button_timers: - if self.button_timers[k] > 0: - self.button_timers[k] += 1 - - for b in buttonEvents: - if b.type.raw in self.button_timers: - self.button_timers[b.type.raw] = 1 if b.pressed else 0 - def publish_logs(self, CS, start_time, CC, lac_log): """Send actuators and hud commands to the car, send controlsstate and MPC logging""" @@ -715,7 +685,7 @@ class Controls: CC.cruiseControl.resume = self.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1 hudControl = CC.hudControl - hudControl.setSpeed = float(self.v_cruise_cluster_kph * CV.KPH_TO_MS) + hudControl.setSpeed = float(self.v_cruise_helper.v_cruise_cluster_kph * CV.KPH_TO_MS) hudControl.speedVisible = self.enabled hudControl.lanesVisible = self.enabled hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead @@ -798,8 +768,8 @@ class Controls: controlsState.engageable = not self.events.any(ET.NO_ENTRY) controlsState.longControlState = self.LoC.long_control_state controlsState.vPid = float(self.LoC.v_pid) - controlsState.vCruise = float(self.v_cruise_kph) - controlsState.vCruiseCluster = float(self.v_cruise_cluster_kph) + controlsState.vCruise = float(self.v_cruise_helper.v_cruise_kph) + controlsState.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph) controlsState.upAccelCmd = float(self.LoC.pid.p) controlsState.uiAccelCmd = float(self.LoC.pid.i) controlsState.ufAccelCmd = float(self.LoC.pid.f) @@ -880,7 +850,6 @@ class Controls: self.publish_logs(CS, start_time, CC, lac_log) self.prof.checkpoint("Sent") - self.update_button_timers(CS.buttonEvents) self.CS_prev = CS def controlsd_thread(self): @@ -889,6 +858,7 @@ class Controls: self.rk.monitor_time() self.prof.display() + def main(sm=None, pm=None, logcan=None): controls = Controls(sm, pm, logcan) controls.controlsd_thread() diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index e74be7199e..27e53d1dd3 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -22,6 +22,7 @@ CAR_ROTATION_RADIUS = 0.0 # EU guidelines MAX_LATERAL_JERK = 5.0 +ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type CRUISE_LONG_PRESS = 50 CRUISE_NEAREST_FUNC = { @@ -34,6 +35,96 @@ CRUISE_INTERVAL_SIGN = { } +class VCruiseHelper: + def __init__(self, CP): + self.CP = CP + self.v_cruise_kph = V_CRUISE_INITIAL + self.v_cruise_cluster_kph = V_CRUISE_INITIAL + self.v_cruise_kph_last = 0 + self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} + + @property + def v_cruise_initialized(self): + return self.v_cruise_kph != V_CRUISE_INITIAL + + def update_v_cruise(self, CS, enabled, is_metric): + self.v_cruise_kph_last = self.v_cruise_kph + + if CS.cruiseState.available: + if not self.CP.pcmCruise: + # if stock cruise is completely disabled, then we can use our own set speed logic + self._update_v_cruise_non_pcm(CS, enabled, is_metric) + self.v_cruise_cluster_kph = self.v_cruise_kph + self.update_button_timers(CS) + else: + self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH + self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH + else: + self.v_cruise_kph = V_CRUISE_INITIAL + self.v_cruise_cluster_kph = V_CRUISE_INITIAL + + def _update_v_cruise_non_pcm(self, CS, enabled, is_metric): + # handle button presses. TODO: this should be in state_control, but a decelCruise press + # would have the effect of both enabling and changing speed is checked after the state transition + if not enabled: + return + + long_press = False + button_type = None + + # should be CV.MPH_TO_KPH, but this causes rounding errors + v_cruise_delta = 1. if is_metric else 1.6 + + for b in CS.buttonEvents: + if b.type.raw in self.button_timers and not b.pressed: + if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS: + return # end long press + button_type = b.type.raw + break + else: + for k in self.button_timers.keys(): + if self.button_timers[k] and self.button_timers[k] % CRUISE_LONG_PRESS == 0: + button_type = k + long_press = True + break + + if button_type: + v_cruise_delta = v_cruise_delta * (5 if long_press else 1) + if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval + self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta + else: + self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] + + # If set is pressed while overriding, clip cruise speed to minimum of vEgo + if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): + self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH) + + self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX) + + def update_button_timers(self, CS): + # increment timer for buttons still pressed + for k in self.button_timers: + if self.button_timers[k] > 0: + self.button_timers[k] += 1 + + for b in CS.buttonEvents: + if b.type.raw in self.button_timers: + self.button_timers[b.type.raw] = 1 if b.pressed else 0 + + def initialize_v_cruise(self, CS): + # initializing is handled by the PCM + if self.CP.pcmCruise: + return + + # 250kph or above probably means we never had a set speed + if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_kph_last < 250: + self.v_cruise_kph = self.v_cruise_kph_last + else: + self.v_cruise_kph = int(round(clip(CS.vEgo * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX))) + + self.v_cruise_cluster_kph = self.v_cruise_kph + + def apply_deadzone(error, deadzone): if error > deadzone: error -= deadzone @@ -48,56 +139,6 @@ def rate_limit(new_value, last_value, dw_step, up_step): return clip(new_value, last_value + dw_step, last_value + up_step) -def update_v_cruise(v_cruise_kph, v_ego, gas_pressed, buttonEvents, button_timers, enabled, metric): - # handle button presses. TODO: this should be in state_control, but a decelCruise press - # would have the effect of both enabling and changing speed is checked after the state transition - if not enabled: - return v_cruise_kph - - long_press = False - button_type = None - - # should be CV.MPH_TO_KPH, but this causes rounding errors - v_cruise_delta = 1. if metric else 1.6 - - for b in buttonEvents: - if b.type.raw in button_timers and not b.pressed: - if button_timers[b.type.raw] > CRUISE_LONG_PRESS: - return v_cruise_kph # end long press - button_type = b.type.raw - break - else: - for k in button_timers.keys(): - if button_timers[k] and button_timers[k] % CRUISE_LONG_PRESS == 0: - button_type = k - long_press = True - break - - if button_type: - v_cruise_delta = v_cruise_delta * (5 if long_press else 1) - if long_press and v_cruise_kph % v_cruise_delta != 0: # partial interval - v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](v_cruise_kph / v_cruise_delta) * v_cruise_delta - else: - v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] - - # If set is pressed while overriding, clip cruise speed to minimum of vEgo - if gas_pressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): - v_cruise_kph = max(v_cruise_kph, v_ego * CV.MS_TO_KPH) - - v_cruise_kph = clip(round(v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX) - - return v_cruise_kph - - -def initialize_v_cruise(v_ego, buttonEvents, v_cruise_last): - for b in buttonEvents: - # 250kph or above probably means we never had a set speed - if b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) and v_cruise_last < 250: - return v_cruise_last - - return int(round(clip(v_ego * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX))) - - def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): if len(psis) != CONTROL_N: psis = [0.0]*CONTROL_N From 76ac3d4c99df24f068aee3baedb796f65dfcca66 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 11 Nov 2022 19:43:30 -0800 Subject: [PATCH 002/185] controlsd: resume does not increment speed while cruise standstill (#25470) * fix runaway set speed for GM * fix runaway set speed for GM * Handle resuming to exit standstill generically * clean that up * ugh i want to fix all the formatting * class that manages v_cruise * better name * move around * add depressed_state * fine to update on pressed change, better name * cmt * we need to check CS. button_change_state only works if we exit standstill on rising edge not falling edge * no defaultdict --- selfdrive/car/gm/interface.py | 8 +------- selfdrive/controls/lib/drive_helpers.py | 8 ++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 2420098b4a..49cae998e8 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -211,13 +211,7 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT: - be = create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS) - - # Suppress resume button if we're resuming from stop so we don't adjust speed. - if be.type == ButtonType.accelCruise and (ret.cruiseState.enabled and ret.standstill): - be.type = ButtonType.unknown - - ret.buttonEvents = [be] + ret.buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)] events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 27e53d1dd3..5ca5319ba5 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -42,6 +42,7 @@ class VCruiseHelper: self.v_cruise_cluster_kph = V_CRUISE_INITIAL self.v_cruise_kph_last = 0 self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} + self.button_change_state = {btn: {"standstill": False} for btn in self.button_timers} @property def v_cruise_initialized(self): @@ -88,6 +89,11 @@ class VCruiseHelper: long_press = True break + # Don't adjust speed when pressing resume to exit standstill + cruise_standstill = self.button_change_state[button_type]["standstill"] or CS.cruiseState.standstill + if button_type == ButtonType.accelCruise and cruise_standstill: + button_type = None + if button_type: v_cruise_delta = v_cruise_delta * (5 if long_press else 1) if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval @@ -109,7 +115,9 @@ class VCruiseHelper: for b in CS.buttonEvents: if b.type.raw in self.button_timers: + # Start/end timer and store current state on change of button pressed self.button_timers[b.type.raw] = 1 if b.pressed else 0 + self.button_change_state[b.type.raw].update({"standstill": CS.cruiseState.standstill}) def initialize_v_cruise(self, CS): # initializing is handled by the PCM From d3f971b365613329a35a3140b9418977baebd856 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 11 Nov 2022 20:10:12 -0800 Subject: [PATCH 003/185] controlsd v_cruise: fix unknown buttons (#26474) Fix button being None --- selfdrive/controls/lib/drive_helpers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 5ca5319ba5..e9d74c54a9 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -90,8 +90,7 @@ class VCruiseHelper: break # Don't adjust speed when pressing resume to exit standstill - cruise_standstill = self.button_change_state[button_type]["standstill"] or CS.cruiseState.standstill - if button_type == ButtonType.accelCruise and cruise_standstill: + if button_type == ButtonType.accelCruise and (self.button_change_state[button_type]["standstill"] or CS.cruiseState.standstill): button_type = None if button_type: From 5f094b836851c392b89429cc654082c659f7caec Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Sat, 12 Nov 2022 06:50:09 +0100 Subject: [PATCH 004/185] CI: regroup devices (#26436) * regroup ci devices * cleanup looopback test * split loopback devices Co-authored-by: Kurt Nistelberger --- Jenkinsfile | 27 +++++++++++++++++++------- release/files_common | 1 + selfdrive/manager/test/test_manager.py | 6 ++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0d624954ea..696446c65f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -111,7 +111,7 @@ pipeline { R3_PUSH = "${env.BRANCH_NAME == 'master' ? '1' : ' '}" } steps { - phone_steps("tici", [ + phone_steps("tici-needs-can", [ ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], @@ -122,16 +122,24 @@ pipeline { } } + stage('loopback-tests') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici-loopback", [ + ["build openpilot", "cd selfdrive/manager && ./build.py"], + ["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"], + ]) + } + } + stage('HW + Unit Tests') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici2", [ + phone_steps("tici-common", [ ["build", "cd selfdrive/manager && ./build.py"], ["test power draw", "python system/hardware/tici/test_power_draw.py"], - ["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"], ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"], - ["test sensord", "python selfdrive/sensord/tests/test_sensord.py"], ["test pigeond", "python selfdrive/sensord/tests/test_pigeond.py"], ]) } @@ -159,27 +167,32 @@ pipeline { } } - stage('sensord (LSM-C)') { + stage('sensord') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { phone_steps("tici-lsmc", [ ["build", "cd selfdrive/manager && ./build.py"], ["test sensord", "cd selfdrive/sensord/tests && python -m unittest test_sensord.py"], ]) + phone_steps("tici-bmx-lsm", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test sensord", "cd selfdrive/sensord/tests && python -m unittest test_sensord.py"], + ]) } } stage('replay') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici3", [ + phone_steps("tici-common", [ ["build", "cd selfdrive/manager && ./build.py"], ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], ]) } } - } + } } + } } diff --git a/release/files_common b/release/files_common index 26662f1ef1..a294e1e5b5 100644 --- a/release/files_common +++ b/release/files_common @@ -95,6 +95,7 @@ selfdrive/boardd/panda_comms.h selfdrive/boardd/panda_comms.cc selfdrive/boardd/set_time.py selfdrive/boardd/pandad.py +selfdrive/boardd/tests/test_boardd_loopback.py selfdrive/car/__init__.py selfdrive/car/docs_definitions.py diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 7ac2c5f506..6d4df0423a 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -4,6 +4,7 @@ import signal import time import unittest +from common.params import Params import selfdrive.manager.manager as manager from selfdrive.manager.process import DaemonProcess from selfdrive.manager.process_config import managed_processes @@ -20,6 +21,10 @@ class TestManager(unittest.TestCase): os.environ['PASSIVE'] = '0' HARDWARE.set_power_save(False) + # ensure clean CarParams + params = Params() + params.clear_all() + def tearDown(self): manager.manager_cleanup() @@ -40,6 +45,7 @@ class TestManager(unittest.TestCase): Ensure all processes exit cleanly when stopped. """ HARDWARE.set_power_save(False) + manager.manager_init() manager.manager_prepare() for p in ALL_PROCESSES: managed_processes[p].start() From a638afb98e2b9d7844c04b1ec55c79d3274bc9b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 11 Nov 2022 23:11:49 -0800 Subject: [PATCH 005/185] controlsd: add tests around cruise speed (#26478) * start to add some tests * test !pcmCruise * test !pcmCruise * better test * fix pylint * new test for making sure we adjust on falling edge of buttons --- selfdrive/controls/tests/test_cruise_speed.py | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) mode change 100644 => 100755 selfdrive/controls/tests/test_cruise_speed.py diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py old mode 100644 new mode 100755 index ca070f1c3f..72b7dddc20 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -1,10 +1,16 @@ #!/usr/bin/env python3 -import unittest import numpy as np +from parameterized import parameterized_class +import unittest + +from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MAX, V_CRUISE_ENABLE_MIN +from cereal import car from common.params import Params +from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +ButtonEvent = car.CarState.ButtonEvent +ButtonType = car.CarState.ButtonEvent.Type -from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver def run_cruise_simulation(cruise, t_end=20.): man = Maneuver( @@ -19,7 +25,7 @@ def run_cruise_simulation(cruise, t_end=20.): ) valid, output = man.evaluate() assert valid - return output[-1,3] + return output[-1, 3] class TestCruiseSpeed(unittest.TestCase): @@ -35,5 +41,57 @@ class TestCruiseSpeed(unittest.TestCase): self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s') +# TODO: test pcmCruise +@parameterized_class(('pcm_cruise',), [(False,)]) +class TestVCruiseHelper(unittest.TestCase): + def setUp(self): + self.CP = car.CarParams(pcmCruise=self.pcm_cruise) # pylint: disable=E1101 + self.v_cruise_helper = VCruiseHelper(self.CP) + + def test_adjust_speed(self): + """ + Asserts speed changes on falling edges of buttons. + """ + + self.v_cruise_helper.initialize_v_cruise(car.CarState()) + + for btn in (ButtonType.accelCruise, ButtonType.decelCruise): + initial_v_cruise = self.v_cruise_helper.v_cruise_kph + for pressed in (True, False): + CS = car.CarState(cruiseState={"available": True}) + CS.buttonEvents = [ButtonEvent(type=btn, pressed=pressed)] + + self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) + self.assertEqual(pressed, (initial_v_cruise == self.v_cruise_helper.v_cruise_kph)) + + def test_resume_in_standstill(self): + """ + Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill. + """ + + self.v_cruise_helper.initialize_v_cruise(car.CarState()) + initial_v_cruise = self.v_cruise_helper.v_cruise_kph + + for standstill in (True, False): + for pressed in (True, False): + CS = car.CarState(cruiseState={"available": True, "standstill": standstill}) + CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)] + + self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) + # speed should only update if not at standstill and button falling edge + should_equal = standstill or pressed + self.assertEqual(should_equal, (initial_v_cruise == self.v_cruise_helper.v_cruise_kph)) + + def test_initialize_v_cruise(self): + """ + Asserts allowed cruise speeds on enabling with SET + """ + + for v_ego in np.linspace(0, 100, 101): + self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=float(v_ego))) + self.assertTrue(V_CRUISE_ENABLE_MIN <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX) + self.assertTrue(self.v_cruise_helper.v_cruise_initialized) + + if __name__ == "__main__": unittest.main() From 870b79818566ab91159d23f1197183f3e6567608 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 11 Nov 2022 23:56:35 -0800 Subject: [PATCH 006/185] controlsd: clean up v_cruise updating (#26479) * clean up * clean up * clean up --- selfdrive/controls/lib/drive_helpers.py | 31 ++++++++++++++----------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index e9d74c54a9..37dacf5fb2 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -42,7 +42,7 @@ class VCruiseHelper: self.v_cruise_cluster_kph = V_CRUISE_INITIAL self.v_cruise_kph_last = 0 self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} - self.button_change_state = {btn: {"standstill": False} for btn in self.button_timers} + self.button_change_states = {btn: {"standstill": False} for btn in self.button_timers} @property def v_cruise_initialized(self): @@ -89,22 +89,25 @@ class VCruiseHelper: long_press = True break + if button_type is None: + return + # Don't adjust speed when pressing resume to exit standstill - if button_type == ButtonType.accelCruise and (self.button_change_state[button_type]["standstill"] or CS.cruiseState.standstill): - button_type = None + cruise_standstill = self.button_change_states[button_type]["standstill"] or CS.cruiseState.standstill + if button_type == ButtonType.accelCruise and cruise_standstill: + return - if button_type: - v_cruise_delta = v_cruise_delta * (5 if long_press else 1) - if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval - self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta - else: - self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] + v_cruise_delta = v_cruise_delta * (5 if long_press else 1) + if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval + self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta + else: + self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] - # If set is pressed while overriding, clip cruise speed to minimum of vEgo - if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): - self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH) + # If set is pressed while overriding, clip cruise speed to minimum of vEgo + if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): + self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH) - self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX) + self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX) def update_button_timers(self, CS): # increment timer for buttons still pressed @@ -116,7 +119,7 @@ class VCruiseHelper: if b.type.raw in self.button_timers: # Start/end timer and store current state on change of button pressed self.button_timers[b.type.raw] = 1 if b.pressed else 0 - self.button_change_state[b.type.raw].update({"standstill": CS.cruiseState.standstill}) + self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill} def initialize_v_cruise(self, CS): # initializing is handled by the PCM From 65f494d845bbfa47293ad3158a68bc38c1b4dcff Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 12 Nov 2022 02:02:30 -0800 Subject: [PATCH 007/185] GM: handle run-away set speed (#26480) * GM: Handle run-away set speed * bumpo * This is a test of both PRs combined * tempbump * Revert "tempbump" This reverts commit b73e04fca25e6a1bac889e44afac6430d5ad7c30. * Revert "This is a test of both PRs combined" This reverts commit 22cc0e6900e0f0470f14ed55e7846e4b1d570826. * fix * bump * fix that * Bump to master * fix fix --- panda | 2 +- selfdrive/car/gm/interface.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/panda b/panda index d573111268..0096d0c4fc 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d57311126860c9a87edf5b7b9f81a2a038866a26 +Subproject commit 0096d0c4fc50d199ca46c6fe40479e1547408aed diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 49cae998e8..c41aac0ae0 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -211,7 +211,12 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT: - ret.buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)] + buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)] + # Handle ACCButtons changing buttons mid-press + if self.CS.cruise_buttons != CruiseButtons.UNPRESS and self.CS.prev_cruise_buttons != CruiseButtons.UNPRESS: + buttonEvents.append(create_button_event(CruiseButtons.UNPRESS, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)) + + ret.buttonEvents = buttonEvents events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], From 7b0f7312e577754958468ff6a176077976f1d6e2 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 14 Nov 2022 04:55:46 +0800 Subject: [PATCH 008/185] Cabana: miscellaneous fixes (#26477) * update pos after adjusted margins * ts >=0 * output debug message to console * fix freq&count incorrect after replay auto loop restart replay * fix different height of play/pause * delay posting CAN message if UI thread is busy * >= * clear undo stack after saving * no space allowed in names * const referer --- tools/cabana/canmessages.cc | 11 +++++++---- tools/cabana/canmessages.h | 1 + tools/cabana/chartswidget.cc | 5 ++++- tools/cabana/detailwidget.cc | 1 + tools/cabana/mainwin.cc | 3 +++ tools/cabana/signaledit.cc | 1 + tools/cabana/videowidget.cc | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index e670ee8c94..ce0b458c96 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -44,7 +44,7 @@ QList CANMessages::findSignalValues(const QString &id, const Signal *si for (auto &evt : *evts) { if (evt->which != cereal::Event::Which::CAN) continue; - for (auto c : evt->event.getCan()) { + for (const auto &c : evt->event.getCan()) { if (bus == c.getSrc() && address == c.getAddress()) { double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), *signal); if ((flag == EQ && val == value) || (flag == LT && val < value) || (flag == GT && val > value)) { @@ -65,6 +65,7 @@ void CANMessages::process(QHash *messages) { emit updated(); emit msgsReceived(messages); delete messages; + processing = false; } bool CANMessages::eventFilter(const Event *event) { @@ -78,7 +79,7 @@ bool CANMessages::eventFilter(const Event *event) { } double current_sec = replay->currentSeconds(); - if (counters_begin_sec == 0) { + if (counters_begin_sec == 0 || counters_begin_sec >= current_sec) { counters.clear(); counters_begin_sec = current_sec; } @@ -105,7 +106,9 @@ bool CANMessages::eventFilter(const Event *event) { } double ts = millis_since_boot(); - if ((ts - prev_update_ts) > (1000.0 / settings.fps)) { + if ((ts - prev_update_ts) > (1000.0 / settings.fps) && !processing) { + // delay posting CAN message if UI thread is busy + processing = true; prev_update_ts = ts; // use pointer to avoid data copy in queued connection. emit received(new_msgs.release()); @@ -120,7 +123,7 @@ const std::deque CANMessages::messages(const QString &id) { } void CANMessages::seekTo(double ts) { - replay->seekTo(ts, false); + replay->seekTo(std::max(double(0), ts), false); counters_begin_sec = 0; } diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index 5ee33bce0d..c52ffdee04 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -63,6 +63,7 @@ protected: Replay *replay = nullptr; std::mutex lock; std::atomic counters_begin_sec = 0; + std::atomic processing = false; QHash counters; QHash> received_msgs; }; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 875ed80ac5..220685a86b 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -192,6 +192,8 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) chart->createDefaultAxes(); chart->legend()->hide(); chart->layout()->setContentsMargins(0, 0, 0, 0); + // top margin for title + chart->setMargins({0, 11, 0, 0}); line_marker = new QGraphicsLineItem(chart); line_marker->setZValue(chart->zValue() + 10); @@ -265,6 +267,7 @@ void ChartView::adjustChartMargins() { if (chart()->plotArea().left() != aligned_pos) { const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left(); chart()->setMargins(QMargins(left_margin, 11, 0, 0)); + updateLineMarker(can->currentSec()); } } @@ -290,7 +293,7 @@ void ChartView::updateSeries(const std::pair range) { double end_ns = (route_start_time + range.second) * 1e9; for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) { if ((*it)->which == cereal::Event::Which::CAN) { - for (auto c : (*it)->event.getCan()) { + for (const auto &c : (*it)->event.getCan()) { if (bus == c.getSrc() && address == c.getAddress()) { auto dat = c.getDat(); double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal); diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 25c3a528f9..00145395fd 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -266,6 +266,7 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title form_layout->addRow("ID", new QLabel(msg_id)); name_edit = new QLineEdit(title, this); + name_edit->setValidator(new QRegExpValidator(QRegExp("^(\\w+)"), name_edit)); form_layout->addRow(tr("Name"), name_edit); size_spin = new QSpinBox(this); diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 40a99a7a56..97d62cb4f4 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,5 +1,6 @@ #include "tools/cabana/mainwin.h" +#include #include #include #include @@ -20,6 +21,7 @@ static MainWindow *main_win = nullptr; void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl; if (main_win) emit main_win->showMessage(msg, 0); } @@ -192,6 +194,7 @@ void MainWindow::saveDBCToFile() { QFile file(file_name); if (file.open(QIODevice::WriteOnly)) file.write(dbc()->generateDBC().toUtf8()); + detail_widget->undo_stack->clear(); } } diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 1cf9b8ae57..d96587e406 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -16,6 +16,7 @@ SignalForm::SignalForm(QWidget *parent) : QWidget(parent) { QFormLayout *form_layout = new QFormLayout(this); name = new QLineEdit(); + name->setValidator(new QRegExpValidator(QRegExp("^(\\w+)"), name)); form_layout->addRow(tr("Name"), name); size = new QSpinBox(); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index d5e640b5f7..d85b23b7e6 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -37,7 +37,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { // btn controls QHBoxLayout *control_layout = new QHBoxLayout(); play_btn = new QPushButton("⏸"); - play_btn->setStyleSheet("font-weight:bold"); + play_btn->setStyleSheet("font-weight:bold; height:16px"); control_layout->addWidget(play_btn); QButtonGroup *group = new QButtonGroup(this); From f924e797b745bf3a93201de85f807a0d101eaa0e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 14 Nov 2022 04:56:05 +0800 Subject: [PATCH 009/185] Cabana: remove bus time from can message (#26475) remove bustime --- tools/cabana/canmessages.cc | 1 - tools/cabana/canmessages.h | 1 - 2 files changed, 2 deletions(-) diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index ce0b458c96..3bcaae4bbd 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -95,7 +95,6 @@ bool CANMessages::eventFilter(const Event *event) { } CanData &data = list.emplace_front(); data.ts = current_sec; - data.bus_time = c.getBusTime(); data.dat.append((char *)c.getDat().begin(), c.getDat().size()); data.count = ++counters[id]; diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index c52ffdee04..ff41edad54 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -16,7 +16,6 @@ struct CanData { double ts = 0.; uint32_t count = 0; uint32_t freq = 0; - uint16_t bus_time = 0; QByteArray dat; }; From 3d208567f34bac3f7f910c8f0343d9472b67bf6c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 14 Nov 2022 04:56:26 +0800 Subject: [PATCH 010/185] Cabana: added color labels to signal list (#26485) add color label --- tools/cabana/signaledit.cc | 55 +++++++++++++++++++++++++++----------- tools/cabana/signaledit.h | 5 ++-- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index d96587e406..8737154c16 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -71,22 +71,40 @@ SignalForm::SignalForm(QWidget *parent) : QWidget(parent) { SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); // title bar - auto toolbar = new QToolBar(this); - toolbar->setStyleSheet("QToolButton {width:15px;height:15px;font-size:15px}"); - icon = new QLabel(); - toolbar->addWidget(icon); + auto title_bar = new QWidget(this); + title_bar->setFixedHeight(32); + QHBoxLayout *title_layout = new QHBoxLayout(title_bar); + title_layout->setContentsMargins(0, 0, 0, 0); + title_bar->setStyleSheet("QToolButton {width:15px;height:15px;font-size:15px}"); + color_label = new QLabel(this); + color_label->setFixedWidth(25); + color_label->setContentsMargins(5, 0, 0, 0); + title_layout->addWidget(color_label); + icon = new QLabel(this); + title_layout->addWidget(icon); title = new ElidedLabel(this); title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index))); - toolbar->addWidget(title); - plot_btn = toolbar->addAction("", [this]() { emit showChart(msg_id, sig, !chart_opened); }); - auto seek_btn = toolbar->addAction(QIcon::fromTheme("edit-find"), "", [this]() { SignalFindDlg(msg_id, sig, this).exec(); }); + title_layout->addWidget(title); + + plot_btn = new QToolButton(this); + plot_btn->setText("📈"); + plot_btn->setCheckable(true); + plot_btn->setAutoRaise(true); + title_layout->addWidget(plot_btn); + auto seek_btn = new QToolButton(this); + seek_btn->setIcon(QIcon::fromTheme("edit-find")); + seek_btn->setAutoRaise(true); seek_btn->setToolTip(tr("Find signal values")); - auto remove_btn = toolbar->addAction("x", [this]() { emit remove(sig); }); + title_layout->addWidget(seek_btn); + auto remove_btn = new QToolButton(this); + remove_btn->setAutoRaise(true); + remove_btn->setText("x"); remove_btn->setToolTip(tr("Remove signal")); - main_layout->addWidget(toolbar); + title_layout->addWidget(remove_btn); + main_layout->addWidget(title_bar); // signal form form = new SignalForm(this); @@ -99,8 +117,11 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa hline->setFrameShadow(QFrame::Sunken); main_layout->addWidget(hline); - QObject::connect(form, &SignalForm::changed, this, &SignalEdit::saveSignal); QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); + QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked); }); + QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); }); + QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); }); + QObject::connect(form, &SignalForm::changed, this, &SignalEdit::saveSignal); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -108,7 +129,9 @@ void SignalEdit::setSignal(const QString &message_id, const Signal *signal) { sig = signal; updateForm(msg_id == message_id && form->isVisible()); msg_id = message_id; - title->setText(QString("%1. %2").arg(form_idx + 1).arg(sig->name.c_str())); + color_label->setText(QString::number(form_idx + 1)); + color_label->setStyleSheet(QString("background-color:%1").arg(getColor(form_idx))); + title->setText(sig->name.c_str()); show(); } @@ -145,9 +168,8 @@ void SignalEdit::saveSignal() { } void SignalEdit::setChartOpened(bool opened) { - plot_btn->setText(opened ? "☒" : "📈"); plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot")); - chart_opened = opened; + plot_btn->setChecked(opened); } void SignalEdit::updateForm(bool visible) { @@ -175,8 +197,9 @@ void SignalEdit::showFormClicked() { } void SignalEdit::signalHovered(const Signal *s) { - auto color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx)); - title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color.name())); + auto bg_color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx)); + auto color = sig == s ? "white" : "black"; + color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(bg_color.name())); } void SignalEdit::hideEvent(QHideEvent *event) { diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index 46ea1bfbe0..335e49a869 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -54,10 +53,10 @@ protected: SignalForm *form = nullptr; ElidedLabel *title; + QLabel *color_label; QLabel *icon; int form_idx = 0; - bool chart_opened = false; - QAction *plot_btn; + QToolButton *plot_btn; }; class SignalFindDlg : public QDialog { From 4ef941e954198a6ed77b4d1cf9389cd621da6aac Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 14 Nov 2022 04:56:48 +0800 Subject: [PATCH 011/185] Cabana: update detail view on change (#26476) update on changed --- tools/cabana/detailwidget.cc | 7 ++++--- tools/cabana/detailwidget.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 00145395fd..260c9dfec7 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -89,7 +89,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); - QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); + QObject::connect(can, &CANMessages::msgsReceived, this, &DetailWidget::updateState); QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); }); QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu); QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { @@ -179,9 +179,10 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { QTimer::singleShot(1, [this]() { setUpdatesEnabled(true); }); } -void DetailWidget::updateState() { +void DetailWidget::updateState(const QHash * msgs) { time_label->setText(QString::number(can->currentSec(), 'f', 3)); - if (msg_id.isEmpty()) return; + if (!msgs->contains(msg_id)) + return; binary_view->updateState(); history_log->updateState(); diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 815afa9bce..5fc6d122fe 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -36,7 +36,7 @@ private: void removeSignal(const Signal *sig); void editMsg(); void removeMsg(); - void updateState(); + void updateState(const QHash * msgs); QString msg_id; QLabel *name_label, *time_label, *warning_label; From 3524cc7f0bbb94ccb4d6b7b0504692d23288a05f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 13 Nov 2022 16:15:17 -0800 Subject: [PATCH 012/185] cruise speed tests: match controlsd initialization of cruise speed (#26491) * helper functions and fix resetting/enabling * comment (resume not yet tested) * make it clear how this resets * this is fine to enable once * same here --- selfdrive/controls/tests/test_cruise_speed.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py index 72b7dddc20..396ff2b46f 100755 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -5,6 +5,7 @@ import unittest from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MAX, V_CRUISE_ENABLE_MIN from cereal import car +from common.conversions import Conversions as CV from common.params import Params from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver @@ -47,40 +48,48 @@ class TestVCruiseHelper(unittest.TestCase): def setUp(self): self.CP = car.CarParams(pcmCruise=self.pcm_cruise) # pylint: disable=E1101 self.v_cruise_helper = VCruiseHelper(self.CP) + self.reset_cruise_speed_state() + + def reset_cruise_speed_state(self): + # Two resets previous cruise speed + for _ in range(2): + self.v_cruise_helper.update_v_cruise(car.CarState(cruiseState={"available": False}), enabled=False, is_metric=False) + + def enable(self, v_ego): + # Simulates user pressing set with a current speed + self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego)) def test_adjust_speed(self): """ Asserts speed changes on falling edges of buttons. """ - self.v_cruise_helper.initialize_v_cruise(car.CarState()) + self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS) for btn in (ButtonType.accelCruise, ButtonType.decelCruise): - initial_v_cruise = self.v_cruise_helper.v_cruise_kph for pressed in (True, False): CS = car.CarState(cruiseState={"available": True}) CS.buttonEvents = [ButtonEvent(type=btn, pressed=pressed)] self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) - self.assertEqual(pressed, (initial_v_cruise == self.v_cruise_helper.v_cruise_kph)) + self.assertEqual(pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) def test_resume_in_standstill(self): """ Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill. """ - self.v_cruise_helper.initialize_v_cruise(car.CarState()) - initial_v_cruise = self.v_cruise_helper.v_cruise_kph + self.enable(0) for standstill in (True, False): for pressed in (True, False): CS = car.CarState(cruiseState={"available": True, "standstill": standstill}) CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)] - self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) + # speed should only update if not at standstill and button falling edge should_equal = standstill or pressed - self.assertEqual(should_equal, (initial_v_cruise == self.v_cruise_helper.v_cruise_kph)) + self.assertEqual(should_equal, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) def test_initialize_v_cruise(self): """ @@ -88,7 +97,10 @@ class TestVCruiseHelper(unittest.TestCase): """ for v_ego in np.linspace(0, 100, 101): - self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=float(v_ego))) + self.reset_cruise_speed_state() + self.assertFalse(self.v_cruise_helper.v_cruise_initialized) + + self.enable(float(v_ego)) self.assertTrue(V_CRUISE_ENABLE_MIN <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX) self.assertTrue(self.v_cruise_helper.v_cruise_initialized) From e46063086f079eff6292a32ae9aeeafa3aa523a3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 13 Nov 2022 16:34:26 -0800 Subject: [PATCH 013/185] controlsd: no speed increment if enabled on button rising edge (#26490) * don't increment speed if we enabled on rising edge * more realistic test --- selfdrive/controls/lib/drive_helpers.py | 10 ++++++--- selfdrive/controls/tests/test_cruise_speed.py | 21 ++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 37dacf5fb2..09550de7bb 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -56,7 +56,7 @@ class VCruiseHelper: # if stock cruise is completely disabled, then we can use our own set speed logic self._update_v_cruise_non_pcm(CS, enabled, is_metric) self.v_cruise_cluster_kph = self.v_cruise_kph - self.update_button_timers(CS) + self.update_button_timers(CS, enabled) else: self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH @@ -97,6 +97,10 @@ class VCruiseHelper: if button_type == ButtonType.accelCruise and cruise_standstill: return + # Don't adjust speed if we've enabled since the button was depressed (some ports enable on rising edge) + if not self.button_change_states[button_type]["enabled"]: + return + v_cruise_delta = v_cruise_delta * (5 if long_press else 1) if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta @@ -109,7 +113,7 @@ class VCruiseHelper: self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX) - def update_button_timers(self, CS): + def update_button_timers(self, CS, enabled): # increment timer for buttons still pressed for k in self.button_timers: if self.button_timers[k] > 0: @@ -119,7 +123,7 @@ class VCruiseHelper: if b.type.raw in self.button_timers: # Start/end timer and store current state on change of button pressed self.button_timers[b.type.raw] = 1 if b.pressed else 0 - self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill} + self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled} def initialize_v_cruise(self, CS): # initializing is handled by the PCM diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py index 396ff2b46f..3d6f55931e 100755 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -74,6 +74,25 @@ class TestVCruiseHelper(unittest.TestCase): self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) self.assertEqual(pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + def test_rising_edge_enable(self): + """ + Some car interfaces may enable on rising edge of a button, + ensure we don't adjust speed if enabled changes mid-press. + """ + + # NOTE: enabled is always one frame behind the result from button press in controlsd + for enabled, pressed in ((False, False), + (False, True), + (True, False)): + CS = car.CarState(cruiseState={"available": True}) + CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=pressed)] + self.v_cruise_helper.update_v_cruise(CS, enabled=enabled, is_metric=False) + if pressed: + self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS) + + # Expected diff on enabling. Speed should not change on falling edge of pressed + self.assertEqual(not pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + def test_resume_in_standstill(self): """ Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill. @@ -93,7 +112,7 @@ class TestVCruiseHelper(unittest.TestCase): def test_initialize_v_cruise(self): """ - Asserts allowed cruise speeds on enabling with SET + Asserts allowed cruise speeds on enabling with SET. """ for v_ego in np.linspace(0, 100, 101): From ca37d0c8cc43a88189c2a96b02aed0edfe5aa32e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 13 Nov 2022 20:50:28 -0800 Subject: [PATCH 014/185] interfaces: disable on falling edge of cancel button (#26493) * both * Update ref_commit --- selfdrive/car/interfaces.py | 4 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 982ba40b17..8e8872a539 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -250,8 +250,8 @@ class CarInterfaceBase(ABC): # Enable OP long on falling edge of enable buttons (defaults to accelCruise and decelCruise, overridable per-port) if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed): events.add(EventName.buttonEnable) - # Disable on rising edge of cancel for both stock and OP long - if b.type == ButtonType.cancel and b.pressed: + # Disable on rising and falling edge of cancel for both stock and OP long + if b.type == ButtonType.cancel: events.add(EventName.buttonCancel) # Handle permanent and temporary steering faults diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 2ff423a84a..55788155b5 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -b5c833a8f5b3e6202a52746fc16809c7b649d591 +2ac5ecc79218aad0319e02218b050319a180e957 From 15383d30161de7646edf770153b0f81d15338c54 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 13 Nov 2022 21:13:46 -0800 Subject: [PATCH 015/185] GM: match stock enabling behavior (#26442) * add first draft enable button timeout * use allow_enable * it doesn't count if brakePressed * enable on rising edge of resume (matches stock) * not today * add comment describing the fault this avoids * cleaner * handle incrementing speed * rename * add test for not changing speed if enabled changes mid-press * spacey * ugh * bumpo * need this to fix a fault (draft) * already have * this should be cancel * fine to do for all * fine to do for all * bump * bumpo * bump to master * Update selfdrive/car/gm/interface.py * Update selfdrive/car/gm/interface.py * Update ref_commit --- panda | 2 +- selfdrive/car/gm/interface.py | 6 +++++- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/panda b/panda index 0096d0c4fc..c0632cd32b 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 0096d0c4fc50d199ca46c6fe40479e1547408aed +Subproject commit c0632cd32b78279dfbb3ce28c7a10ded8090d40f diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index c41aac0ae0..eb5ab7329a 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -218,9 +218,13 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = buttonEvents + # The ECM allows enabling on falling edge of set, but only rising edge of resume events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], - pcm_enable=self.CP.pcmCruise) + pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,)) + if not self.CP.pcmCruise: + if any(b.type == ButtonType.accelCruise and b.pressed for b in ret.buttonEvents): + events.add(EventName.buttonEnable) # Enabling at a standstill with brake is allowed # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 55788155b5..f35e23d45f 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -2ac5ecc79218aad0319e02218b050319a180e957 +aa2d370836588fd80b648dbed8d156765ec804d5 From 102669a232824c309dae269cfbdf763af5214da8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 13 Nov 2022 22:28:27 -0800 Subject: [PATCH 016/185] compressed vipc: fix client connect --- tools/camerastream/compressed_vipc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/camerastream/compressed_vipc.py b/tools/camerastream/compressed_vipc.py index 42a416985e..cab11493f2 100755 --- a/tools/camerastream/compressed_vipc.py +++ b/tools/camerastream/compressed_vipc.py @@ -91,9 +91,14 @@ def main(addr, cams, nvidia=False): vipc_server.create_buffers(vst, 4, False, W, H) vipc_server.start_listener() + procs = [] for k, v in cams.items(): - multiprocessing.Process(target=decoder, args=(addr, k, vipc_server, v, nvidia)).start() + p = multiprocessing.Process(target=decoder, args=(addr, k, vipc_server, v, nvidia)) + p.start() + procs.append(p) + for p in procs: + p.join() if __name__ == "__main__": parser = argparse.ArgumentParser(description="Decode video streams and broadcast on VisionIPC") From 29f24c7491d74ff2d2f7ca12e9a085dbe91c93fb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 15 Nov 2022 02:04:14 +0800 Subject: [PATCH 017/185] Cabana: Fix title overlapping on chart with long names (#26494) fix title overlapping --- tools/cabana/chartswidget.cc | 6 +----- tools/cabana/chartswidget.h | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 220685a86b..06387b3585 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -207,7 +207,6 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) item_group->setZValue(chart->zValue() + 10); // title - msg_title = new QGraphicsTextItem(chart); QToolButton *remove_btn = new QToolButton(); remove_btn->setText("X"); remove_btn->setAutoRaise(true); @@ -236,13 +235,11 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) void ChartView::resizeEvent(QResizeEvent *event) { QChartView::resizeEvent(event); - msg_title->setPos(11, 6); close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8); } void ChartView::updateTitle() { - chart()->setTitle(signal->name.c_str()); - msg_title->setHtml(tr("%1 %2").arg(dbc()->msg(id)->name).arg(id)); + chart()->setTitle(tr("%1 %2 %3").arg(dbc()->msg(id)->name).arg(id).arg(signal->name.c_str())); } void ChartView::updateFromSettings() { @@ -250,7 +247,6 @@ void ChartView::updateFromSettings() { chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); auto color = chart()->titleBrush().color(); line_marker->setPen(QPen(color, 2)); - msg_title->setDefaultTextColor(color); } void ChartView::setRange(double min, double max, bool force_update) { diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index e32a6697ce..20c673a757 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -44,7 +44,7 @@ private: QGraphicsItemGroup *item_group; QGraphicsLineItem *line_marker, *track_line; QGraphicsEllipseItem *track_ellipse; - QGraphicsTextItem *value_text, *msg_title; + QGraphicsTextItem *value_text; QGraphicsProxyWidget *close_btn_proxy; QVector vals; }; From ae40774425872b9d680448a41e2835c017c48e6c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 15 Nov 2022 02:13:16 +0800 Subject: [PATCH 018/185] Cabana: add test case for parsing can messages (#26495) * add test case for parsing can messages * require size equal * cleanup --- tools/cabana/tests/test_cabana.cc | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index ee4a581529..586422ffc8 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -1,6 +1,12 @@ +#include "opendbc/can/common.h" +#undef INFO #include "catch2/catch.hpp" #include "tools/cabana/dbcmanager.h" +#include "tools/replay/logreader.h" + +// demo route, first segment +const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2"; TEST_CASE("DBCManager::generateDBC") { DBCManager dbc_origin(nullptr); @@ -20,3 +26,42 @@ TEST_CASE("DBCManager::generateDBC") { REQUIRE(sig == new_m.sigs[name]); } } + +TEST_CASE("Parse can messages") { + DBCManager dbc(nullptr); + dbc.open("toyota_new_mc_pt_generated"); + CANParser can_parser(0, "toyota_new_mc_pt_generated", {}, {}); + + LogReader log; + REQUIRE(log.load(TEST_RLOG_URL, nullptr, {}, true)); + REQUIRE(log.events.size() > 0); + for (auto e : log.events) { + if (e->which == cereal::Event::Which::CAN) { + std::map, std::vector> values_1; + for (const auto &c : e->event.getCan()) { + const auto msg = dbc.msg(c.getAddress()); + if (c.getSrc() == 0 && msg) { + for (auto &[name, sig] : msg->sigs) { + double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), sig); + values_1[{c.getAddress(), name.toStdString()}].push_back(val); + } + } + } + + can_parser.UpdateCans(e->mono_time, e->event.getCan()); + auto values_2 = can_parser.query_latest(); + for (auto &[key, v1] : values_1) { + bool found = false; + for (auto &v2 : values_2) { + if (v2.address == key.first && v2.name == key.second) { + REQUIRE(v2.all_values.size() == v1.size()); + REQUIRE(v2.all_values == v1); + found = true; + break; + } + } + REQUIRE(found); + } + } + } +} From f15d169289c0d806a49af123678bd49b8b3ba4f1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 14 Nov 2022 11:01:01 -0800 Subject: [PATCH 019/185] controlsd cruise speed: fix missing initial value --- selfdrive/controls/lib/drive_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 09550de7bb..bdbdb7023a 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -42,7 +42,7 @@ class VCruiseHelper: self.v_cruise_cluster_kph = V_CRUISE_INITIAL self.v_cruise_kph_last = 0 self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} - self.button_change_states = {btn: {"standstill": False} for btn in self.button_timers} + self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers} @property def v_cruise_initialized(self): From 41e5c79948216af2d416dcea2bceb1ba53855022 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 14 Nov 2022 15:04:59 -0500 Subject: [PATCH 020/185] =?UTF-8?q?VW=20MQB:=20Add=20missing=20FW=20for=20?= =?UTF-8?q?2016=20=C5=A0koda=20Superb=20(#26484)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * VW MQB: Add missing FW for 2016 Škoda Superb * regen CARS.md --- docs/CARS.md | 2 +- selfdrive/car/volkswagen/values.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 32da6126bd..b8a313386d 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -150,7 +150,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| -|Škoda|Superb 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index babaffbcbe..cb2343e08f 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -233,7 +233,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"), CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), - CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-18"), + CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"), CAR.SKODA_OCTAVIA_MK3: [ VWCarInfo("Škoda Octavia 2015, 2018-19"), VWCarInfo("Škoda Octavia RS 2016"), @@ -1064,6 +1064,7 @@ FW_VERSIONS = { }, CAR.SKODA_SUPERB_MK3: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906026ET\xf1\x891343', b'\xf1\x8704L906026FP\xf1\x891196', b'\xf1\x8704L906026KB\xf1\x894071', b'\xf1\x8704L906026KD\xf1\x894798', @@ -1074,9 +1075,11 @@ FW_VERSIONS = { b'\xf1\x870CW300042H \xf1\x891601', b'\xf1\x870D9300011T \xf1\x894801', b'\xf1\x870D9300012 \xf1\x894940', + b'\xf1\x870D9300041H \xf1\x894905', b'\xf1\x870GC300043 \xf1\x892301', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111', b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111', b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\022111200111121001121110012211292221111', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', From fbf2f3816b18321ed921cf03042327134be68edb Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Mon, 14 Nov 2022 15:02:26 -0800 Subject: [PATCH 021/185] [locationd] Add input checks (#26460) * add input checks with same decay as reset_tracker * add observation timings check * typo * bugfix * improve offline locationd visibility * sbugfix offline lld Co-authored-by: Adeeb Shihadeh --- selfdrive/locationd/liblocationd.cc | 12 +++++ selfdrive/locationd/locationd.cc | 75 ++++++++++++++++++++++++++--- selfdrive/locationd/locationd.h | 9 +++- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/selfdrive/locationd/liblocationd.cc b/selfdrive/locationd/liblocationd.cc index 49404668a4..da57fb7ff4 100755 --- a/selfdrive/locationd/liblocationd.cc +++ b/selfdrive/locationd/liblocationd.cc @@ -26,4 +26,16 @@ extern "C" { memcpy(std_buff, stdev.data(), sizeof(double) * stdev.size()); } + bool is_gps_ok(Localizer *localizer){ + return localizer->is_gps_ok(); + } + + bool are_inputs_ok(Localizer *localizer){ + return localizer->are_inputs_ok(); + } + + void observation_timings_invalid_reset(Localizer *localizer){ + localizer->observation_timings_invalid_reset(); + } + } diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 8d9e247655..4325900c0e 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -19,6 +19,9 @@ const double VALID_TIME_SINCE_RESET = 1.0; // s const double VALID_POS_STD = 50.0; // m const double MAX_RESET_TRACKER = 5.0; const double SANE_GPS_UNCERTAINTY = 1500.0; // m +const double INPUT_INVALID_THRESHOLD = 5.0; // same as reset tracker +const double DECAY = 0.99995; // same as reset tracker +const double MAX_FILTER_REWIND_TIME = 0.8; // s // TODO: GPS sensor time offsets are empirically calculated // They should be replaced with synced time from a real clock @@ -200,6 +203,14 @@ VectorXd Localizer::get_stdev() { return this->kf->get_P().diagonal().array().sqrt(); } +bool Localizer::are_inputs_ok() { + return this->critical_services_valid(this->observation_values_invalid) && !this->observation_timings_invalid; +} + +void Localizer::observation_timings_invalid_reset(){ + this->observation_timings_invalid = false; +} + void Localizer::handle_sensor(double current_time, const cereal::SensorEventData::Reader& log) { // TODO does not yet account for double sensor readings in the log @@ -209,10 +220,15 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData } double sensor_time = 1e-9 * log.getTimestamp(); - + // sensor time and log time should be close if (std::abs(current_time - sensor_time) > 0.1) { LOGE("Sensor reading ignored, sensor timestamp more than 100ms off from log time"); + this->observation_timings_invalid = true; + return; + } + else if (!this->is_timestamp_valid(sensor_time)) { + this->observation_timings_invalid = true; return; } @@ -227,6 +243,10 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData auto meas = Vector3d(-v[2], -v[1], -v[0]); if (meas.norm() < ROTATION_SANITY_CHECK) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_GYRO, { meas }); + this->observation_values_invalid["gyroscope"] *= DECAY; + } + else{ + this->observation_values_invalid["gyroscope"] += 1.0; } } @@ -242,6 +262,10 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData auto meas = Vector3d(-v[2], -v[1], -v[0]); if (meas.norm() < ACCEL_SANITY_CHECK) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_ACCEL, { meas }); + this->observation_values_invalid["accelerometer"] *= DECAY; + } + else{ + this->observation_values_invalid["accelerometer"] += 1.0; } } } @@ -335,8 +359,14 @@ void Localizer::handle_car_state(double current_time, const cereal::CarState::Re void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log) { VectorXd rot_device = this->device_from_calib * floatlist2vector(log.getRot()); VectorXd trans_device = this->device_from_calib * floatlist2vector(log.getTrans()); + + if (!this->is_timestamp_valid(current_time)) { + this->observation_timings_invalid = true; + return; + } if ((rot_device.norm() > ROTATION_SANITY_CHECK) || (trans_device.norm() > TRANS_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } @@ -344,10 +374,12 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry VectorXd trans_calib_std = floatlist2vector(log.getTransStd()); if ((rot_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK) || (trans_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } if ((rot_calib_std.norm() > 10 * ROTATION_SANITY_CHECK) || (trans_calib_std.norm() > 10 * TRANS_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } @@ -363,12 +395,19 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry { rot_device }, { rot_device_cov }); this->kf->predict_and_observe(current_time, OBSERVATION_CAMERA_ODO_TRANSLATION, { trans_device }, { trans_device_cov }); + this->observation_values_invalid["cameraOdometry"] *= DECAY; } void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log) { + if (!this->is_timestamp_valid(current_time)) { + this->observation_timings_invalid = true; + return; + } + if (log.getRpyCalib().size() > 0) { auto live_calib = floatlist2vector(log.getRpyCalib()); if ((live_calib.minCoeff() < -CALIB_RPY_SANITY_CHECK) || (live_calib.maxCoeff() > CALIB_RPY_SANITY_CHECK)) { + this->observation_values_invalid["liveCalibration"] += 1.0; return; } @@ -376,6 +415,7 @@ void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibra this->device_from_calib = euler2rot(this->calib); this->calib_from_device = this->device_from_calib.transpose(); this->calibrated = log.getCalStatus() == 1; + this->observation_values_invalid["liveCalibration"] *= DECAY; } } @@ -407,8 +447,8 @@ void Localizer::time_check(double current_time) { void Localizer::update_reset_tracker() { // reset tracker is tuned to trigger when over 1reset/10s over 2min period - if (this->isGpsOK()) { - this->reset_tracker *= .99995; + if (this->is_gps_ok()) { + this->reset_tracker *= DECAY; } else { this->reset_tracker = 0.0; } @@ -483,10 +523,28 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build return msg_builder.toBytes(); } -bool Localizer::isGpsOK() { +bool Localizer::is_gps_ok() { return this->gps_valid; } +bool Localizer::critical_services_valid(std::map critical_services) { + for (auto &kv : critical_services){ + if (kv.second >= INPUT_INVALID_THRESHOLD){ + return false; + } + } + return true; +} + +bool Localizer::is_timestamp_valid(double current_time) { + double filter_time = this->kf->get_filter_time(); + if (!std::isnan(filter_time) && ((filter_time - current_time) > MAX_FILTER_REWIND_TIME)) { + LOGE("Observation timestamp is older than the max rewind threshold of the filter"); + return false; + } + return true; +} + void Localizer::determine_gps_mode(double current_time) { // 1. If the pos_std is greater than what's not acceptable and localizer is in gps-mode, reset to no-gps-mode // 2. If the pos_std is greater than what's not acceptable and localizer is in no-gps-mode, fake obs @@ -521,10 +579,15 @@ int Localizer::locationd_thread() { uint64_t cnt = 0; bool filterInitialized = false; + const std::vector critical_input_services = {"cameraOdometry", "liveCalibration", "accelerometer", "gyroscope"}; + for (std::string service : critical_input_services) { + this->observation_values_invalid.insert({service, 0.0}); + } while (!do_exit) { sm.update(); if (filterInitialized){ + this->observation_timings_invalid_reset(); for (const char* service : service_list) { if (sm.updated(service) && sm.valid(service)){ const cereal::Event::Reader log = sm[service]; @@ -538,8 +601,8 @@ int Localizer::locationd_thread() { // 100Hz publish for notcars, 20Hz for cars const char* trigger_msg = sm["carParams"].getCarParams().getNotCar() ? "accelerometer" : "cameraOdometry"; if (sm.updated(trigger_msg)) { - bool inputsOK = sm.allAliveAndValid(); - bool gpsOK = this->isGpsOK(); + bool inputsOK = sm.allAliveAndValid() && this->are_inputs_ok(); + bool gpsOK = this->is_gps_ok(); bool sensorsOK = sm.allAliveAndValid({"accelerometer", "gyroscope"}); MessageBuilder msg_builder; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index d6bb5347c5..f0872d9f56 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "cereal/messaging/messaging.h" @@ -32,8 +33,12 @@ public: void finite_check(double current_time = NAN); void time_check(double current_time = NAN); void update_reset_tracker(); - bool isGpsOK(); + bool is_gps_ok(); + bool critical_services_valid(std::map critical_services); + bool is_timestamp_valid(double current_time); void determine_gps_mode(double current_time); + bool are_inputs_ok(); + void observation_timings_invalid_reset(); kj::ArrayPtr get_message_bytes(MessageBuilder& msg_builder, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid); @@ -73,4 +78,6 @@ private: bool gps_mode = false; bool gps_valid = false; bool ublox_available = true; + bool observation_timings_invalid = false; + std::map observation_values_invalid; }; From 8e91ce1eb4f91c976570056fd8979a4afcf43a53 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 15 Nov 2022 11:09:19 +0800 Subject: [PATCH 022/185] Cabana: Move history logs to a tabbed widget (#26481) * tabwidget * cleanup * update state before show * cleanup * remove spacing * fix right panel stretch issue * fix missing } --- tools/cabana/detailwidget.cc | 55 +++++++++++++++++++++++------------- tools/cabana/detailwidget.h | 6 ++-- tools/cabana/historylog.cc | 4 +-- tools/cabana/mainwin.cc | 7 +++-- tools/cabana/signaledit.cc | 15 ---------- tools/cabana/signaledit.h | 6 ++-- 6 files changed, 47 insertions(+), 46 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 260c9dfec7..192d1fd66c 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -64,31 +64,35 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart frame_layout->addWidget(warning_widget); main_layout->addWidget(title_frame); - QWidget *container = new QWidget(this); - QVBoxLayout *container_layout = new QVBoxLayout(container); - container_layout->setSpacing(0); - container_layout->setContentsMargins(0, 0, 0, 0); - - scroll = new QScrollArea(this); - scroll->setWidget(container); - scroll->setWidgetResizable(true); - scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - main_layout->addWidget(scroll); - + // msg widget + QWidget *msg_widget = new QWidget(this); + QVBoxLayout *msg_layout = new QVBoxLayout(msg_widget); + msg_layout->setContentsMargins(0, 0, 0, 0); // binary view binary_view = new BinaryView(this); - container_layout->addWidget(binary_view); - + msg_layout->addWidget(binary_view); // signals signals_layout = new QVBoxLayout(); - container_layout->addLayout(signals_layout); + signals_layout->setSpacing(0); + msg_layout->addLayout(signals_layout); + msg_layout->addStretch(0); + + scroll = new QScrollArea(this); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidget(msg_widget); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - // history log + tab_widget = new QTabWidget(this); + tab_widget->setTabPosition(QTabWidget::South); + tab_widget->addTab(scroll, "Msg"); history_log = new HistoryLog(this); - container_layout->addWidget(history_log); + tab_widget->addTab(history_log, "Logs"); + main_layout->addWidget(tab_widget); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); + QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); }); QObject::connect(can, &CANMessages::msgsReceived, this, &DetailWidget::updateState); QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); }); QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu); @@ -151,6 +155,7 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { form = new SignalEdit(i); QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); + QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showFormClicked); QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); QObject::connect(form, &SignalEdit::showChart, charts, &ChartsWidget::showChart); @@ -176,16 +181,26 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { warning_label->setText(warnings.join('\n')); warning_widget->setVisible(!warnings.isEmpty()); - QTimer::singleShot(1, [this]() { setUpdatesEnabled(true); }); + setUpdatesEnabled(true); } void DetailWidget::updateState(const QHash * msgs) { time_label->setText(QString::number(can->currentSec(), 'f', 3)); - if (!msgs->contains(msg_id)) + if (msg_id.isEmpty() || (msgs && !msgs->contains(msg_id))) return; - binary_view->updateState(); - history_log->updateState(); + if (tab_widget->currentIndex() == 0) + binary_view->updateState(); + else + history_log->updateState(); +} + +void DetailWidget::showFormClicked() { + auto s = qobject_cast(sender()); + setUpdatesEnabled(false); + for (auto f : signal_list) + f->updateForm(f == s && !f->isFormVisible()); + setUpdatesEnabled(true); } void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool opened) { diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 5fc6d122fe..4346d1c5d5 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -28,6 +28,7 @@ public: QUndoStack *undo_stack = nullptr; private: + void showFormClicked(); void updateChartState(const QString &id, const Signal *sig, bool opened); void showTabBarContextMenu(const QPoint &pt); void addSignal(int start_bit, int size, bool little_endian); @@ -36,13 +37,14 @@ private: void removeSignal(const Signal *sig); void editMsg(); void removeMsg(); - void updateState(const QHash * msgs); + void updateState(const QHash * msgs = nullptr); QString msg_id; QLabel *name_label, *time_label, *warning_label; QWidget *warning_widget; QVBoxLayout *signals_layout; QTabBar *tabbar; + QTabWidget *tab_widget; QToolBar *toolbar; QAction *remove_msg_act; HistoryLog *history_log; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 28e344a46e..1b0898afbd 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -86,10 +86,8 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap); horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); verticalHeader()->setVisible(false); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); setFrameShape(QFrame::NoFrame); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); } diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 97d62cb4f4..f0419a2fb3 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -72,7 +72,7 @@ MainWindow::MainWindow() : QMainWindow() { video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); - r_layout->addWidget(charts_widget); + r_layout->addWidget(charts_widget, 1); main_layout->addWidget(right_container); setCentralWidget(central_widget); @@ -192,9 +192,10 @@ void MainWindow::saveDBCToFile() { if (!file_name.isEmpty()) { settings.last_dir = QFileInfo(file_name).absolutePath(); QFile file(file_name); - if (file.open(QIODevice::WriteOnly)) + if (file.open(QIODevice::WriteOnly)) { file.write(dbc()->generateDBC().toUtf8()); detail_widget->undo_stack->clear(); + } } } @@ -216,7 +217,7 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe void MainWindow::dockCharts(bool dock) { if (dock && floating_window) { floating_window->removeEventFilter(charts_widget); - r_layout->addWidget(charts_widget); + r_layout->addWidget(charts_widget, 1); floating_window->deleteLater(); floating_window = nullptr; } else if (!dock && !floating_window) { diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 8737154c16..eb22b78d5a 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -189,26 +188,12 @@ void SignalEdit::updateForm(bool visible) { icon->setText(visible ? "▼ " : "> "); } -void SignalEdit::showFormClicked() { - parentWidget()->setUpdatesEnabled(false); - for (auto &edit : parentWidget()->findChildren()) - edit->updateForm(edit == this && !form->isVisible()); - QTimer::singleShot(1, [this]() { parentWidget()->setUpdatesEnabled(true); }); -} - void SignalEdit::signalHovered(const Signal *s) { auto bg_color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx)); auto color = sig == s ? "white" : "black"; color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(bg_color.name())); } -void SignalEdit::hideEvent(QHideEvent *event) { - msg_id = ""; - sig = nullptr; - updateForm(false); - QWidget::hideEvent(event); -} - void SignalEdit::enterEvent(QEvent *event) { emit highlight(sig); QWidget::enterEvent(event); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index 335e49a869..da0b9758c7 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -34,6 +34,8 @@ public: void setSignal(const QString &msg_id, const Signal *sig); void setChartOpened(bool opened); void signalHovered(const Signal *sig); + void updateForm(bool show); + inline bool isFormVisible() const { return form->isVisible(); } const Signal *sig = nullptr; QString msg_id; @@ -42,14 +44,12 @@ signals: void showChart(const QString &name, const Signal *sig, bool show); void remove(const Signal *sig); void save(const Signal *sig, const Signal &new_sig); + void showFormClicked(); protected: - void hideEvent(QHideEvent *event) override; void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; void saveSignal(); - void updateForm(bool show); - void showFormClicked(); SignalForm *form = nullptr; ElidedLabel *title; From 2d766fee14e94011e261c8c557098a7223c87014 Mon Sep 17 00:00:00 2001 From: Jason Wen <47793918+sunnyhaibin@users.noreply.github.com> Date: Mon, 14 Nov 2022 22:46:29 -0500 Subject: [PATCH 023/185] HKG: Car Port for 2022 Kia Stinger (#26397) * HKG: Car Port for 2022 Kia Stinger * Substitute KIA STINGER GT2 2018 torque params * bump panda * Add test route * Update CARS.md * Not this checksum * Update test route * Update CARS.md * Harness C -> Harness K Co-authored-by: Adeeb Shihadeh --- RELEASES.md | 1 + docs/CARS.md | 3 ++- selfdrive/car/hyundai/carcontroller.py | 2 +- selfdrive/car/hyundai/hyundaican.py | 3 ++- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/hyundai/values.py | 22 +++++++++++++++++++++- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/substitute.yaml | 1 + 8 files changed, 30 insertions(+), 5 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index bf89b667fa..d00ece4b73 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -30,6 +30,7 @@ Version 0.8.17 (2022-11-21) * Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin! * Kia Sportage 2023 support thanks to sunnyhaibin! * Kia Sportage Hybrid 2023 support thanks to sunnyhaibin! +* Kia Stinger 2022 support thanks to sunnyhaibin! Version 0.8.16 (2022-08-26) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index b8a313386d..ca3a224586 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 214 Supported Cars +# 215 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -107,6 +107,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| +|Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 2f944edc0f..1ab90878b8 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -175,7 +175,7 @@ class CarController: if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022): + CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022): can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled)) # 5 Hz ACC options diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index dcb8430976..c2ffffbf22 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -20,7 +20,8 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, - CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): + CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, + CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 0306f7e104..0b5fd3bb39 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -158,7 +158,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.5 if candidate == CAR.KIA_OPTIMA_G4: ret.minSteerSpeed = 32 * CV.MPH_TO_MS - elif candidate == CAR.KIA_STINGER: + elif candidate in (CAR.KIA_STINGER, CAR.KIA_STINGER_2022): ret.mass = 1825. + STD_CARGO_KG ret.wheelbase = 2.78 ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 1dba3a5442..ecba7b7494 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -95,6 +95,7 @@ class CAR: KIA_SORENTO = "KIA SORENTO GT LINE 2018" KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" + KIA_STINGER_2022 = "KIA STINGER 2022" KIA_CEED = "KIA CEED INTRO ED 2019" KIA_EV6 = "KIA EV6 2022" @@ -181,6 +182,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), + CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", harness=Harness.hyundai_k), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), CAR.KIA_EV6: [ HyundaiCarInfo("Kia EV6 (without HDA II) 2022", "Highway Driving Assist", harness=Harness.hyundai_l), @@ -790,6 +792,23 @@ FW_VERSIONS = { b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', ], }, + CAR.KIA_STINGER_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', + ], + }, CAR.PALISADE: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ', @@ -1451,7 +1470,7 @@ FEATURES = { "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022}, # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022}, + "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022}, } CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN} @@ -1496,6 +1515,7 @@ DBC = { CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None), CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_STINGER_2022: dbc_dict('hyundai_kia_generic', None), CAR.KONA: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index d0051454a6..cd61528439 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -111,6 +111,7 @@ routes = [ CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11), CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), + CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022), CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2 CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1 diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index c043c9d455..77236e393e 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -37,6 +37,7 @@ HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 +KIA STINGER 2022: KIA STINGER GT2 2018 GENESIS G90 2017: GENESIS G70 2018 GENESIS G80 2017: GENESIS G70 2018 GENESIS G70 2020: HYUNDAI SONATA 2020 From 7fcafa402239c30d8344c46608ade6a8fd4831f4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 14 Nov 2022 23:28:43 -0800 Subject: [PATCH 024/185] ui: stretch abstract control title (#26499) stretch toggle title so it's easier to expand --- selfdrive/ui/qt/widgets/controls.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index d3b77935df..619fd3cb4c 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -38,7 +38,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons title_label = new QPushButton(title); title_label->setFixedHeight(120); title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left"); - hlayout->addWidget(title_label); + hlayout->addWidget(title_label, 1); // value next to control button value = new ElidedLabel(); From ffa32df062fe3cf241e887e7afbc582118bd1e36 Mon Sep 17 00:00:00 2001 From: ambientocclusion <1399123+ambientocclusion@users.noreply.github.com> Date: Tue, 15 Nov 2022 00:04:20 -0800 Subject: [PATCH 025/185] Multilang: add missing Japanese translations (#26482) Add new Japanese translations --- selfdrive/ui/translations/main_ja.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 87c60516fc..339826796b 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -60,11 +60,11 @@ Cellular Metered - + 従量制通信設定 Prevent large data uploads when on a metered connection - + 大量のデータのアップロードを防止します。 @@ -240,11 +240,11 @@ Reset - + リセット Review - + 確認 @@ -864,7 +864,7 @@ location set Uninstall - + アンインストール @@ -1004,19 +1004,19 @@ location set Experimental Mode - + 実験モード openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 <br> <h4>🌮 エンドツーエンドアクセル制御 🌮</h4> エンジンとブレーキの制御を全てopenpilotの運転モデルに委ねます。openpilotは人間が運転するのと同じように考え、赤信号や停止サインで車を停止します。openpilotが運転速度も決めるので、あなたが設定する速度は上限速度になります。 openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - + openpilotは、この車に搭載されているアクセル制御(ACC)を標準で利用します。openpilotによるアクセル制御を利用したい場合は、この設定を有効にしてください。 WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - + 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 @@ -1074,7 +1074,7 @@ location set Forget - + 削除 From d7f943e27524744f20b008a55ad3358fc9272034 Mon Sep 17 00:00:00 2001 From: Oxygen Date: Tue, 15 Nov 2022 16:23:37 +0800 Subject: [PATCH 026/185] Update missing items in main_zh-CHS.ts (#26492) Update missing iterms in main_zh-CHS.ts Co-authored-by: Shane Smiskol --- selfdrive/ui/translations/main_zh-CHS.ts | 50 ++++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 558a6cec4f..64b32c80d5 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -60,11 +60,11 @@ Cellular Metered - + 按流量计费的手机移动网络 Prevent large data uploads when on a metered connection - + 当使用按流量计费的连接时,避免上传大流量数据 @@ -140,7 +140,7 @@ Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。仅熄火时可用。 + 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。(仅熄火时可用) Reset Calibration @@ -204,7 +204,7 @@ Your device is pointed %1° %2 and %3° %4. - 您的设备校准为%1° %2、%3° %4。 + 您的设备校准为%1° %2、%3° %4。 down @@ -240,11 +240,11 @@ Reset - + 重置 Review - + 预览 @@ -814,35 +814,35 @@ location set SoftwarePanel Updates are only downloaded while the car is off. - + 车辆熄火时才能下载升级文件。 Current Version - + 当前版本 Download - + 下载 Install Update - + 安装更新 INSTALL - + 安装 Target Branch - + 目标分支 SELECT - + 选择 Select a branch - + 选择分支 UNINSTALL @@ -862,7 +862,7 @@ location set Uninstall - + 卸载 @@ -966,15 +966,15 @@ location set Experimental openpilot Longitudinal Control - + 试验性的openpilot纵向控制 openpilot longitudinal control is not currently available for this car. - + 目前此车辆无法使用openpilot纵向控制功能。 Enable experimental longitudinal control to enable this. - + 启用试验性的纵向控制功能。 Disengage on Accelerator Pedal @@ -1002,19 +1002,19 @@ location set Experimental Mode - + 测试模式 openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - + openpilot 默认 <b>轻松模式</b>驾驶车辆。 试验模式启用一些轻松模式之外的 <b>试验性功能</b>。 试验性功能包括: <br> <h4>🌮 端到端(End-to-End) 纵向控制 🌮</h4> 允许智能驾驶模型控制加速和制动。 openpilot 将模仿人类驾驶行为, 包括在遇到红灯和停车让行标识时停车。 鉴于智能驾驶模型判断实际行驶车速,设定速度仅为车速上限。 openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - + openpilot默认使用此车辆自带ACC,而非openpilot纵向控制功能。启用此按钮将切换到openpilot纵向控制功能。 WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - + 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 @@ -1064,15 +1064,15 @@ location set FORGET - 忘记 + 忽略 Forget Wi-Fi Network "%1"? - 忘记WiFi网络 "%1"? + 忽略WiFi网络 "%1"? Forget - + 忽略 From e58f6fbc11424e8059d6a86a4f0990f604c3a22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 15 Nov 2022 00:46:47 -0800 Subject: [PATCH 027/185] Rate limit honda steer (#26500) * Rate limit honda steer * Update ref_commit --- selfdrive/car/honda/carcontroller.py | 14 +++++++++++++- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 5fa475fe08..ba1f13fb4e 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -99,6 +99,12 @@ HUDData = namedtuple("HUDData", "lanes_visible", "fcw", "acc_alert", "steer_required"]) +def rate_limit_steer(new_steer, last_steer): + # TODO just hardcoded ramp to min/max in 0.2s for all Honda + MAX_DELTA = 5 * DT_CTRL + return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA) + + class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP @@ -116,6 +122,7 @@ class CarController: self.speed = 0.0 self.gas = 0.0 self.brake = 0.0 + self.last_steer = 0.0 def update(self, CC, CS): actuators = CC.actuators @@ -130,6 +137,10 @@ class CarController: accel = 0.0 gas, brake = 0.0, 0.0 + # *** rate limit steer *** + limited_steer = rate_limit_steer(actuators.steer, self.last_steer) + self.last_steer = limited_steer + # *** apply brake hysteresis *** pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady, CS.out.vEgo, self.CP.carFingerprint) @@ -143,7 +154,7 @@ class CarController: # **** process the car messages **** # steer torque is converted back to CAN reference (positive when steering right) - apply_steer = int(interp(-actuators.steer * self.params.STEER_MAX, + apply_steer = int(interp(-limited_steer * self.params.STEER_MAX, self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V)) # Send CAN commands @@ -250,6 +261,7 @@ class CarController: new_actuators.accel = self.accel new_actuators.gas = self.gas new_actuators.brake = self.brake + new_actuators.steer = self.last_steer self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f35e23d45f..a8053936d7 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -aa2d370836588fd80b648dbed8d156765ec804d5 +324408a87f49413da864616cb409537ce7d8beb2 From 844f7692d48c5b2b2b6f8bc90070d9fc83ef7171 Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Tue, 15 Nov 2022 19:44:25 +0100 Subject: [PATCH 028/185] loggerd: add missing utility include (#26502) --- selfdrive/loggerd/loggerd.h | 1 + selfdrive/loggerd/tests/test_logger.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/selfdrive/loggerd/loggerd.h b/selfdrive/loggerd/loggerd.h index 6eafbe08d0..1fa6349828 100644 --- a/selfdrive/loggerd/loggerd.h +++ b/selfdrive/loggerd/loggerd.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "cereal/messaging/messaging.h" #include "cereal/services.h" diff --git a/selfdrive/loggerd/tests/test_logger.cc b/selfdrive/loggerd/tests/test_logger.cc index 18a0e57df7..ba7835d632 100644 --- a/selfdrive/loggerd/tests/test_logger.cc +++ b/selfdrive/loggerd/tests/test_logger.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include "catch2/catch.hpp" #include "cereal/messaging/messaging.h" From bdc432d21824cc83627bb5d1abd5e99b4bc62fc3 Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Tue, 15 Nov 2022 11:55:43 -0700 Subject: [PATCH 029/185] Add video link for Volt (#26504) --- selfdrive/car/gm/values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index eace6b6aca..03392ba0f9 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -90,7 +90,7 @@ class GMCarInfo(CarInfo): CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"), - CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0), + CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ"), CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"), CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), From a662af57c7de750df4f1650ee4079c56665b817e Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 15 Nov 2022 13:07:46 -0800 Subject: [PATCH 030/185] CI: use github.head_ref to group PR action runs (#26505) * CI: use github.head_ref to group PR action runs for push triggers, github.ref is set to the "branch or tag ref that was pushed" for pull_request triggers, it is set to the "pull request merge branch" (master?) github.head_ref is only set when the trigger is pull_request https://docs.github.com/en/actions/learn-github-actions/contexts#github-context * only check github.ref for push event --- .github/workflows/selfdrive_tests.yaml | 2 +- .github/workflows/tools_tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 598f2c592b..f2cc51285d 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -7,7 +7,7 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true env: diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 549a2f4195..94cc3c2580 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -7,7 +7,7 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true env: From fbf3ef0895182ffab90ddda83fa485b02d5a76d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 15 Nov 2022 14:21:00 -0800 Subject: [PATCH 031/185] More Honda rate limit (#26509) * More Honda rate limit * Update ref_commit --- selfdrive/car/honda/carcontroller.py | 4 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index ba1f13fb4e..790dce1810 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -100,8 +100,8 @@ HUDData = namedtuple("HUDData", def rate_limit_steer(new_steer, last_steer): - # TODO just hardcoded ramp to min/max in 0.2s for all Honda - MAX_DELTA = 5 * DT_CTRL + # TODO just hardcoded ramp to min/max in 0.33s for all Honda + MAX_DELTA = 3 * DT_CTRL return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index a8053936d7..4aa9d60ab5 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -324408a87f49413da864616cb409537ce7d8beb2 +959e63af52d9efdb62156cab4b773f88b154fd75 From 9c5df76a6c8784af3bec64827a2dbab464d8ce39 Mon Sep 17 00:00:00 2001 From: hoomoose <94947902+hoomoose@users.noreply.github.com> Date: Tue, 15 Nov 2022 17:19:30 -0700 Subject: [PATCH 032/185] Hyundai: longitudinal support for all CAN-FD EV and Hybrids (#26345) * Update values.py * Update interface.py * Update interface.py * Update carcontroller.py * Update interface.py * Update interface.py * Update values.py * Update values.py * Update interface.py * Update values.py * Update interface.py * Update carcontroller.py * cleanup * update docs * bump panda Co-authored-by: Adeeb Shihadeh --- docs/CARS.md | 12 ++++++------ panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 5 +++-- selfdrive/car/hyundai/interface.py | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index ca3a224586..59b8145556 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -60,8 +60,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| |Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| -|Hyundai|Ioniq 5 (without HDA II) 2022-23|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| +|Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| +|Hyundai|Ioniq 5 (without HDA II) 2022-23|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| @@ -83,13 +83,13 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Tucson Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| +|Hyundai|Tucson Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| -|Kia|EV6 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| +|Kia|EV6 (without HDA II) 2022|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| @@ -105,7 +105,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| -|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| +|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| diff --git a/panda b/panda index c0632cd32b..5dc5cd8e20 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c0632cd32b78279dfbb3ce28c7a10ded8090d40f +Subproject commit 5dc5cd8e20668dfb15d35b175fccbfd3f7b63b09 diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 1ab90878b8..e5fdbfd57a 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -85,7 +85,7 @@ class CarController: # *** common hyundai stuff *** # tester present - w/ no response (keeps relevant ECU disabled) - if self.frame % 100 == 0 and self.CP.openpilotLongitudinalControl: + if self.frame % 100 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and self.CP.openpilotLongitudinalControl: addr, bus = 0x7d0, 0 if self.CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, 5 @@ -122,7 +122,8 @@ class CarController: can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CP, CC.enabled)) if self.CP.openpilotLongitudinalControl: - can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) + if hda2: + can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) if self.frame % 2 == 0: can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, set_speed_in_units)) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 0b5fd3bb39..8738aabd17 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -219,7 +219,7 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] - ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2) + ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in CANFD_RADAR_SCC_CAR else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] @@ -283,7 +283,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, logcan, sendcan): - if CP.openpilotLongitudinalControl: + if CP.openpilotLongitudinalControl and not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value): addr, bus = 0x7d0, 0 if CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, 5 From 4eda53cef2adb949709166c2e5d204565376ce2e Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 15 Nov 2022 16:41:27 -0800 Subject: [PATCH 033/185] add laikad comment --- selfdrive/locationd/laikad.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 2769f394c5..6936d88acc 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -187,6 +187,7 @@ class Laikad: "gpsTimeOfWeek": tow, "positionECEF": measurement_msg(value=ecef_pos.tolist(), std=pos_std.tolist(), valid=kf_valid), "velocityECEF": measurement_msg(value=ecef_vel.tolist(), std=vel_std.tolist(), valid=kf_valid), + # TODO std is incorrectly the dimension of the measurements and not 3D "positionFixECEF": measurement_msg(value=self.last_pos_fix, std=self.last_pos_residual, valid=self.last_pos_fix_t == t), "ubloxMonoTime": gnss_mono_time, "correctedMeasurements": meas_msgs From 2ad9a4f95a82bf922c86b373026c0e96f9971a80 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 16:44:03 -0800 Subject: [PATCH 034/185] offroad ui: support storing confirmation of a toggle (#26510) * show confirmation toggle on first toggle of experimental mode * don't store confirmation if users toggle off *after* this PR * refactor * cleaner * not true * try here --- common/params.cc | 1 + selfdrive/ui/qt/offroad/settings.cc | 20 ++++++++------------ selfdrive/ui/qt/widgets/controls.h | 10 ++++++++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/common/params.cc b/common/params.cc index e17d1f1b13..9e3e32d584 100644 --- a/common/params.cc +++ b/common/params.cc @@ -103,6 +103,7 @@ std::unordered_map keys = { {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisablePowerDown", PERSISTENT}, {"ExperimentalMode", PERSISTENT}, + {"ExperimentalModeConfirmed", PERSISTENT}, {"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB {"DisableUpdates", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT}, diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index a03cf02010..ced0744692 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -28,20 +28,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon, confirm - std::vector> toggle_defs{ + std::vector> toggle_defs{ { "OpenpilotEnabledToggle", tr("Enable openpilot"), tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), "../assets/offroad/icon_openpilot.png", - false, }, { "ExperimentalMode", tr("Experimental Mode"), "", "../assets/offroad/icon_road.png", - false, }, { "ExperimentalLongitudinalEnabled", @@ -50,35 +48,30 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { .arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).")) .arg(tr("openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control.")), "../assets/offroad/icon_speed_limit.png", - true, }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), "../assets/offroad/icon_warning.png", - false, }, { "IsMetric", tr("Use Metric System"), tr("Display speed in km/h instead of mph."), "../assets/offroad/icon_metric.png", - false, }, { "RecordFront", tr("Record and Upload Driver Camera"), tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), "../assets/offroad/icon_monitoring.png", - false, }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), "../assets/offroad/icon_disengage_on_accelerator.svg", - false, }, #ifdef ENABLE_MAPS { @@ -86,20 +79,18 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Show ETA in 24h Format"), tr("Use 24h format instead of am/pm"), "../assets/offroad/icon_metric.png", - false, }, { "NavSettingLeftSide", tr("Show Map on Left Side of UI"), tr("Show map on left side when in split screen view."), "../assets/offroad/icon_road.png", - false, }, #endif }; - for (auto &[param, title, desc, icon, confirm] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, confirm, this); + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, false, this); bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); @@ -108,6 +99,11 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { toggles[param.toStdString()] = toggle; } + // Toggles with confirmation dialogs + toggles["ExperimentalMode"]->confirm = true; + toggles["ExperimentalMode"]->store_confirm = true; + toggles["ExperimentalLongitudinalEnabled"]->confirm = true; + connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); }); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index b67224b33d..c639b7f481 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -139,13 +139,16 @@ class ParamControl : public ToggleControl { Q_OBJECT public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool confirm, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { + ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool _confirm, QWidget *parent = nullptr) : confirm(_confirm), ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { QString content("

" + title + "


" "

" + getDescription() + "

"); ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this); - if (!confirm || !state || dialog.exec()) { + + bool confirmed = store_confirm && params.getBool(key + "Confirmed"); + if (!confirm || confirmed || !state || dialog.exec()) { + if (store_confirm && state) params.putBool(key + "Confirmed", true); params.putBool(key, state); } else { toggle.togglePosition(); @@ -153,6 +156,9 @@ public: }); } + bool confirm = false; + bool store_confirm = false; + void refresh() { if (params.getBool(key) != toggle.on) { toggle.togglePosition(); From 9c96b21367af3dda9b193a821fe2e175a63f4176 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 17:18:13 -0800 Subject: [PATCH 035/185] publish experimental mode state (#26512) * publish experimental mode state * remove that --- cereal | 2 +- selfdrive/controls/controlsd.py | 1 + selfdrive/ui/qt/onroad.cc | 8 +++++--- selfdrive/ui/ui.cc | 1 - selfdrive/ui/ui.h | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cereal b/cereal index afafa0a2a5..3bae09cf65 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit afafa0a2a537d775842ab2e1bf20cb9a33b34f9a +Subproject commit 3bae09cf6527674d7eda3a9956242aad94a8f3d2 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index f69e9e7fd1..a18bec83a8 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -777,6 +777,7 @@ class Controls: controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) controlsState.canErrorCounter = self.can_rcv_timeout_counter + controlsState.experimentalMode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl lat_tuning = self.CP.lateralTuning.which() if self.joystick_mode: diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 23986726c8..fcf29181e7 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -446,6 +446,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.save(); const UIScene &scene = s->scene; + SubMaster &sm = *(s->sm); + // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); @@ -461,8 +463,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint path QLinearGradient bg(0, height(), 0, height() / 4); float start_hue, end_hue; - if (scene.experimental_mode) { - const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration(); + if (sm["controlsState"].getControlsState().getExperimentalMode()) { + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration(); float acceleration_future = 0; if (acceleration.getZ().size() > 16) { acceleration_future = acceleration.getX()[16]; // 2.5 seconds @@ -555,7 +557,7 @@ void AnnotatedCameraWidget::paintGL() { } else if (v_ego > 15) { wide_cam_requested = false; } - wide_cam_requested = wide_cam_requested && s->scene.experimental_mode; + wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode(); // TODO: also detect when ecam vision stream isn't available // for replay of old routes, never go to widecam wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 970448359e..2d4533afe1 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -174,7 +174,6 @@ void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - s->scene.experimental_mode = params.getBool("ExperimentalMode"); } void UIState::updateStatus() { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 38e2ffe3ce..d6f5c3e2e0 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -104,7 +104,7 @@ typedef struct UIScene { QPointF lead_vertices[2]; float light_sensor; - bool started, ignition, is_metric, map_on_left, longitudinal_control, experimental_mode; + bool started, ignition, is_metric, map_on_left, longitudinal_control; uint64_t started_frame; } UIScene; From d4fd0c3c8798317e4af87642af30ddbcd39fc4d6 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 15 Nov 2022 18:41:43 -0800 Subject: [PATCH 036/185] [torqued] Make torqued work on Hyundai Tucson (#26511) * changes to get offline values * set tucon values * move to params.yaml --- selfdrive/car/torque_data/override.yaml | 1 - selfdrive/car/torque_data/params.yaml | 1 + selfdrive/locationd/torqued.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 2ef5a1cd0f..c0b8592ee0 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -30,7 +30,6 @@ CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] -HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0] HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0] KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.0] KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index c2ebadfc7a..a9023b4edc 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -38,6 +38,7 @@ HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0 HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] +HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272] JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185] JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003] KIA EV6 2022: [3.2, 3.0, 0.05] diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 42dff60087..588bca1578 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -16,9 +16,9 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 MIN_POINTS_TOTAL = 4000 -MIN_POINTS_TOTAL_QLOG = 800 +MIN_POINTS_TOTAL_QLOG = 600 FIT_POINTS_TOTAL = 2000 -FIT_POINTS_TOTAL_QLOG = 800 +FIT_POINTS_TOTAL_QLOG = 600 MIN_VEL = 15 # m/s FRICTION_FACTOR = 1.5 # ~85% of data coverage FACTOR_SANITY = 0.3 From f3efc8998cee2750e65a99f96a6e9c787c17a19f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 18:25:16 -0800 Subject: [PATCH 037/185] taco time --- RELEASES.md | 17 ++++++++--------- common/version.h | 2 +- selfdrive/ui/qt/offroad/settings.cc | 11 ++++++++--- selfdrive/ui/translations/main_ja.ts | 12 ++++++------ selfdrive/ui/translations/main_ko.ts | 12 ++++++------ selfdrive/ui/translations/main_pt-BR.ts | 12 ++++++------ selfdrive/ui/translations/main_zh-CHS.ts | 12 ++++++------ selfdrive/ui/translations/main_zh-CHT.ts | 12 ++++++------ 8 files changed, 47 insertions(+), 43 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index d00ece4b73..e15286c5f0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,29 +1,28 @@ -Version 0.8.17 (2022-11-21) +Version 0.9.0 (2022-11-21) ======================== * New driving model - * Internal feature space information content increased tenfold during training (to ~700 bits), which makes the model dramatically more accurate + * Internal feature space information content increased tenfold during training to ~700 bits, which makes the model dramatically more accurate * Less reliance on previous frames makes model more reactive and snappy * Trained in new reprojective simulator - * Trained in 36hrs from scratch, compared to one week for previous releases + * Trained in 36 hours from scratch, compared to one week for previous releases * Training now simulates both lateral and longitudinal behavior, which allows openpilot to slow down for turns, stop at traffic lights, and more in experimental mode -* Driver monitoring updates - * New bigger model with added end-to-end distracted trigger - * Reduced false positives during driver calibration * Experimental driving mode * End-to-end longitudinal control * Stops for traffic lights and stop signs * Slows down for turns - * openpilot defaults to chill mode, enable experimental in settings + * openpilot defaults to chill mode, enable experimental mode in settings +* Driver monitoring updates + * New bigger model with added end-to-end distracted trigger + * Reduced false positives during driver calibration * Self-tuning torque controller: learns parameters live for each car * Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models * UI updates - * Multi-language in navigation * Matched speeds shown on car's dash + * Multi-language in navigation * Improved update experience * Border turns grey while overriding steering * Bookmark events while driving; view them in comma connect * New onroad visualization for experimental mode -* AGNOS 6 * tools: new and improved cabana thanks to deanlee! * Experimental longitudinal support for Volkswagen, CAN-FD Hyundai, and new GM models * Genesis GV70 2022-23 support thanks to zunichky and sunnyhaibin! diff --git a/common/version.h b/common/version.h index 0a109c1faa..74a56a7c1b 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.17" +#define COMMA_VERSION "0.9.0" diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index ced0744692..cf6f589b65 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -46,7 +46,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Experimental openpilot Longitudinal Control"), QString("%1
%2") .arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).")) - .arg(tr("openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control.")), + .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.")), "../assets/offroad/icon_speed_limit.png", }, { @@ -119,10 +119,15 @@ void TogglesPanel::updateToggles() { const QString e2e_description = tr("\ openpilot defaults to driving in chill mode.\ Experimental mode enables alpha-level features that aren't ready for chill mode. \ - Experimental features are listed below:\ + Experimental features are listed below: \
\

🌮 End-to-End Longitudinal Control 🌮

\ - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound."); + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. \ + Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. \ +
\ +

New Driving Visualization

\ + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.\ + "); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 339826796b..f7329811a6 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1007,16 +1007,16 @@ location set 実験モード - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 <br> <h4>🌮 エンドツーエンドアクセル制御 🌮</h4> エンジンとブレーキの制御を全てopenpilotの運転モデルに委ねます。openpilotは人間が運転するのと同じように考え、赤信号や停止サインで車を停止します。openpilotが運転速度も決めるので、あなたが設定する速度は上限速度になります。 + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilotは、この車に搭載されているアクセル制御(ACC)を標準で利用します。openpilotによるアクセル制御を利用したい場合は、この設定を有効にしてください。 + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 31c910a910..56d7dfd965 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1007,16 +1007,16 @@ location set 실험적 모드 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험 모드의 특징은 아래에 나열되어 있습니다 <br> <h4>🌮 E2E 롱컨트롤 🌮</h4> 주행모델이 가속과 감속을 제어하도록 합니다. openpilot은 신호등과 정지표지판을 보고 멈추는 것을 포함하여 운전자가 생각하는것처럼 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 상한선으로만 작용합니다. + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 자동긴급제동(AEB)를 비활성화합니다. - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilot은 차량의 내장 ACC로 기본 설정됩니다. 롱컨트롤으로 전환하려면 이 옵션을 활성화하세요. + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 자동긴급제동(AEB)를 비활성화합니다. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 6a1d32f87a..f68400a3c7 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1011,16 +1011,16 @@ trabalho definido Modo Experimental - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: <br> <h4>🌮 Controle Longitudinal de Ponta a Ponta 🌮</h4> Deixe o modelo de condução controlar o acelerador e os freios. Uma vez que o modelo de condução decide qual velocidade dirigir, a velocidade definida só funcionará como um limite superior. + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB). - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - O padrão do openpilot é o ACC integrado do carro em vez do controle longitudinal do openpilot neste carro. Habilite isto para alternar para controle longitudinal do openpilot. + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB). + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 64b32c80d5..174f40696b 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1005,16 +1005,16 @@ location set 测试模式 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot 默认 <b>轻松模式</b>驾驶车辆。 试验模式启用一些轻松模式之外的 <b>试验性功能</b>。 试验性功能包括: <br> <h4>🌮 端到端(End-to-End) 纵向控制 🌮</h4> 允许智能驾驶模型控制加速和制动。 openpilot 将模仿人类驾驶行为, 包括在遇到红灯和停车让行标识时停车。 鉴于智能驾驶模型判断实际行驶车速,设定速度仅为车速上限。 + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilot默认使用此车辆自带ACC,而非openpilot纵向控制功能。启用此按钮将切换到openpilot纵向控制功能。 + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index c9810597b5..d1683a9562 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1007,16 +1007,16 @@ location set 實驗模式 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. - openpilot 默認以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。 實驗功能如下: <br> <h4>🌮端到端縱向控制🌮</h4> 讓駕駛模型控制油門和剎車。 openpilot 將像人類一樣駕駛,包括紅燈和停車標誌時停車,因為是由駕駛模型來決定車速,所以定速的設定值只會作為上限。 + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告:openpilot 縱向控制在這輛車上仍屬實驗性質,啟用後會喪失自動緊急煞車 (AEB) 功能。 - openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. - openpilot 默認使用汽車內置的主動巡航控制 (ACC),而不是使用 openpilot 縱向控制。啟用此選項可切換到 openpilot 縱向控制。 + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告:openpilot 縱向控制在這輛車上仍屬實驗性質,啟用後會喪失自動緊急煞車 (AEB) 功能。 + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + From 97a205c94df412d40db1a94bbc4bb735b5afea27 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 16 Nov 2022 11:07:50 +0800 Subject: [PATCH 038/185] UI: fix CameraView crash on deleting. (#26390) * fix crash on delete * TODO? * after makeCurrent --- selfdrive/ui/qt/offroad/driverview.cc | 1 + selfdrive/ui/qt/widgets/cameraview.cc | 10 ++++++++++ selfdrive/ui/qt/widgets/cameraview.h | 1 + 3 files changed, 12 insertions(+) diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 0ff786fb91..1377bb3b23 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -35,6 +35,7 @@ void DriverViewScene::showEvent(QShowEvent* event) { } void DriverViewScene::hideEvent(QHideEvent* event) { + // TODO: stop vipc thread ? params.putBool("IsDriverViewEnabled", false); } diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index a606d6893e..347cdb1dca 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -102,6 +102,7 @@ CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool CameraWidget::~CameraWidget() { makeCurrent(); + stopVipcThread(); if (isValid()) { glDeleteVertexArrays(1, &frame_vao); glDeleteBuffers(1, &frame_vbo); @@ -171,6 +172,15 @@ void CameraWidget::showEvent(QShowEvent *event) { } } +void CameraWidget::stopVipcThread() { + if (vipc_thread) { + vipc_thread->requestInterruption(); + vipc_thread->quit(); + vipc_thread->wait(); + vipc_thread = nullptr; + } +} + void CameraWidget::updateFrameMat() { int w = width(), h = height(); diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 0698d1fb9a..7cc3847f99 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -51,6 +51,7 @@ protected: void updateCalibration(const mat3 &calib); void vipcThread(); void clearFrames(); + void stopVipcThread(); bool zoomed_view; GLuint frame_vao, frame_vbo, frame_ibo; From c6e3d566e9b23b2046f933f3dd9e189708230a56 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 19:24:12 -0800 Subject: [PATCH 039/185] update experimental mode disabled description --- selfdrive/ui/qt/offroad/settings.cc | 4 ++-- selfdrive/ui/translations/main_ja.ts | 16 ++++++++-------- selfdrive/ui/translations/main_ko.ts | 16 ++++++++-------- selfdrive/ui/translations/main_pt-BR.ts | 16 ++++++++-------- selfdrive/ui/translations/main_zh-CHS.ts | 16 ++++++++-------- selfdrive/ui/translations/main_zh-CHT.ts | 16 ++++++++-------- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index cf6f589b65..997e344221 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -151,8 +151,8 @@ void TogglesPanel::updateToggles() { e2e_toggle->setEnabled(false); params.remove("ExperimentalMode"); - const QString no_long = tr("openpilot longitudinal control is not currently available for this car."); - const QString exp_long = tr("Enable experimental longitudinal control to enable this."); + const QString no_long = tr("Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control."); + const QString exp_long = tr("Enable experimental longitudinal control to allow experimental mode."); e2e_toggle->setDescription("" + (CP.getExperimentalLongitudinalAvailable() ? exp_long : no_long) + "

" + e2e_description); } diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index f7329811a6..21fdc48fef 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -970,14 +970,6 @@ location set Experimental openpilot Longitudinal Control 実験段階のopenpilotによるアクセル制御 - - openpilot longitudinal control is not currently available for this car. - openpilotによるアクセル制御は、この車では現在利用できません。 - - - Enable experimental longitudinal control to enable this. - ここ機能を使う為には、「実験段階のopenpilotによるアクセル制御」を先に有効化してください。 - Disengage on Accelerator Pedal アクセルを踏むと openpilot を中断 @@ -1018,6 +1010,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + +
Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 56d7dfd965..57c688ffe6 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -970,14 +970,6 @@ location set Experimental openpilot Longitudinal Control openpilot 롱컨트롤 (실험적) - - openpilot longitudinal control is not currently available for this car. - 현재 이 차량에는 openpilot 롱컨트롤을 사용할 수 없습니다. - - - Enable experimental longitudinal control to enable this. - openpilot 롱컨트롤을 활성화합니다. (실험적) - Disengage on Accelerator Pedal 가속페달 조작시 해제 @@ -1018,6 +1010,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index f68400a3c7..2d7195e0d2 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -974,14 +974,6 @@ trabalho definido Experimental openpilot Longitudinal Control Controle longitudinal experimental openpilot - - openpilot longitudinal control is not currently available for this car. - controle longitudinal openpilot não está disponível para este carro. - - - Enable experimental longitudinal control to enable this. - Habilite o controle longitudinal experimental para habilitar isso. - Disengage on Accelerator Pedal Desacionar Com Pedal Do Acelerador @@ -1022,6 +1014,14 @@ trabalho definido openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 174f40696b..fb9b6dd6f5 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -968,14 +968,6 @@ location set Experimental openpilot Longitudinal Control 试验性的openpilot纵向控制 - - openpilot longitudinal control is not currently available for this car. - 目前此车辆无法使用openpilot纵向控制功能。 - - - Enable experimental longitudinal control to enable this. - 启用试验性的纵向控制功能。 - Disengage on Accelerator Pedal 踩油门时取消控制 @@ -1016,6 +1008,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index d1683a9562..86d5482355 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -970,14 +970,6 @@ location set Experimental openpilot Longitudinal Control 使用 openpilot 縱向控制(實驗) - - openpilot longitudinal control is not currently available for this car. - openpilot 縱向控制目前不適用於這輛車。 - - - Enable experimental longitudinal control to enable this. - 打開縱向控制(實驗)以啟用此功能。 - Disengage on Accelerator Pedal 油門取消控車 @@ -1018,6 +1010,14 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + + + + Enable experimental longitudinal control to allow experimental mode. + + Updater From 3b71a9780dab0eed22dc277ce26a4d95c97f07df Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 15 Nov 2022 19:45:15 -0800 Subject: [PATCH 040/185] [torqued] Non-zero offline friction (#26513) add non zero friction values --- selfdrive/car/torque_data/override.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index c0b8592ee0..6ec782444c 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -30,10 +30,10 @@ CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] -HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0] -KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.0] -KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0] -GENESIS GV70 1ST GEN: [2.42, 2.42, 0.01] +HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.1] +KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1] +KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1] +GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] From f2d97da9b46d81454ec227152e280f1261ae9883 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 15 Nov 2022 20:14:35 -0800 Subject: [PATCH 041/185] jenkins: use tici-needs-can to build release --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 696446c65f..c34d253585 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -59,7 +59,7 @@ pipeline { branch 'devel-staging' } steps { - phone_steps("tici", [ + phone_steps("tici-needs-can", [ ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], ]) } From b3f75b0c5b151cbcb048a31bb33c8f49b2a818b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 20:46:42 -0800 Subject: [PATCH 042/185] ui: function for setting toggle confirmation settings (#26516) function for setting confirmation settings --- selfdrive/ui/qt/offroad/settings.cc | 7 +++---- selfdrive/ui/qt/widgets/controls.h | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 997e344221..85b09dc183 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -90,7 +90,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }; for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, false, this); + auto toggle = new ParamControl(param, title, desc, icon, this); bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); @@ -100,9 +100,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } // Toggles with confirmation dialogs - toggles["ExperimentalMode"]->confirm = true; - toggles["ExperimentalMode"]->store_confirm = true; - toggles["ExperimentalLongitudinalEnabled"]->confirm = true; + toggles["ExperimentalMode"]->setConfirmation(true, true); + toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index c639b7f481..e2fd9d1b9d 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -139,7 +139,7 @@ class ParamControl : public ToggleControl { Q_OBJECT public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool _confirm, QWidget *parent = nullptr) : confirm(_confirm), ToggleControl(title, desc, icon, false, parent) { + ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { QString content("

" + title + "


" @@ -156,8 +156,10 @@ public: }); } - bool confirm = false; - bool store_confirm = false; + void setConfirmation(bool _confirm, bool _store_confirm) { + confirm = _confirm; + store_confirm = _store_confirm; + }; void refresh() { if (params.getBool(key) != toggle.on) { @@ -172,6 +174,8 @@ public: private: std::string key; Params params; + bool confirm = false; + bool store_confirm = false; }; class ListWidget : public QWidget { From 62024176c6e0386401374873e73512ab76f4204a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 20:52:31 -0800 Subject: [PATCH 043/185] ui: toggle control supports active icons (#26514) * support active icons * function * fix crash if not setting icon but active icon * revert * clean up --- selfdrive/ui/qt/widgets/controls.cc | 6 +++--- selfdrive/ui/qt/widgets/controls.h | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 619fd3cb4c..456cf748f4 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -26,10 +26,10 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons hlayout->setSpacing(20); // left icon + icon_label = new QLabel(); if (!icon.isEmpty()) { - QPixmap pix(icon); - QLabel *icon_label = new QLabel(); - icon_label->setPixmap(pix.scaledToWidth(80, Qt::SmoothTransformation)); + icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + icon_label->setPixmap(icon_pixmap); icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); hlayout->addWidget(icon_label); } diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index e2fd9d1b9d..0540088a78 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -54,6 +54,9 @@ public: return description->text(); } + QLabel *icon_label; + QPixmap icon_pixmap; + public slots: void showDescription() { description->setVisible(true); @@ -153,6 +156,12 @@ public: } else { toggle.togglePosition(); } + + if (state && !active_icon_pixmap.isNull()) { + icon_label->setPixmap(active_icon_pixmap); + } else if (!icon_pixmap.isNull()) { + icon_label->setPixmap(icon_pixmap); + } }); } @@ -161,6 +170,10 @@ public: store_confirm = _store_confirm; }; + void setActiveIcon(const QString &icon) { + active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + } + void refresh() { if (params.getBool(key) != toggle.on) { toggle.togglePosition(); @@ -174,6 +187,7 @@ public: private: std::string key; Params params; + QPixmap active_icon_pixmap; bool confirm = false; bool store_confirm = false; }; From 797c626984080e1ff17206b99e67a11bba87992f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 21:07:11 -0800 Subject: [PATCH 044/185] ui: handle two dynamic toggle icon cases (#26517) handles two cases --- selfdrive/ui/qt/widgets/controls.h | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 0540088a78..770b9b92dd 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -153,15 +153,10 @@ public: if (!confirm || confirmed || !state || dialog.exec()) { if (store_confirm && state) params.putBool(key + "Confirmed", true); params.putBool(key, state); + setIcon(state); } else { toggle.togglePosition(); } - - if (state && !active_icon_pixmap.isNull()) { - icon_label->setPixmap(active_icon_pixmap); - } else if (!icon_pixmap.isNull()) { - icon_label->setPixmap(icon_pixmap); - } }); } @@ -175,8 +170,10 @@ public: } void refresh() { - if (params.getBool(key) != toggle.on) { + bool state = params.getBool(key); + if (state != toggle.on) { toggle.togglePosition(); + setIcon(state); } }; @@ -185,6 +182,14 @@ public: }; private: + void setIcon(bool state) { + if (state && !active_icon_pixmap.isNull()) { + icon_label->setPixmap(active_icon_pixmap); + } else if (!icon_pixmap.isNull()) { + icon_label->setPixmap(icon_pixmap); + } + }; + std::string key; Params params; QPixmap active_icon_pixmap; From 73ec91f3bc20409e672480428f125dd6c8b9f2a1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 16 Nov 2022 13:10:05 +0800 Subject: [PATCH 045/185] Cabana: fix right panel layout after undocking charts (#26497) * fix stretch * set window title --- tools/cabana/mainwin.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index f0419a2fb3..7781ab3b75 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -73,6 +73,7 @@ MainWindow::MainWindow() : QMainWindow() { video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); r_layout->addWidget(charts_widget, 1); + r_layout->addStretch(0); main_layout->addWidget(right_container); setCentralWidget(central_widget); @@ -217,11 +218,12 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe void MainWindow::dockCharts(bool dock) { if (dock && floating_window) { floating_window->removeEventFilter(charts_widget); - r_layout->addWidget(charts_widget, 1); + r_layout->insertWidget(2, charts_widget, 1); floating_window->deleteLater(); floating_window = nullptr; } else if (!dock && !floating_window) { floating_window = new QWidget(nullptr); + floating_window->setWindowTitle("Charts - Cabana"); floating_window->setLayout(new QVBoxLayout()); floating_window->layout()->addWidget(charts_widget); floating_window->installEventFilter(charts_widget); From 58b84fb401a804967aa0dd5ee66fafa90194fd30 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 22:18:26 -0800 Subject: [PATCH 046/185] ui: offroad experimental mode button (#26498) * draft * draft * before qpushbutton * icon, clean up button, clicked goes to toggles * fix icon * add imgs * img * make square * works with layouts! * fix gradient * this looks good * clean up * clean up * remove padding around couch * use scene's experimental_model, new onroad design * rename widget * def want 3 * update translations * add img * add 25px of padding! * make 300px (no change) * clean up old images * 5 px smaller * add white img * fix from merge * no style sheets * see how this looks on device * aliased vertical line (clean up) * clean up * imgs * couch * delete * bye bye * expand toggle support * clean up * fix dynamic icon * make exp icon dynamic * order * move to offroad --- .../assets/fonts/JetBrainsMono-Medium.ttf | Bin 0 -> 204140 bytes selfdrive/assets/img_couch.svg | 3 + selfdrive/assets/img_experimental.svg | 10 +++ selfdrive/assets/img_experimental_grey.svg | 4 + selfdrive/assets/img_experimental_white.svg | 4 + selfdrive/ui/SConscript | 2 +- selfdrive/ui/qt/home.cc | 19 ++++- selfdrive/ui/qt/home.h | 5 +- selfdrive/ui/qt/offroad/experimental_mode.cc | 75 ++++++++++++++++++ selfdrive/ui/qt/offroad/experimental_mode.h | 31 ++++++++ selfdrive/ui/qt/offroad/settings.cc | 23 +++++- selfdrive/ui/qt/offroad/settings.h | 5 ++ selfdrive/ui/qt/onroad.cc | 6 +- selfdrive/ui/qt/onroad.h | 1 + selfdrive/ui/qt/sidebar.h | 2 +- selfdrive/ui/qt/window.cc | 4 +- selfdrive/ui/qt/window.h | 2 +- selfdrive/ui/translations/main_ja.ts | 11 +++ selfdrive/ui/translations/main_ko.ts | 11 +++ selfdrive/ui/translations/main_pt-BR.ts | 11 +++ selfdrive/ui/translations/main_zh-CHS.ts | 11 +++ selfdrive/ui/translations/main_zh-CHT.ts | 11 +++ 22 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 selfdrive/assets/fonts/JetBrainsMono-Medium.ttf create mode 100644 selfdrive/assets/img_couch.svg create mode 100644 selfdrive/assets/img_experimental.svg create mode 100644 selfdrive/assets/img_experimental_grey.svg create mode 100644 selfdrive/assets/img_experimental_white.svg create mode 100644 selfdrive/ui/qt/offroad/experimental_mode.cc create mode 100644 selfdrive/ui/qt/offroad/experimental_mode.h diff --git a/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf b/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a6ba5529af2486ea7d2b92b7b8bd598bbee46e29 GIT binary patch literal 204140 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 literal 0 HcmV?d00001 diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg new file mode 100644 index 0000000000..5b3c048318 --- /dev/null +++ b/selfdrive/assets/img_couch.svg @@ -0,0 +1,3 @@ + + + diff --git a/selfdrive/assets/img_experimental.svg b/selfdrive/assets/img_experimental.svg new file mode 100644 index 0000000000..0eaec3b3cd --- /dev/null +++ b/selfdrive/assets/img_experimental.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/img_experimental_grey.svg new file mode 100644 index 0000000000..dc87105ac5 --- /dev/null +++ b/selfdrive/assets/img_experimental_grey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/img_experimental_white.svg new file mode 100644 index 0000000000..ae4f18fde2 --- /dev/null +++ b/selfdrive/assets/img_experimental_white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 84e055752a..669c214746 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -58,7 +58,7 @@ qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc"] + "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc"] qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) if GetOption('test'): qt_src.remove("main.cc") # replaced by test_runner diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 3fe00e6ed9..3f3c9a5885 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -4,6 +4,7 @@ #include #include +#include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/drive_stats.h" #include "selfdrive/ui/qt/widgets/prime.h" @@ -22,7 +23,8 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { slayout = new QStackedLayout(); main_layout->addLayout(slayout); - home = new OffroadHome(); + home = new OffroadHome(this); + QObject::connect(home, &OffroadHome::openSettings, this, &HomeWindow::openSettings); slayout->addWidget(home); onroad = new OnroadWindow(this); @@ -128,11 +130,24 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { main_layout->addSpacing(25); center_layout = new QStackedLayout(); + // Vertical experimental button and drive stats layout + QWidget* statsAndExperimentalModeButtonWidget = new QWidget(this); + QVBoxLayout* statsAndExperimentalModeButton = new QVBoxLayout(statsAndExperimentalModeButtonWidget); + statsAndExperimentalModeButton->setSpacing(30); + statsAndExperimentalModeButton->setMargin(0); + + ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); + QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); + + statsAndExperimentalModeButton->addWidget(experimental_mode, 1); + statsAndExperimentalModeButton->addWidget(new DriveStats, 1); + + // Horizontal experimental + drive stats and setup widget QWidget* statsAndSetupWidget = new QWidget(this); QHBoxLayout* statsAndSetup = new QHBoxLayout(statsAndSetupWidget); statsAndSetup->setMargin(0); statsAndSetup->setSpacing(30); - statsAndSetup->addWidget(new DriveStats, 1); + statsAndSetup->addWidget(statsAndExperimentalModeButtonWidget, 1); statsAndSetup->addWidget(new SetupWidget); center_layout->addWidget(statsAndSetupWidget); diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 6636da56ec..ed1c215467 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -22,6 +22,9 @@ class OffroadHome : public QFrame { public: explicit OffroadHome(QWidget* parent = 0); +signals: + void openSettings(int index = 0, const QString ¶m = ""); + private: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; @@ -45,7 +48,7 @@ public: explicit HomeWindow(QWidget* parent = 0); signals: - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void closeSettings(); public slots: diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc new file mode 100644 index 0000000000..f73149cdf2 --- /dev/null +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -0,0 +1,75 @@ +#include "selfdrive/ui/qt/offroad/experimental_mode.h" + +#include +#include +#include +#include + +#include "selfdrive/ui/ui.h" + +ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { + chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + + // go to toggles and expand experimental mode description + connect(this, &QPushButton::clicked, [=]() { emit openSettings(2, "ExperimentalMode"); }); + + setFixedHeight(125); + QHBoxLayout *main_layout = new QHBoxLayout; + main_layout->setContentsMargins(horizontal_padding, 0, horizontal_padding, 0); + + mode_label = new QLabel; + mode_icon = new QLabel; + mode_icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + + main_layout->addWidget(mode_label, 1, Qt::AlignLeft); + main_layout->addWidget(mode_icon, 0, Qt::AlignRight); + + setLayout(main_layout); + + setStyleSheet(R"( + QPushButton { + border: none; + } + + QLabel { + font-size: 45px; + font-weight: 300; + text-align: left; + font-family: JetBrainsMono; + color: #000000; + } + )"); +} + +void ExperimentalModeButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::Antialiasing); + + QPainterPath path; + path.addRoundedRect(rect(), 10, 10); + + // gradient + bool pressed = isDown(); + QLinearGradient gradient(rect().left(), 0, rect().right(), 0); + if (experimental_mode) { + gradient.setColorAt(0, QColor(255, 155, 63, pressed ? 0xcc : 0xff)); + gradient.setColorAt(1, QColor(219, 56, 34, pressed ? 0xcc : 0xff)); + } else { + gradient.setColorAt(0, QColor(20, 255, 171, pressed ? 0xcc : 0xff)); + gradient.setColorAt(1, QColor(35, 149, 255, pressed ? 0xcc : 0xff)); + } + p.fillPath(path, gradient); + + // vertical line + p.setPen(QPen(QColor(0, 0, 0, 0x4d), 3, Qt::SolidLine)); + int line_x = rect().right() - img_width - (2 * horizontal_padding); + p.drawLine(line_x, rect().bottom(), line_x, rect().top()); +} + +void ExperimentalModeButton::showEvent(QShowEvent *event) { + experimental_mode = params.getBool("ExperimentalMode"); + mode_icon->setPixmap(experimental_mode ? experimental_pixmap : chill_pixmap); + mode_label->setText(experimental_mode ? tr("EXPERIMENTAL MODE ON") : tr("CHILL MODE ON")); +} diff --git a/selfdrive/ui/qt/offroad/experimental_mode.h b/selfdrive/ui/qt/offroad/experimental_mode.h new file mode 100644 index 0000000000..bfb7638bbe --- /dev/null +++ b/selfdrive/ui/qt/offroad/experimental_mode.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "common/params.h" + +class ExperimentalModeButton : public QPushButton { + Q_OBJECT + +public: + explicit ExperimentalModeButton(QWidget* parent = 0); + +signals: + void openSettings(int index = 0, const QString &toggle = ""); + +private: + void showEvent(QShowEvent *event) override; + + Params params; + bool experimental_mode; + int img_width = 100; + int horizontal_padding = 30; + QPixmap experimental_pixmap; + QPixmap chill_pixmap; + QLabel *mode_label; + QLabel *mode_icon; + +protected: + void paintEvent(QPaintEvent *event) override; +}; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 85b09dc183..01cb0ea720 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -39,7 +39,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "ExperimentalMode", tr("Experimental Mode"), "", - "../assets/offroad/icon_road.png", + "../assets/img_experimental_white.svg", }, { "ExperimentalLongitudinalEnabled", @@ -100,6 +100,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } // Toggles with confirmation dialogs + toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); toggles["ExperimentalMode"]->setConfirmation(true, true); toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); @@ -108,6 +109,10 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }); } +void TogglesPanel::expandToggleDescription(const QString ¶m) { + toggles[param.toStdString()]->showDescription(); +} + void TogglesPanel::showEvent(QShowEvent *event) { updateToggles(); } @@ -299,8 +304,15 @@ void DevicePanel::poweroff() { } void SettingsWindow::showEvent(QShowEvent *event) { - panel_widget->setCurrentIndex(0); - nav_btns->buttons()[0]->setChecked(true); + setCurrentPanel(0); +} + +void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { + panel_widget->setCurrentIndex(index); + nav_btns->buttons()[index]->setChecked(true); + if (!param.isEmpty()) { + emit expandToggleDescription(param); + } } SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { @@ -341,10 +353,13 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); + TogglesPanel *toggles = new TogglesPanel(this); + QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); + QList> panels = { {tr("Device"), device}, {tr("Network"), new Networking(this)}, - {tr("Toggles"), new TogglesPanel(this)}, + {tr("Toggles"), toggles}, {tr("Software"), new SoftwarePanel(this)}, }; diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 4177f28cf4..c63be4e138 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -17,6 +17,7 @@ class SettingsWindow : public QFrame { public: explicit SettingsWindow(QWidget *parent = 0); + void setCurrentPanel(int index, const QString ¶m = ""); protected: void showEvent(QShowEvent *event) override; @@ -25,6 +26,7 @@ signals: void closeSettings(); void reviewTrainingGuide(); void showDriverView(); + void expandToggleDescription(const QString ¶m); private: QPushButton *sidebar_alert_widget; @@ -56,6 +58,9 @@ public: explicit TogglesPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; +public slots: + void expandToggleDescription(const QString ¶m); + private: Params params; std::map toggles; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index fcf29181e7..50f891dd56 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -174,6 +174,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par pm = std::make_unique>({"uiDebug"}); engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); + experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size - 5, img_size - 5}); dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size}); } @@ -378,8 +379,9 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { // engage-ability icon if (engageable) { + SubMaster &sm = *(uiState()->sm); drawIcon(p, rect().right() - radius / 2 - bdr_s * 2, radius / 2 + int(bdr_s * 1.5), - engage_img, bg_colors[status], 1.0); + sm["controlsState"].getControlsState().getExperimentalMode() ? experimental_img : engage_img, blackColor(166), 1.0); } // dm icon @@ -409,7 +411,7 @@ void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QB p.setBrush(bg); p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius); p.setOpacity(opacity); - p.drawPixmap(x - img_size / 2, y - img_size / 2, img); + p.drawPixmap(x - img.size().width() / 2, y - img.size().height() / 2, img); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 7edca6b3d5..9e18355970 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -51,6 +51,7 @@ private: void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); QPixmap engage_img; + QPixmap experimental_img; QPixmap dm_img; const int radius = 192; const int img_size = (radius / 2) * 1.5; diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 53ad7467ac..fb96e1d540 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -20,7 +20,7 @@ public: explicit Sidebar(QWidget* parent = 0); signals: - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void valueChanged(); public slots: diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 04ce15ef23..198b1edbf6 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -53,6 +53,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { QFontDatabase::addApplicationFont("../assets/fonts/Inter-Regular.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-SemiBold.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-Thin.ttf"); + QFontDatabase::addApplicationFont("../assets/fonts/JetBrainsMono-Medium.ttf"); // no outline to prevent the focus rectangle setStyleSheet(R"( @@ -64,8 +65,9 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_NoSystemBackground); } -void MainWindow::openSettings() { +void MainWindow::openSettings(int index, const QString ¶m) { main_layout->setCurrentWidget(settingsWindow); + settingsWindow->setCurrentPanel(index, param); } void MainWindow::closeSettings() { diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 0bd328aa8a..71fc466c20 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -15,7 +15,7 @@ public: private: bool eventFilter(QObject *obj, QEvent *event) override; - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void closeSettings(); Device device; diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 21fdc48fef..963eeadd81 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -281,6 +281,17 @@ カメラを起動しています
+ + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 57c688ffe6..9defc2c36f 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -281,6 +281,17 @@ 카메라 시작중 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 2d7195e0d2..a1c966da45 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -281,6 +281,17 @@ câmera iniciando + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index fb9b6dd6f5..e77b3ef63d 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -281,6 +281,17 @@ 正在启动相机 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 86d5482355..f667fdc978 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -281,6 +281,17 @@ 開啟相機中 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + + + + CHILL MODE ON + + + InputDialog From 1899d439f4aaf8f218ad7d40d8d70bec0d6f151a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 23:11:53 -0800 Subject: [PATCH 047/185] Multilang: refactor experimental description (#26518) * refactor e2e description * forgot to update --- selfdrive/ui/qt/offroad/settings.cc | 23 +++++++++++------------ selfdrive/ui/translations/main_ja.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_ko.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_pt-BR.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_zh-CHS.ts | 22 +++++++++++++++++++--- selfdrive/ui/translations/main_zh-CHT.ts | 22 +++++++++++++++++++--- 6 files changed, 106 insertions(+), 27 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 01cb0ea720..330f636e2b 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -120,18 +120,17 @@ void TogglesPanel::showEvent(QShowEvent *event) { void TogglesPanel::updateToggles() { auto e2e_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; - const QString e2e_description = tr("\ - openpilot defaults to driving in chill mode.\ - Experimental mode enables alpha-level features that aren't ready for chill mode. \ - Experimental features are listed below: \ -
\ -

🌮 End-to-End Longitudinal Control 🌮

\ - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. \ - Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. \ -
\ -

New Driving Visualization

\ - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.\ - "); + const QString e2e_description = QString("%1
" + "

%2


" + "%3
" + "

%4


" + "%5") + .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) + .arg(tr("🌮 End-to-End Longitudinal Control 🌮")) + .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.")) + .arg(tr("New Driving Visualization")) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.");) auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 963eeadd81..2e01ce0dd1 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1018,15 +1018,31 @@ location set
- openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.
diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 9defc2c36f..8ce618edff 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1018,15 +1018,31 @@ location set - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index a1c966da45..2dc1538017 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1022,15 +1022,31 @@ trabalho definido - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index e77b3ef63d..fe569e1fb0 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1016,15 +1016,31 @@ location set - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index f667fdc978..3ec67f3961 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1018,15 +1018,31 @@ location set - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. + Enable experimental longitudinal control to allow experimental mode. - Enable experimental longitudinal control to allow experimental mode. + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + + + + 🌮 End-to-End Longitudinal Control 🌮 + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + + + + New Driving Visualization + + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. From d8bc69c788c581485ac9e65e8f6d06e4c368935c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 15 Nov 2022 23:52:47 -0800 Subject: [PATCH 048/185] Multilang: add missing translations (#26519) * add back missing japanese translations that haven't been changed * other languages * fix --- selfdrive/ui/qt/offroad/settings.cc | 2 +- selfdrive/ui/translations/main_ja.ts | 4 ++-- selfdrive/ui/translations/main_ko.ts | 4 ++-- selfdrive/ui/translations/main_pt-BR.ts | 4 ++-- selfdrive/ui/translations/main_zh-CHS.ts | 4 ++-- selfdrive/ui/translations/main_zh-CHT.ts | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 330f636e2b..bde8628dc4 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -130,7 +130,7 @@ void TogglesPanel::updateToggles() { .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.")) .arg(tr("New Driving Visualization")) - .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.");) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 2e01ce0dd1..ebf40cc5c5 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -1027,11 +1027,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 エンドツーエンドアクセル制御 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 8ce618edff..98d46149dd 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1027,11 +1027,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험 모드의 특징은 아래에 나열되어 있습니다 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 E2E 롱컨트롤 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 2dc1538017..4a698947f1 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -1031,11 +1031,11 @@ trabalho definido openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 Controle Longitudinal de Ponta a Ponta 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index fe569e1fb0..dc94fbc953 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1025,11 +1025,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: 🌮 End-to-End Longitudinal Control 🌮 - + 🌮 端到端(End-to-End) 纵向控制 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 3ec67f3961..46fb5578a3 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -1027,11 +1027,11 @@ location set openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - + openpilot 默認以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: 🌮 End-to-End Longitudinal Control 🌮 - + 🌮端到端縱向控制🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. From fe2368c9eb14ee49eb59af6d892becdb4d449443 Mon Sep 17 00:00:00 2001 From: Oxygen Date: Wed, 16 Nov 2022 19:11:45 +0800 Subject: [PATCH 049/185] Updated main_zh-CHS.ts (#26524) --- selfdrive/ui/translations/main_zh-CHS.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index dc94fbc953..31202e45f2 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 试验模式运行 CHILL MODE ON - + 轻松模式运行 @@ -1013,15 +1013,15 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + 针对此车辆,openpilot默认使用车辆自带的ACC,而非openpilot的纵向控制。启用此选项将切换到openpilot纵向控制。当使用试验性的openpilot纵向控制时,建议同时启用试验模式。 Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 Enable experimental longitudinal control to allow experimental mode. - + 启用试验性的纵向控制,以便允许使用试验模式。 openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: @@ -1033,15 +1033,15 @@ location set Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - + 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 New Driving Visualization - + 新驾驶视角 The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + 当低速行驶时,驾驶视角将切换到前向广角摄像头,便于更完整地显示转向路径。右上角将显示试验模式图标。 From e099e42147af23a700bf4b64e08540574aa1d285 Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Wed, 16 Nov 2022 18:12:31 +0100 Subject: [PATCH 050/185] cabana: default factor to 1 (#26521) --- tools/cabana/detailwidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 192d1fd66c..8a221ed9db 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -235,7 +235,7 @@ void DetailWidget::addSignal(int start_bit, int size, bool little_endian) { } } } - Signal sig = {.is_little_endian = little_endian}; + Signal sig = {.is_little_endian = little_endian, .factor = 1}; for (int i = 1; /**/; ++i) { sig.name = "NEW_SIGNAL_" + std::to_string(i); if (msg->sigs.count(sig.name.c_str()) == 0) break; From e79a384a9bdc0f7421e2a914abf88a302a7e32a2 Mon Sep 17 00:00:00 2001 From: Lee Jong Mun <43285072+crwusiz@users.noreply.github.com> Date: Thu, 17 Nov 2022 02:13:41 +0900 Subject: [PATCH 051/185] Multilang: kor translation update (#26522) --- selfdrive/ui/translations/main_ko.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 98d46149dd..bda64c53ab 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 실험적 모드 사용 CHILL MODE ON - + 안정적 모드 사용 @@ -875,7 +875,7 @@ location set Uninstall - 삭제 + 제거 @@ -1015,15 +1015,15 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + 이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤을 사용하려면 이 옵션을 활성화하세요. 실험적 openpilot 롱컨트롤을 사용하는 경우 실험적 모드를 활성화 하세요. Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + 차량의 기본 ACC가 롱컨트롤에 사용되기 때문에 현재 이 차량에서는 실험적 모드를 사용할수 없습니다. Enable experimental longitudinal control to allow experimental mode. - + 실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요. openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: @@ -1035,15 +1035,15 @@ location set Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - + 주행모델이 가속과 감속을 제어하도록 합니다. openpilot은 신호등과 정지표지판을 보고 멈추는 것을 포함하여 운전자가 생각하는것처럼 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 상한선으로만 작용합니다. 이것은 알파 기능이므로 사용에 주의해야 합니다. New Driving Visualization - + 새로운 주행 시각화 The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + 주행 시각화는 저속에서 도로를 향하는 광각 카메라로 전환되어 일부 회전을 더 잘 보여줍니다. 실험적 모드 로고도 우측상단에 표시됩니다. From 8f9f015567a9f2dad66d31c25c514c436d1ba862 Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Wed, 16 Nov 2022 18:25:36 +0100 Subject: [PATCH 052/185] add missing include (#26520) --- selfdrive/ui/qt/offroad/experimental_mode.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index f73149cdf2..b99220c1d1 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "selfdrive/ui/ui.h" From c3e2d35963a95bfeaf99048decbd6066c52113d8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 16 Nov 2022 14:20:00 -0800 Subject: [PATCH 053/185] offroad ui: update chill mode icon (#26523) * Add files via upload * Add files via upload * Delete thick couch.svg * Rename img_couch.svg to img_couch_old.svg * Rename thickerer couch.svg to img_couch.svg * Add files via upload * Rename img_couch.svg to img_couch_alt.svg * Rename best couch.svg to img_couch.svg * delete old files --- selfdrive/assets/img_couch.svg | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg index 5b3c048318..2e86012809 100644 --- a/selfdrive/assets/img_couch.svg +++ b/selfdrive/assets/img_couch.svg @@ -1,3 +1,4 @@ - - + + + From e3c913bfa1d8c534f351d6ead5c2156ee4ee4ffa Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 17 Nov 2022 07:43:49 +0800 Subject: [PATCH 054/185] Cabana: sort signal by start bit and keep cursor position after save (#26529) sort signals by start bit and keep cursor position after save --- tools/cabana/binaryview.cc | 14 +++++++------- tools/cabana/dbcmanager.cc | 9 +++++++++ tools/cabana/dbcmanager.h | 1 + tools/cabana/detailwidget.cc | 6 +++--- tools/cabana/signaledit.cc | 11 +++++++++-- tools/cabana/signaledit.h | 2 ++ 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index bcd2b88a81..7707316877 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -166,19 +166,19 @@ void BinaryViewModel::setMessage(const QString &message_id) { row_count = dbc_msg->size; items.resize(row_count * column_count); int i = 0; - for (auto &[name, sig] : dbc_msg->sigs) { - auto [start, end] = getSignalRange(&sig); + for (auto sig : dbc_msg->getSignals()) { + auto [start, end] = getSignalRange(sig); for (int j = start; j <= end; ++j) { - int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j; + int bit_index = sig->is_little_endian ? bigEndianBitIndex(j) : j; int idx = column_count * (bit_index / 8) + bit_index % 8; if (idx >= items.size()) { - qWarning() << "signal " << name << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size; + qWarning() << "signal " << sig->name.c_str() << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; break; } - if (j == start) sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; - if (j == end) sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; + if (j == start) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; + if (j == end) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; items[idx].bg_color = getColor(i); - items[idx].sigs.push_back(&sig); + items[idx].sigs.push_back(sig); } ++i; } diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index 18f103d34c..e7d3ead9c6 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -107,6 +107,15 @@ DBCManager *dbc() { return &dbc_manager; } +// DBCMsg + +std::vector DBCMsg::getSignals() const { + std::vector ret; + for (auto &[name, sig] : sigs) ret.push_back(&sig); + std::sort(ret.begin(), ret.end(), [](auto l, auto r) { return l->start_bit < r->start_bit; }); + return ret; +} + // helper functions static QVector BIG_ENDIAN_START_BITS = []() { diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index b1d2082969..4e0bc91069 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -9,6 +9,7 @@ struct DBCMsg { QString name; uint32_t size; std::map sigs; + std::vector getSignals() const; }; class DBCManager : public QObject { diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 8a221ed9db..99cf45f5fa 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -149,7 +149,7 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { QStringList warnings; const DBCMsg *msg = dbc()->msg(msg_id); if (msg) { - for (auto &[name, sig] : msg->sigs) { + for (auto sig : msg->getSignals()) { SignalEdit *form = i < signal_list.size() ? signal_list[i] : nullptr; if (!form) { form = new SignalEdit(i); @@ -162,8 +162,8 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { signals_layout->addWidget(form); signal_list.push_back(form); } - form->setSignal(msg_id, &sig); - form->setChartOpened(charts->isChartOpened(msg_id, &sig)); + form->setSignal(msg_id, sig); + form->setChartOpened(charts->isChartOpened(msg_id, sig)); ++i; } if (msg->size != can->lastMessage(msg_id).dat.size()) diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index eb22b78d5a..6e4cf2a83a 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -116,11 +116,16 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa hline->setFrameShadow(QFrame::Sunken); main_layout->addWidget(hline); + save_timer = new QTimer(this); + save_timer->setInterval(300); + save_timer->setSingleShot(true); + save_timer->callOnTimeout(this, &SignalEdit::saveSignal); + QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked); }); QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); }); QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); }); - QObject::connect(form, &SignalForm::changed, this, &SignalEdit::saveSignal); + QObject::connect(form, &SignalForm::changed, [this]() { save_timer->start(); }); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -174,7 +179,9 @@ void SignalEdit::setChartOpened(bool opened) { void SignalEdit::updateForm(bool visible) { if (visible && sig) { form->changed_by_user = false; - form->name->setText(sig->name.c_str()); + if (form->name->text() != sig->name.c_str()) { + form->name->setText(sig->name.c_str()); + } form->endianness->setCurrentIndex(sig->is_little_endian ? 0 : 1); form->sign->setCurrentIndex(sig->is_signed ? 0 : 1); form->factor->setText(QString::number(sig->factor)); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index da0b9758c7..f889a9c096 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "selfdrive/ui/qt/widgets/controls.h" @@ -57,6 +58,7 @@ protected: QLabel *icon; int form_idx = 0; QToolButton *plot_btn; + QTimer *save_timer; }; class SignalFindDlg : public QDialog { From b6de850dd7b9935ded59895a84b3edf5c767dae0 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 17 Nov 2022 07:45:08 +0800 Subject: [PATCH 055/185] Cabana: save & restore splitter state (#26526) save & restore splitter state --- tools/cabana/mainwin.cc | 4 ++++ tools/cabana/settings.cc | 2 ++ tools/cabana/settings.h | 2 ++ 3 files changed, 8 insertions(+) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 7781ab3b75..324323ac44 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -55,6 +55,9 @@ MainWindow::MainWindow() : QMainWindow() { charts_widget = new ChartsWidget(this); detail_widget = new DetailWidget(charts_widget, this); splitter->addWidget(detail_widget); + if (!settings.splitter_state.isEmpty()) { + splitter->restoreState(settings.splitter_state); + } main_layout->addWidget(splitter); // right widgets @@ -247,6 +250,7 @@ void MainWindow::closeEvent(QCloseEvent *event) { if (floating_window) floating_window->deleteLater(); + settings.splitter_state = splitter->saveState(); settings.save(); QWidget::closeEvent(event); } diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index b3a4ed4872..be806aa705 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -21,6 +21,7 @@ void Settings::save() { s.setValue("chart_theme", chart_theme); s.setValue("max_chart_x_range", max_chart_x_range); s.setValue("last_dir", last_dir); + s.setValue("splitter_state", splitter_state); } void Settings::load() { @@ -32,6 +33,7 @@ void Settings::load() { chart_theme = s.value("chart_theme", 0).toInt(); max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt(); last_dir = s.value("last_dir", QDir::homePath()).toString(); + splitter_state = s.value("splitter_state").toByteArray(); } // SettingsDlg diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index e08d0ae55e..624a1ce33d 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -19,6 +20,7 @@ public: int chart_theme = 0; int max_chart_x_range = 3 * 60; // 3 minutes QString last_dir; + QByteArray splitter_state; signals: void changed(); From 2cd1571f4ae9f8aa1aa5879aa8c9107028d7f937 Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Thu, 17 Nov 2022 00:45:28 +0100 Subject: [PATCH 056/185] Discover Qt paths using qmake (#26501) * discover qt paths using qmake * fix device build * use subprocess.check_output --- SConstruct | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/SConstruct b/SConstruct index 74680c0598..033e10a1f0 100644 --- a/SConstruct +++ b/SConstruct @@ -298,12 +298,15 @@ if arch == "Darwin": qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) else: - qt_env['QTDIR'] = "/usr" + qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip() + qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip() + + qt_env['QTDIR'] = qt_install_prefix qt_dirs = [ - f"/usr/include/{real_arch}-linux-gnu/qt5", - f"/usr/include/{real_arch}-linux-gnu/qt5/QtGui/5.12.8/QtGui", + f"{qt_install_headers}", + f"{qt_install_headers}/QtGui/5.12.8/QtGui", ] - qt_dirs += [f"/usr/include/{real_arch}-linux-gnu/qt5/Qt{m}" for m in qt_modules] + qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] qt_libs = [f"Qt5{m}" for m in qt_modules] if arch == "larch64": From 66edb15b8f558ce81b5a57af4e60fc24e1fc489c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 17 Nov 2022 07:51:05 +0800 Subject: [PATCH 057/185] Cabana: improve binary view (#26525) * improve * set minimum width * click signal to open&hide form * disable vertical header's click --- tools/cabana/binaryview.cc | 94 +++++++++++++++--------------------- tools/cabana/binaryview.h | 5 +- tools/cabana/canmessages.h | 6 --- tools/cabana/detailwidget.cc | 12 ++++- tools/cabana/detailwidget.h | 1 + tools/cabana/signaledit.cc | 3 +- 6 files changed, 54 insertions(+), 67 deletions(-) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 7707316877..dae4591976 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -23,11 +23,13 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { delegate = new BinaryItemDelegate(this); setItemDelegate(delegate); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + verticalHeader()->setSectionsClickable(false); horizontalHeader()->hide(); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); setFrameShape(QFrame::NoFrame); + setShowGrid(false); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); setMouseTracking(true); } @@ -46,46 +48,30 @@ void BinaryView::setSelection(const QRect &rect, QItemSelectionModel::SelectionF return; QItemSelection selection; - auto [tl, br] = std::minmax(anchor_index, index); - if ((resize_sig && resize_sig->is_little_endian) || (!resize_sig && index < anchor_index)) { - // little_endian selection - if (tl.row() == br.row()) { - selection.merge({model->index(tl.row(), tl.column()), model->index(tl.row(), br.column())}, flags); - } else { - for (int row = tl.row(); row <= br.row(); ++row) { - int left_col = (row == br.row()) ? br.column() : 0; - int right_col = (row == tl.row()) ? tl.column() : 7; - selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags); - } - } - } else { - // big endian selection - for (int row = tl.row(); row <= br.row(); ++row) { - int left_col = (row == tl.row()) ? tl.column() : 0; - int right_col = (row == br.row()) ? br.column() : 7; - selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags); - } + auto [start, size, is_lb] = getSelection(index); + for (int i = start; i < start + size; ++i) { + auto idx = model->bitIndex(i, is_lb); + selection.merge({idx, idx}, flags); } selectionModel()->select(selection, flags); } void BinaryView::mousePressEvent(QMouseEvent *event) { delegate->setSelectionColor(style()->standardPalette().color(QPalette::Active, QPalette::Highlight)); - if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) { + if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) { anchor_index = index; auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); - if (item && item->sigs.size() > 0) { - int bit_idx = get_bit_index(anchor_index, true); - for (auto s : item->sigs) { - if (bit_idx == s->lsb || bit_idx == s->msb) { - resize_sig = s; - delegate->setSelectionColor(item->bg_color); - break; - } + int bit_idx = get_bit_index(anchor_index, true); + for (auto s : item->sigs) { + if (bit_idx == s->lsb || bit_idx == s->msb) { + anchor_index = model->bitIndex(bit_idx == s->lsb ? s->msb : s->lsb, true); + resize_sig = s; + delegate->setSelectionColor(item->bg_color); + break; } } } - QTableView::mousePressEvent(event); + event->accept(); } void BinaryView::mouseMoveEvent(QMouseEvent *event) { @@ -104,28 +90,14 @@ void BinaryView::mouseReleaseEvent(QMouseEvent *event) { auto release_index = indexAt(event->pos()); if (release_index.isValid() && anchor_index.isValid()) { - if (release_index.column() == 8) { - release_index = model->index(release_index.row(), 7); - } - bool little_endian = (resize_sig && resize_sig->is_little_endian) || (!resize_sig && release_index < anchor_index); - int release_bit_idx = get_bit_index(release_index, little_endian); - int archor_bit_idx = get_bit_index(anchor_index, little_endian); - if (resize_sig) { - auto [sig_from, sig_to] = getSignalRange(resize_sig); - int start_bit, end_bit; - if (archor_bit_idx == sig_from) { - std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_to); - if (start_bit >= sig_from && start_bit <= sig_to) - start_bit = std::min(start_bit + 1, sig_to); - } else { - std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_from); - if (end_bit >= sig_from && end_bit <= sig_to) - end_bit = std::max(end_bit - 1, sig_from); - } - emit resizeSignal(resize_sig, start_bit, end_bit - start_bit + 1); + if (selectionModel()->hasSelection()) { + auto [start_bit, size, is_lb] = getSelection(release_index); + resize_sig ? emit resizeSignal(resize_sig, start_bit, size) + : emit addSignal(start_bit, size, is_lb); } else { - auto [sart_bit, end_bit] = std::minmax(archor_bit_idx, release_bit_idx); - emit addSignal(sart_bit, end_bit - sart_bit + 1, little_endian); + auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); + if (item && item->sigs.size() > 0) + emit signalClicked(item->sigs.back()); } } clearSelection(); @@ -156,6 +128,17 @@ QSet BinaryView::getOverlappingSignals() const { return overlapping; } +std::tuple BinaryView::getSelection(QModelIndex index) { + if (index.column() == 8) { + index = model->index(index.row(), 7); + } + bool is_lb = (resize_sig && resize_sig->is_little_endian) || (!resize_sig && index < anchor_index); + int cur_bit_idx = get_bit_index(index, is_lb); + int anchor_bit_idx = get_bit_index(anchor_index, is_lb); + auto [start_bit, end_bit] = std::minmax(cur_bit_idx, anchor_bit_idx); + return {start_bit, end_bit - start_bit + 1, is_lb}; +} + // BinaryViewModel void BinaryViewModel::setMessage(const QString &message_id) { @@ -203,7 +186,7 @@ void BinaryViewModel::updateState() { char hex[3] = {'\0'}; for (int i = 0; i < std::min(binary.size(), row_count); ++i) { for (int j = 0; j < column_count - 1; ++j) { - items[i * column_count + j].val = QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0'); + items[i * column_count + j].val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0'; } hex[0] = toHex(binary[i] >> 4); hex[1] = toHex(binary[i] & 0xf); @@ -250,17 +233,16 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->save(); // background - bool hover = item->sigs.contains(bin_view->hoveredSignal()); - QColor bg_color = hover ? hoverColor(item->bg_color) : item->bg_color; if (option.state & QStyle::State_Selected) { - bg_color = selection_color; + painter->fillRect(option.rect, selection_color); + } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { + painter->fillRect(option.rect, item->bg_color); } - painter->fillRect(option.rect, bg_color); // text if (index.column() == 8) { // hex column painter->setFont(hex_font); - } else if (hover) { + } else if (option.state & QStyle::State_Selected || (!bin_view->resize_sig && item->sigs.contains(bin_view->hovered_sig))) { painter->setPen(Qt::white); } painter->drawText(option.rect, Qt::AlignCenter, item->val); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 2d6fc5c18b..0ea111d917 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -32,6 +32,7 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } + inline QModelIndex bitIndex(int bit, bool is_lb) const { return index(bit / 8, is_lb ? (7 - bit % 8) : bit % 8); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { return createIndex(row, column, (void *)&items[row * column_count + column]); } @@ -63,15 +64,16 @@ public: void setMessage(const QString &message_id); void highlight(const Signal *sig); QSet getOverlappingSignals() const; - inline const Signal *hoveredSignal() const { return hovered_sig; } inline void updateState() { model->updateState(); } signals: + void signalClicked(const Signal *sig); void signalHovered(const Signal *sig); void addSignal(int start_bit, int size, bool little_endian); void resizeSignal(const Signal *sig, int from, int size); private: + std::tuple getSelection(QModelIndex index); void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; @@ -83,4 +85,5 @@ private: BinaryItemDelegate *delegate; const Signal *resize_sig = nullptr; const Signal *hovered_sig = nullptr; + friend class BinaryItemDelegate; }; diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index ff41edad54..4cb0f403a0 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -80,11 +80,5 @@ inline const QString &getColor(int i) { return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; } -inline QColor hoverColor(const QColor &color) { - QColor c = color.convertTo(QColor::Hsv); - c.setHsv(color.hue(), 180, 180); - return c; -} - // A global pointer referring to the unique CANMessages object extern CANMessages *can; diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 99cf45f5fa..52ae530a56 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -17,6 +17,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { undo_stack = new QUndoStack(this); + setMinimumWidth(500); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setSpacing(0); @@ -90,6 +91,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart tab_widget->addTab(history_log, "Logs"); main_layout->addWidget(tab_widget); + QObject::connect(binary_view, &BinaryView::signalClicked, this, &DetailWidget::showForm); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); }); @@ -197,9 +199,15 @@ void DetailWidget::updateState(const QHash * msgs) { void DetailWidget::showFormClicked() { auto s = qobject_cast(sender()); + showForm(s->sig); +} + +void DetailWidget::showForm(const Signal *sig) { setUpdatesEnabled(false); - for (auto f : signal_list) - f->updateForm(f == s && !f->isFormVisible()); + for (auto f : signal_list) { + f->updateForm(f->sig == sig && !f->isFormVisible()); + if (f->sig == sig) scroll->ensureWidgetVisible(f); + } setUpdatesEnabled(true); } diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 4346d1c5d5..41fa0edd41 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -28,6 +28,7 @@ public: QUndoStack *undo_stack = nullptr; private: + void showForm(const Signal *sig); void showFormClicked(); void updateChartState(const QString &id, const Signal *sig, bool opened); void showTabBarContextMenu(const QPoint &pt); diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 6e4cf2a83a..a0660901cf 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -196,9 +196,8 @@ void SignalEdit::updateForm(bool visible) { } void SignalEdit::signalHovered(const Signal *s) { - auto bg_color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx)); auto color = sig == s ? "white" : "black"; - color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(bg_color.name())); + color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(getColor(form_idx))); } void SignalEdit::enterEvent(QEvent *event) { From 187f8c177a1a4d507a4ca145463f852cee3ce7aa Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Thu, 17 Nov 2022 03:03:13 -0700 Subject: [PATCH 058/185] Add video_link for GMC Sierra (#26506) * Add video_link for GMS Sierra * fix precommit Co-authored-by: Shane Smiskol --- selfdrive/car/gm/values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 03392ba0f9..35f87307d6 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -100,7 +100,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), CAR.SILVERADO: [ GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II", footnotes=[], harness=Harness.gm), - GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", footnotes=[], harness=Harness.gm), + GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE", footnotes=[], harness=Harness.gm), ], CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22", footnotes=[], harness=Harness.gm), } From 37ad8f4f3fe0e730c46ba60f60e9c8db20c4c20e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 18 Nov 2022 02:52:04 +0800 Subject: [PATCH 059/185] Cabana: support for multiple series in chart (#26538) * customize axis x & y * new function chartView::addSignal * support multiple series in chartView * more * show legend * update changed signals only * fix z value * cleanup * limit trake line in plot area * display signal name in value_text *   * fix axis y * add spaces * cache min max value for axis y * cleanup * better values text * remove force_update --- tools/cabana/chartswidget.cc | 316 +++++++++++++++++++++++------------ tools/cabana/chartswidget.h | 52 ++++-- tools/cabana/signaledit.cc | 7 +- tools/cabana/signaledit.h | 2 +- 4 files changed, 253 insertions(+), 124 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 06387b3585..4bd398a93b 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -4,11 +4,9 @@ #include #include #include +#include #include -#include -#include #include -#include // ChartsWidget @@ -19,7 +17,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // toolbar QToolBar *toolbar = new QToolBar(tr("Charts"), this); title_label = new QLabel(); - title_label->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); + title_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); toolbar->addWidget(title_label); toolbar->addWidget(range_label = new QLabel()); reset_zoom_btn = toolbar->addAction("⟲"); @@ -42,21 +40,10 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { main_layout->addWidget(charts_scroll); - QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { removeAll(nullptr); }); - QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll); - QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartsWidget::signalUpdated); - QObject::connect(dbc(), &DBCManager::msgRemoved, [this](uint32_t address) { - for (auto c : charts.toVector()) - if (DBCManager::parseId(c->id).second == address) removeChart(c); - }); - QObject::connect(dbc(), &DBCManager::msgUpdated, [this](uint32_t address) { - for (auto c : charts) { - if (DBCManager::parseId(c->id).second == address) c->updateTitle(); - } - }); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged); QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState); - QObject::connect(remove_all_btn, &QAction::triggered, [this]() { removeAll(); }); + QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll); QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset); QObject::connect(dock_btn, &QAction::triggered, [this]() { emit dock(!docking); @@ -81,8 +68,8 @@ void ChartsWidget::zoomIn(double min, double max) { zoomed_range = {min, max}; is_zoomed = zoomed_range != display_range; updateToolBar(); - emit rangeChanged(min, max, is_zoomed); updateState(); + emit rangeChanged(min, max, is_zoomed); } void ChartsWidget::zoomReset() { @@ -108,13 +95,13 @@ void ChartsWidget::updateState() { if (prev_range != display_range) { QFutureSynchronizer future_synchronizer; for (auto c : charts) - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, display_range)); + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::setEventsRange, display_range)); } } const auto &range = is_zoomed ? zoomed_range : display_range; for (auto c : charts) { - c->setRange(range.first, range.second); + c->setDisplayRange(range.first, range.second); c->updateLineMarker(current_sec); } } @@ -128,49 +115,45 @@ void ChartsWidget::updateToolBar() { dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } -void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) { - auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; }); - if (it != charts.end()) { - if (!show) removeChart((*it)); - } else if (show) { - auto chart = new ChartView(id, sig, this); - chart->updateSeries(display_range); - QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); - QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); - QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); - charts_layout->insertWidget(0, chart); - charts.push_back(chart); - emit chartOpened(chart->id, chart->signal); +ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) { + for (auto c : charts) + if (c->hasSeries(id, sig)) return c; + return nullptr; +} + +void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) { + if (!show) { + if (ChartView *chart = findChart(id, sig)) { + chart->removeSeries(id, sig); + } + } else { + ChartView *chart = merge && charts.size() > 0 ? charts.back() : nullptr; + if (!chart) { + chart = new ChartView(this); + chart->setEventsRange(display_range); + QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); + QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); + QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); + QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::chartClosed); + charts_layout->insertWidget(0, chart); + charts.push_back(chart); + } + chart->addSeries(id, sig); + emit chartOpened(id, sig); updateState(); } updateToolBar(); } -bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) { - auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; }); - return it != charts.end(); -} - void ChartsWidget::removeChart(ChartView *chart) { charts.removeOne(chart); chart->deleteLater(); updateToolBar(); - emit chartClosed(chart->id, chart->signal); } -void ChartsWidget::removeAll(const Signal *sig) { +void ChartsWidget::removeAll() { for (auto c : charts.toVector()) - if (!sig || c->signal == sig) removeChart(c); -} - -void ChartsWidget::signalUpdated(const Signal *sig) { - for (auto c : charts) { - if (c->signal == sig) { - c->updateTitle(); - c->updateSeries(display_range); - c->setRange(display_range.first, display_range.second, true); - } - } + removeChart(c); } bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { @@ -183,14 +166,14 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { // ChartView -ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) - : id(id), signal(sig), QChartView(nullptr, parent) { - QLineSeries *series = new QLineSeries(); +ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { QChart *chart = new QChart(); chart->setBackgroundRoundness(0); - chart->addSeries(series); - chart->createDefaultAxes(); - chart->legend()->hide(); + axis_x = new QValueAxis(this); + axis_y = new QValueAxis(this); + chart->addAxis(axis_x, Qt::AlignBottom); + chart->addAxis(axis_y, Qt::AlignLeft); + chart->legend()->setShowToolTips(true); chart->layout()->setContentsMargins(0, 0, 0, 0); // top margin for title chart->setMargins({0, 11, 0, 0}); @@ -213,33 +196,108 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) remove_btn->setToolTip(tr("Remove Chart")); close_btn_proxy = new QGraphicsProxyWidget(chart); close_btn_proxy->setWidget(remove_btn); + close_btn_proxy->setZValue(chart->zValue() + 11); setChart(chart); setRenderHint(QPainter::Antialiasing); setRubberBand(QChartView::HorizontalRubberBand); updateFromSettings(); - updateTitle(); QTimer *timer = new QTimer(this); timer->setInterval(100); timer->setSingleShot(true); timer->callOnTimeout(this, &ChartView::adjustChartMargins); + QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved); + QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated); + QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved); + QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated); QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings); - QObject::connect(remove_btn, &QToolButton::clicked, [=]() { emit remove(id, sig); }); + QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) { // use a singleshot timer to avoid recursion call. timer->start(); }); } +ChartView::~ChartView() { + for (auto &s : sigs) + emit seriesRemoved(s.msg_id, s.sig); +} + +void ChartView::addSeries(const QString &msg_id, const Signal *sig) { + QLineSeries *series = new QLineSeries(this); + chart()->addSeries(series); + series->attachAxis(axis_x); + series->attachAxis(axis_y); + auto [source, address] = DBCManager::parseId(msg_id); + sigs.push_back({.msg_id = msg_id, .address = address, .source = source, .sig = sig, .series = series}); + updateTitle(); + updateSeries(sig); +} + +void ChartView::removeSeries(const QString &msg_id, const Signal *sig) { + auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); + if (it != sigs.end()) { + it = removeSeries(it); + } +} + +bool ChartView::hasSeries(const QString &msg_id, const Signal *sig) const { + auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); + return it != sigs.end(); +} + +QList::iterator ChartView::removeSeries(const QList::iterator &it) { + chart()->removeSeries(it->series); + it->series->deleteLater(); + emit seriesRemoved(it->msg_id, it->sig); + + auto ret = sigs.erase(it); + if (!sigs.isEmpty()) { + updateAxisY(); + } else { + emit remove(); + } + return ret; +} + +void ChartView::signalUpdated(const Signal *sig) { + auto it = std::find_if(sigs.begin(), sigs.end(), [=](auto &s) { return s.sig == sig; }); + if (it != sigs.end()) { + updateTitle(); + // TODO: don't update series if only name changed. + updateSeries(sig); + } +} + +void ChartView::signalRemoved(const Signal *sig) { + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + it = (it->sig == sig) ? removeSeries(it) : ++it; + } +} + +void ChartView::msgUpdated(uint32_t address) { + auto it = std::find_if(sigs.begin(), sigs.end(), [=](auto &s) { return s.address == address; }); + if (it != sigs.end()) + updateTitle(); +} + +void ChartView::msgRemoved(uint32_t address) { + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + it = (it->address == address) ? removeSeries(it) : ++it; + } +} + void ChartView::resizeEvent(QResizeEvent *event) { QChartView::resizeEvent(event); close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8); } void ChartView::updateTitle() { - chart()->setTitle(tr("%1 %2 %3").arg(dbc()->msg(id)->name).arg(id).arg(signal->name.c_str())); + for (auto &s : sigs) { + s.series->setName(QString("%1 %2 %3").arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id)); + } } void ChartView::updateFromSettings() { @@ -249,9 +307,15 @@ void ChartView::updateFromSettings() { line_marker->setPen(QPen(color, 2)); } -void ChartView::setRange(double min, double max, bool force_update) { - auto axis_x = dynamic_cast(chart()->axisX()); - if (force_update || (min != axis_x->min() || max != axis_x->max())) { +void ChartView::setEventsRange(const std::pair &range) { + if (range != events_range) { + events_range = range; + updateSeries(); + } +} + +void ChartView::setDisplayRange(double min, double max) { + if (min != axis_x->min() || max != axis_x->max()) { axis_x->setRange(min, max); updateAxisY(); } @@ -268,7 +332,6 @@ void ChartView::adjustChartMargins() { } void ChartView::updateLineMarker(double current_sec) { - auto axis_x = dynamic_cast(chart()->axisX()); int x = chart()->plotArea().left() + chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min()); if (int(line_marker->line().x1()) != x) { @@ -276,48 +339,72 @@ void ChartView::updateLineMarker(double current_sec) { } } -void ChartView::updateSeries(const std::pair range) { +void ChartView::updateSeries(const Signal *sig) { auto events = can->events(); if (!events) return; - vals.clear(); - vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz - auto [bus, address] = DBCManager::parseId(id); - double route_start_time = can->routeStartTime(); - Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + range.first) * 1e9); - auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); - double end_ns = (route_start_time + range.second) * 1e9; - for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - for (const auto &c : (*it)->event.getCan()) { - if (bus == c.getSrc() && address == c.getAddress()) { - auto dat = c.getDat(); - double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal); - double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds - vals.push_back({ts, value}); + for (int i = 0; i < sigs.size(); ++i) { + if (auto &s = sigs[i]; !sig || s.sig == sig) { + s.vals.clear(); + s.vals.reserve((events_range.second - events_range.first) * 1000); // [n]seconds * 1000hz + s.min_y = std::numeric_limits::max(); + s.max_y = std::numeric_limits::lowest(); + + double route_start_time = can->routeStartTime(); + Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + events_range.first) * 1e9); + auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); + double end_ns = (route_start_time + events_range.second) * 1e9; + + for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) { + if ((*it)->which == cereal::Event::Which::CAN) { + for (const auto &c : (*it)->event.getCan()) { + if (s.source == c.getSrc() && s.address == c.getAddress()) { + auto dat = c.getDat(); + double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig); + double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds + s.vals.push_back({ts, value}); + + if (value < s.min_y) s.min_y = value; + if (value > s.max_y) s.max_y = value; + } + } } } + + QLineSeries *series = (QLineSeries *)chart()->series()[i]; + series->replace(s.vals); } } - QLineSeries *series = (QLineSeries *)chart()->series()[0]; - series->replace(vals); + updateAxisY(); } // auto zoom on yaxis void ChartView::updateAxisY() { - const auto axis_x = dynamic_cast(chart()->axisX()); - const auto axis_y = dynamic_cast(chart()->axisY()); - auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - if (begin == vals.end()) - return; - - auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); - const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); - if (max->y() == min->y()) { - axis_y->setRange(min->y() - 1, max->y() + 1); + double min_y = std::numeric_limits::max(); + double max_y = std::numeric_limits::lowest(); + if (events_range == std::pair{axis_x->min(), axis_x->max()}) { + for (auto &s : sigs) { + if (s.min_y < min_y) min_y = s.min_y; + if (s.max_y > max_y) max_y = s.max_y; + } } else { - double range = max->y() - min->y(); - axis_y->setRange(min->y() - range * 0.05, max->y() + range * 0.05); + for (auto &s : sigs) { + auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); + if (begin == s.vals.end()) + return; + + auto end = std::upper_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); + const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); + if (min->y() < min_y) min_y = min->y(); + if (max->y() > max_y) max_y = max->y(); + } + } + + if (max_y == min_y) { + axis_y->setRange(min_y - 1, max_y + 1); + } else { + double range = max_y - min_y; + axis_y->setRange(min_y - range * 0.05, max_y + range * 0.05); axis_y->applyNiceNumbers(); } } @@ -333,7 +420,6 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { rubber->hide(); QRectF rect = rubber->geometry().normalized(); rect.translate(-chart()->plotArea().topLeft()); - const auto axis_x = dynamic_cast(chart()->axisX()); double min = axis_x->min() + (rect.left() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min()); double max = axis_x->min() + (rect.right() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min()); if (rubber->width() <= 0) { @@ -357,26 +443,40 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { void ChartView::mouseMoveEvent(QMouseEvent *ev) { auto rubber = findChild(); bool is_zooming = rubber && rubber->isVisible(); - if (!is_zooming) { - const auto plot_area = chart()->plotArea(); - auto axis_x = dynamic_cast(chart()->axisX()); + const auto plot_area = chart()->plotArea(); + + if (!is_zooming && plot_area.contains(ev->pos())) { double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right() - 1); double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width(); - auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); }); - if (value != vals.end()) { - QPointF pos = chart()->mapToPosition((*value)); + QStringList text_list; + QPointF pos = plot_area.bottomRight(); + double tm = 0.0; + + for (auto &s : sigs) { + auto value = std::upper_bound(s.vals.begin(), s.vals.end(), sec, [](double x, auto &p) { return x < p.x(); }); + if (value != s.vals.end()) { + text_list.push_back(QString(" %1 : %2 ").arg(sigs.size() > 1 ? s.sig->name.c_str() : "Value").arg(value->y())); + tm = value->x(); + auto y_pos = chart()->mapToPosition(*value); + if (y_pos.y() < pos.y()) pos = y_pos; + } + } + + if (!text_list.isEmpty()) { + value_text->setHtml("
 Time: " + + QString::number(tm, 'f', 3) + " 
" + text_list.join("
") + "
"); track_line->setLine(pos.x(), plot_area.top(), pos.x(), plot_area.bottom()); - track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10); - value_text->setHtml(tr("
%1, %2)
") - .arg(value->x(), 0, 'f', 3).arg(value->y())); int text_x = pos.x() + 8; - if ((text_x + value_text->boundingRect().width()) > plot_area.right()) { - text_x = pos.x() - value_text->boundingRect().width() - 8; + QRectF text_rect = value_text->boundingRect(); + if ((text_x + text_rect.width()) > plot_area.right()) { + text_x = pos.x() - text_rect.width() - 8; } - value_text->setPos(text_x, pos.y() - 10); + value_text->setPos(text_x, pos.y() - text_rect.height() / 2); + track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10); } - item_group->setVisible(value != vals.end()); + item_group->setVisible(!text_list.isEmpty()); } else { + item_group->setVisible(false); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } QChartView::mouseMoveEvent(ev); diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 20c673a757..e32072a831 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" @@ -18,35 +20,59 @@ class ChartView : public QChartView { Q_OBJECT public: - ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr); - void updateSeries(const std::pair range); - void setRange(double min, double max, bool force_update = false); + ChartView(QWidget *parent = nullptr); + ~ChartView(); + void addSeries(const QString &msg_id, const Signal *sig); + void removeSeries(const QString &msg_id, const Signal *sig); + bool hasSeries(const QString &msg_id, const Signal *sig) const; + void updateSeries(const Signal *sig = nullptr); + void setEventsRange(const std::pair &range); + void setDisplayRange(double min, double max); void updateLineMarker(double current_sec); - void updateFromSettings(); - void updateTitle(); - QString id; - const Signal *signal; + struct SigItem { + QString msg_id; + uint8_t source = 0; + uint32_t address = 0; + const Signal *sig = nullptr; + QLineSeries *series = nullptr; + double min_y = 0; + double max_y = 0; + QVector vals; + }; signals: + void seriesRemoved(const QString &id, const Signal *sig); void zoomIn(double min, double max); void zoomReset(); - void remove(const QString &msg_id, const Signal *sig); + void remove(); + +private slots: + void msgRemoved(uint32_t address); + void msgUpdated(uint32_t address); + void signalUpdated(const Signal *sig); + void signalRemoved(const Signal *sig); private: + QList::iterator removeSeries(const QList::iterator &it); void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *ev) override; void leaveEvent(QEvent *event) override; void resizeEvent(QResizeEvent *event) override; void adjustChartMargins(); void updateAxisY(); + void updateTitle(); + void updateFromSettings(); + QValueAxis *axis_x; + QValueAxis *axis_y; QGraphicsItemGroup *item_group; QGraphicsLineItem *line_marker, *track_line; QGraphicsEllipseItem *track_ellipse; QGraphicsTextItem *value_text; QGraphicsProxyWidget *close_btn_proxy; - QVector vals; + std::pair events_range = {0, 0}; + QList sigs; }; class ChartsWidget : public QWidget { @@ -54,9 +80,9 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); - void showChart(const QString &id, const Signal *sig, bool show); + void showChart(const QString &id, const Signal *sig, bool show, bool merge); void removeChart(ChartView *chart); - bool isChartOpened(const QString &id, const Signal *sig); + inline bool isChartOpened(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; } signals: void dock(bool floating); @@ -69,10 +95,10 @@ private: void updateState(); void zoomIn(double min, double max); void zoomReset(); - void signalUpdated(const Signal *sig); void updateToolBar(); - void removeAll(const Signal *sig = nullptr); + void removeAll(); bool eventFilter(QObject *obj, QEvent *event) override; + ChartView *findChart(const QString &id, const Signal *sig); QLabel *title_label; QLabel *range_label; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index a0660901cf..93b7aa88f7 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -122,7 +123,9 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa save_timer->callOnTimeout(this, &SignalEdit::saveSignal); QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); - QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked); }); + QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { + emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); + }); QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); }); QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); }); QObject::connect(form, &SignalForm::changed, [this]() { save_timer->start(); }); @@ -172,7 +175,7 @@ void SignalEdit::saveSignal() { } void SignalEdit::setChartOpened(bool opened) { - plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot")); + plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot\nSHIFT click to add to previous opened chart")); plot_btn->setChecked(opened); } diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index f889a9c096..f035797e72 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -42,7 +42,7 @@ public: signals: void highlight(const Signal *sig); - void showChart(const QString &name, const Signal *sig, bool show); + void showChart(const QString &name, const Signal *sig, bool show, bool merge); void remove(const Signal *sig); void save(const Signal *sig, const Signal &new_sig); void showFormClicked(); From 9c815c2081fb61dfe84a79b617f17280c0396773 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 19 Nov 2022 02:25:11 +0800 Subject: [PATCH 060/185] Cabana: draw line marker in drawForegound (#26542) draw line marker in drawForegound --- tools/cabana/chartswidget.cc | 25 ++++++++----------------- tools/cabana/chartswidget.h | 4 ++-- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 4bd398a93b..5a129b5f61 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -102,7 +102,7 @@ void ChartsWidget::updateState() { const auto &range = is_zoomed ? zoomed_range : display_range; for (auto c : charts) { c->setDisplayRange(range.first, range.second); - c->updateLineMarker(current_sec); + c->scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } } @@ -178,9 +178,6 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { // top margin for title chart->setMargins({0, 11, 0, 0}); - line_marker = new QGraphicsLineItem(chart); - line_marker->setZValue(chart->zValue() + 10); - track_line = new QGraphicsLineItem(chart); track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); track_ellipse = new QGraphicsEllipseItem(chart); @@ -304,7 +301,6 @@ void ChartView::updateFromSettings() { setFixedHeight(settings.chart_height); chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); auto color = chart()->titleBrush().color(); - line_marker->setPen(QPen(color, 2)); } void ChartView::setEventsRange(const std::pair &range) { @@ -327,15 +323,6 @@ void ChartView::adjustChartMargins() { if (chart()->plotArea().left() != aligned_pos) { const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left(); chart()->setMargins(QMargins(left_margin, 11, 0, 0)); - updateLineMarker(can->currentSec()); - } -} - -void ChartView::updateLineMarker(double current_sec) { - int x = chart()->plotArea().left() + - chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min()); - if (int(line_marker->line().x1()) != x) { - line_marker->setLine(x, chart()->plotArea().top() - chart()->margins().top() + 3, x, height()); } } @@ -429,7 +416,6 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { // zoom in if selected range is greater than 0.5s emit zoomIn(min, max); } - viewport()->update(); event->accept(); } else if (event->button() == Qt::RightButton) { emit zoomReset(); @@ -437,7 +423,6 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { } else { QGraphicsView::mouseReleaseEvent(event); } - setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); } void ChartView::mouseMoveEvent(QMouseEvent *ev) { @@ -477,7 +462,13 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { item_group->setVisible(!text_list.isEmpty()); } else { item_group->setVisible(false); - setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } QChartView::mouseMoveEvent(ev); } + +void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { + qreal x = chart()->plotArea().left() + + chart()->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); + painter->setPen(QPen(chart()->titleBrush().color(), 2)); + painter->drawLine(QPointF{x, chart()->plotArea().top() - 2}, QPointF{x, chart()->plotArea().bottom() + 2}); +} diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index e32072a831..8f0af95b1a 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -28,7 +28,6 @@ public: void updateSeries(const Signal *sig = nullptr); void setEventsRange(const std::pair &range); void setDisplayRange(double min, double max); - void updateLineMarker(double current_sec); struct SigItem { QString msg_id; @@ -63,11 +62,12 @@ private: void updateAxisY(); void updateTitle(); void updateFromSettings(); + void drawForeground(QPainter *painter, const QRectF &rect) override; QValueAxis *axis_x; QValueAxis *axis_y; QGraphicsItemGroup *item_group; - QGraphicsLineItem *line_marker, *track_line; + QGraphicsLineItem *track_line; QGraphicsEllipseItem *track_ellipse; QGraphicsTextItem *value_text; QGraphicsProxyWidget *close_btn_proxy; From 34c80a6fbdf8780ad0b75f497c226cf1d9de0ddb Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 18 Nov 2022 17:50:24 -0800 Subject: [PATCH 061/185] Albert Einstein Model (#26456) * d0bc247d-9529-4e50-a5dd-95a8a5733874/440 38fb1ce9-18d7-4292-9ffd-678f8483ebf4/700 * update model replay ref commit --- selfdrive/modeld/models/supercombo.onnx | 2 +- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 8805b3dce8..c0db988cf6 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db746e3753de84367595fedd089c2bd41b06bd401ea28e085663533d0e63d74b +oid sha256:c73998c9f428380dd2282b451de6011469b717498ae83578cbf7aa95948910f7 size 45962473 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index f541b6a6d5..cdd6ed7a6b 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -30efb4238327d723e17a3bda7e7c19c18f8a3b18 +ca02aa240e629920ad88a6510213cb0a153af92b From 0b79ba8acb99f6c1dc3957a985229e3d5a15892b Mon Sep 17 00:00:00 2001 From: AlexandreSato <66435071+AlexandreSato@users.noreply.github.com> Date: Sat, 19 Nov 2022 19:20:41 -0300 Subject: [PATCH 062/185] Multilang: Update pt-BR translations (#26549) * update pt-BR translations * fix some cutoff texts * update pt-BR translation --- selfdrive/ui/translations/main_pt-BR.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 4a698947f1..105d6f77f0 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + MODO EXPERIMENTAL ATIVADO CHILL MODE ON - + MODO CHILL ATIVADO
@@ -1019,15 +1019,15 @@ trabalho definido On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + Neste carro o penpilot por padrão utiliza o ACC nativo do veículo ao invés de controlar longitudinalmente. Ative isto para mudar para o controle longitudinal do openpilot. Ativar o Modo Experimental é recomendado quando em uso do controle longitudinal experimental do openpilot. Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + O Modo Experimental está atualmente indisponível para este carro, já que o ACC original do carro é usado para controle longitudinal. Enable experimental longitudinal control to allow experimental mode. - + Ative o controle longitudinal experimental para permitir o modo experimental. openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: @@ -1039,15 +1039,15 @@ trabalho definido Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - + Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade alfa; erros devem ser esperados. New Driving Visualization - + Nova Visualização de Condução The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + A visualização da direção fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será exibido no canto superior direito. From c3822bdddadab3c24e9275ea5037cff96456f2fb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 19 Nov 2022 14:43:20 -0800 Subject: [PATCH 063/185] bump to 0.9.1 --- RELEASES.md | 5 +++++ common/version.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index e15286c5f0..97c92dfa47 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,8 @@ +Version 0.9.1 (2022-12-XX) +======================== +* New driving model + + Version 0.9.0 (2022-11-21) ======================== * New driving model diff --git a/common/version.h b/common/version.h index 74a56a7c1b..7b5764785a 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.0" +#define COMMA_VERSION "0.9.1" From 649663e06d43a6d1b05fe83120391f3bd2401b8f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 19 Nov 2022 14:45:34 -0800 Subject: [PATCH 064/185] body: add can fingerprint (#26548) --- selfdrive/car/body/values.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index 66f1b947a8..418835a3e9 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -26,6 +26,12 @@ CAR_INFO: Dict[str, CarInfo] = { CAR.BODY: CarInfo("comma body", package="All"), } +FINGERPRINTS = { + CAR.BODY: [{ + 513: 8, 516: 8, 514: 3, 515: 4, + }], +} + FW_QUERY_CONFIG = FwQueryConfig( requests=[ Request( From 5b8aed2ebf80ba0875ed21b8130a6ab7451e5740 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Sun, 20 Nov 2022 11:44:04 -0800 Subject: [PATCH 065/185] body: fix UDS reqests (#26552) * bump body * add new fw version --- body | 2 +- selfdrive/car/body/values.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/body b/body index 04aeb30ce0..dc780f858c 160000 --- a/body +++ b/body @@ -1 +1 @@ -Subproject commit 04aeb30ce0bb14759989cd374158233877e1e151 +Subproject commit dc780f858c1ef641471d09b72569e199e3e10acb diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index 418835a3e9..548039bc70 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -46,10 +46,13 @@ FW_VERSIONS = { CAR.BODY: { (Ecu.engine, 0x720, None): [ b'0.0.01', - b'02/27/2022' + b'02/27/2022', + b'0.3.00a', ], + # git hash of the firmware used (Ecu.debug, 0x721, None): [ - b'166bd860' # git hash of the firmware used + b'166bd860', + b'dc780f85', ], }, } From 17b1839e0a358302e049f37f5c843532fb9c7f26 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 21 Nov 2022 05:23:59 +0800 Subject: [PATCH 066/185] Cabana: faster align charts, visual glitches removed. (#26543) * faster adjust chart margins * delay adjust * update foreground after set margins * set display range on create * fix init display_range * common function updateDisplayRange * set min max to 0 if no values * fix axis y * use mapToValue * cleanup * get minmax from series * cleanup * cleanup eventsMerged * cleanup include --- tools/cabana/chartswidget.cc | 119 ++++++++++++++++------------------- tools/cabana/chartswidget.h | 2 +- 2 files changed, 54 insertions(+), 67 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 5a129b5f61..bc1f1e2a38 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -54,13 +54,25 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { void ChartsWidget::eventsMerged() { if (auto events = can->events(); events && !events->empty()) { - auto it = std::find_if(events->begin(), events->end(), [=](const Event *e) { return e->which == cereal::Event::Which::CAN; }); - event_range.first = it == events->end() ? 0 : (*it)->mono_time / (double)1e9 - can->routeStartTime(); - event_range.second = it == events->end() ? 0 : events->back()->mono_time / (double)1e9 - can->routeStartTime(); - if (display_range.first == 0 && event_range.second == 0) { - display_range.first = event_range.first; - display_range.second = std::min(event_range.first + settings.max_chart_x_range, event_range.second); - } + event_range.first = (events->front()->mono_time / (double)1e9) - can->routeStartTime(); + event_range.second = (events->back()->mono_time / (double)1e9) - can->routeStartTime(); + updateDisplayRange(); + } +} + +void ChartsWidget::updateDisplayRange() { + auto prev_range = display_range; + double current_sec = can->currentSec(); + if (current_sec < display_range.first || current_sec >= (display_range.second - 5)) { + // reached the end, or seeked to a timestamp out of range. + display_range.first = current_sec - 5; + } + display_range.first = std::max(display_range.first, event_range.first); + display_range.second = std::min(display_range.first + settings.max_chart_x_range, event_range.second); + if (prev_range != display_range) { + QFutureSynchronizer future_synchronizer; + for (auto c : charts) + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::setEventsRange, display_range)); } } @@ -79,24 +91,10 @@ void ChartsWidget::zoomReset() { void ChartsWidget::updateState() { if (charts.isEmpty()) return; - const double current_sec = can->currentSec(); - if (is_zoomed) { - if (current_sec < zoomed_range.first || current_sec >= zoomed_range.second) { - can->seekTo(zoomed_range.first); - } - } else { - auto prev_range = display_range; - if (current_sec < display_range.first || current_sec >= (display_range.second - 5)) { - // line marker reached the end, or seeked to a timestamp out of range. - display_range.first = current_sec - 5; - } - display_range.first = std::max(display_range.first, event_range.first); - display_range.second = std::min(display_range.first + settings.max_chart_x_range, event_range.second); - if (prev_range != display_range) { - QFutureSynchronizer future_synchronizer; - for (auto c : charts) - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::setEventsRange, display_range)); - } + if (!is_zoomed) { + updateDisplayRange(); + } else if (can->currentSec() < zoomed_range.first || can->currentSec() >= zoomed_range.second) { + can->seekTo(zoomed_range.first); } const auto &range = is_zoomed ? zoomed_range : display_range; @@ -122,15 +120,14 @@ ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) { } void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) { - if (!show) { - if (ChartView *chart = findChart(id, sig)) { - chart->removeSeries(id, sig); - } - } else { + setUpdatesEnabled(false); + if (show) { ChartView *chart = merge && charts.size() > 0 ? charts.back() : nullptr; if (!chart) { chart = new ChartView(this); chart->setEventsRange(display_range); + auto range = is_zoomed ? zoomed_range : display_range; + chart->setDisplayRange(range.first, range.second); QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); @@ -140,9 +137,11 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bo } chart->addSeries(id, sig); emit chartOpened(id, sig); - updateState(); + } else if (ChartView *chart = findChart(id, sig)) { + chart->removeSeries(id, sig); } updateToolBar(); + setUpdatesEnabled(true); } void ChartsWidget::removeChart(ChartView *chart) { @@ -175,8 +174,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { chart->addAxis(axis_y, Qt::AlignLeft); chart->legend()->setShowToolTips(true); chart->layout()->setContentsMargins(0, 0, 0, 0); - // top margin for title - chart->setMargins({0, 11, 0, 0}); + chart->setMargins(QMargins(20, 11, 11, 11)); track_line = new QGraphicsLineItem(chart); track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); @@ -200,21 +198,12 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { setRubberBand(QChartView::HorizontalRubberBand); updateFromSettings(); - QTimer *timer = new QTimer(this); - timer->setInterval(100); - timer->setSingleShot(true); - timer->callOnTimeout(this, &ChartView::adjustChartMargins); - QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved); QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated); QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved); QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated); QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings); QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); - QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) { - // use a singleshot timer to avoid recursion call. - timer->start(); - }); } ChartView::~ChartView() { @@ -300,7 +289,6 @@ void ChartView::updateTitle() { void ChartView::updateFromSettings() { setFixedHeight(settings.chart_height); chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); - auto color = chart()->titleBrush().color(); } void ChartView::setEventsRange(const std::pair &range) { @@ -320,18 +308,19 @@ void ChartView::setDisplayRange(double min, double max) { void ChartView::adjustChartMargins() { // TODO: Remove hardcoded aligned_pos const int aligned_pos = 60; - if (chart()->plotArea().left() != aligned_pos) { + if ((int)chart()->plotArea().left() != aligned_pos) { const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left(); - chart()->setMargins(QMargins(left_margin, 11, 0, 0)); + chart()->setMargins(QMargins(left_margin, 11, 11, 11)); + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } } void ChartView::updateSeries(const Signal *sig) { auto events = can->events(); - if (!events) return; + if (!events || sigs.isEmpty()) return; - for (int i = 0; i < sigs.size(); ++i) { - if (auto &s = sigs[i]; !sig || s.sig == sig) { + for (auto &s : sigs) { + if (!sig || s.sig == sig) { s.vals.clear(); s.vals.reserve((events_range.second - events_range.first) * 1000); // [n]seconds * 1000hz s.min_y = std::numeric_limits::max(); @@ -357,9 +346,7 @@ void ChartView::updateSeries(const Signal *sig) { } } } - - QLineSeries *series = (QLineSeries *)chart()->series()[i]; - series->replace(s.vals); + s.series->replace(s.vals); } } updateAxisY(); @@ -367,6 +354,8 @@ void ChartView::updateSeries(const Signal *sig) { // auto zoom on yaxis void ChartView::updateAxisY() { + if (sigs.isEmpty()) return; + double min_y = std::numeric_limits::max(); double max_y = std::numeric_limits::lowest(); if (events_range == std::pair{axis_x->min(), axis_x->max()}) { @@ -376,17 +365,16 @@ void ChartView::updateAxisY() { } } else { for (auto &s : sigs) { - auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - if (begin == s.vals.end()) - return; - - auto end = std::upper_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); - const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); - if (min->y() < min_y) min_y = min->y(); - if (max->y() > max_y) max_y = max->y(); + for (int i = 0; i < s.series->count(); ++i) { + double y = s.series->at(i).y(); + if (y < min_y) min_y = y; + if (y > max_y) max_y = y; + } } } + if (min_y == std::numeric_limits::max()) min_y = 0; + if (max_y == std::numeric_limits::lowest()) max_y = 0; if (max_y == min_y) { axis_y->setRange(min_y - 1, max_y + 1); } else { @@ -394,6 +382,8 @@ void ChartView::updateAxisY() { axis_y->setRange(min_y - range * 0.05, max_y + range * 0.05); axis_y->applyNiceNumbers(); } + + QTimer::singleShot(0, this, &ChartView::adjustChartMargins); } void ChartView::leaveEvent(QEvent *event) { @@ -406,9 +396,8 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { rubber->hide(); QRectF rect = rubber->geometry().normalized(); - rect.translate(-chart()->plotArea().topLeft()); - double min = axis_x->min() + (rect.left() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min()); - double max = axis_x->min() + (rect.right() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min()); + double min = chart()->mapToValue(rect.topLeft()).x(); + double max = chart()->mapToValue(rect.bottomRight()).x(); if (rubber->width() <= 0) { // no rubber dragged, seek to mouse position can->seekTo(min); @@ -431,8 +420,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { const auto plot_area = chart()->plotArea(); if (!is_zooming && plot_area.contains(ev->pos())) { - double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right() - 1); - double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width(); + double sec = chart()->mapToValue(ev->pos()).x(); QStringList text_list; QPointF pos = plot_area.bottomRight(); double tm = 0.0; @@ -467,8 +455,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { } void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { - qreal x = chart()->plotArea().left() + - chart()->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); + qreal x = chart()->mapToPosition(QPointF{can->currentSec(), 0}).x(); painter->setPen(QPen(chart()->titleBrush().color(), 2)); painter->drawLine(QPointF{x, chart()->plotArea().top() - 2}, QPointF{x, chart()->plotArea().bottom() + 2}); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 8f0af95b1a..c8e5a1040c 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -93,6 +92,7 @@ signals: private: void eventsMerged(); void updateState(); + void updateDisplayRange(); void zoomIn(double min, double max); void zoomReset(); void updateToolBar(); From de443d30b37aa5d8b15c94749399d836155bcf60 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 22 Nov 2022 02:47:36 +0800 Subject: [PATCH 067/185] Cabana: 'edit-find' icon is not avaible on some platforms (#26561) Fixed edit-find icon is not avaible on some platforms --- tools/cabana/signaledit.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 93b7aa88f7..5d627a49da 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -95,7 +95,7 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa plot_btn->setAutoRaise(true); title_layout->addWidget(plot_btn); auto seek_btn = new QToolButton(this); - seek_btn->setIcon(QIcon::fromTheme("edit-find")); + seek_btn->setText("🔍"); seek_btn->setAutoRaise(true); seek_btn->setToolTip(tr("Find signal values")); title_layout->addWidget(seek_btn); From 89eeb72dbdb82258fdd26d7260f0c6455a371bf1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 22 Nov 2022 02:47:47 +0800 Subject: [PATCH 068/185] Cabana: update chart and video position (after a click) in pause mode (#26560) update chart and video position (after a click) in pause mode --- tools/cabana/canmessages.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index 3bcaae4bbd..a8e881c5fe 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -124,6 +124,7 @@ const std::deque CANMessages::messages(const QString &id) { void CANMessages::seekTo(double ts) { replay->seekTo(std::max(double(0), ts), false); counters_begin_sec = 0; + emit updated(); } void CANMessages::settingChanged() { From 2b4b0cc67f59c896caf32fd937ad6ebf0f7ae640 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 22 Nov 2022 02:47:58 +0800 Subject: [PATCH 069/185] Replay: fixed the freq of video and events are unstable after changing the replay speed (#26559) fix freq is unstable after changing the replay speed --- tools/replay/replay.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 1337a4ef2c..1464a6cf57 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -358,6 +358,7 @@ void Replay::publishFrame(const Event *e) { void Replay::stream() { cereal::Event::Which cur_which = cereal::Event::Which::INIT_DATA; + double prev_replay_speed = 1.0; std::unique_lock lk(stream_lock_); while (true) { @@ -397,10 +398,11 @@ void Replay::stream() { long rtime = nanos_since_boot() - loop_start_ts; long behind_ns = etime - rtime; // if behind_ns is greater than 1 second, it means that an invalid segemnt is skipped by seeking/replaying - if (behind_ns >= 1 * 1e9) { - // reset start times + if (behind_ns >= 1 * 1e9 || speed_ != prev_replay_speed) { + // reset event start times evt_start_ts = cur_mono_time_; loop_start_ts = nanos_since_boot(); + prev_replay_speed = speed_; } else if (behind_ns > 0 && !hasFlag(REPLAY_FLAG_FULL_SPEED)) { precise_nano_sleep(behind_ns); } From 7b46928fc9cda9e5200d72c15253f60981eedc74 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 22 Nov 2022 02:48:08 +0800 Subject: [PATCH 070/185] Cabana: fix auto zoom y-axis for multiple line series (#26558) auto zoom y-axis for multiple series --- tools/cabana/chartswidget.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index bc1f1e2a38..3029d9db89 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -365,10 +365,10 @@ void ChartView::updateAxisY() { } } else { for (auto &s : sigs) { - for (int i = 0; i < s.series->count(); ++i) { - double y = s.series->at(i).y(); - if (y < min_y) min_y = y; - if (y > max_y) max_y = y; + auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); + for (auto it = begin; it != s.vals.end() && it->x() <= axis_x->max(); ++it) { + if (it->y() < min_y) min_y = it->y(); + if (it->y() > max_y) max_y = it->y(); } } } From efbfbc062280063b1cde744a97ecde5abb711784 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 22 Nov 2022 02:48:19 +0800 Subject: [PATCH 071/185] Cabana: display dashes if no value available (#26557) show dot if no value --- tools/cabana/chartswidget.cc | 45 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 3029d9db89..5d530a8c8a 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -420,34 +420,35 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { const auto plot_area = chart()->plotArea(); if (!is_zooming && plot_area.contains(ev->pos())) { - double sec = chart()->mapToValue(ev->pos()).x(); QStringList text_list; - QPointF pos = plot_area.bottomRight(); - double tm = 0.0; - + QPointF pos = {}; + const double sec = chart()->mapToValue(ev->pos()).x(); for (auto &s : sigs) { - auto value = std::upper_bound(s.vals.begin(), s.vals.end(), sec, [](double x, auto &p) { return x < p.x(); }); - if (value != s.vals.end()) { - text_list.push_back(QString(" %1 : %2 ").arg(sigs.size() > 1 ? s.sig->name.c_str() : "Value").arg(value->y())); - tm = value->x(); - auto y_pos = chart()->mapToPosition(*value); - if (y_pos.y() < pos.y()) pos = y_pos; + QString value = "--"; + // use reverse iterator to find last item <= sec. + auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; }); + if (it != s.vals.rend() && it->x() >= axis_x->min()) { + value = QString::number(it->y()); + auto value_pos = chart()->mapToPosition(*it); + if (value_pos.x() > pos.x()) pos = value_pos; } + text_list.push_back(QString(" %1 : %2 ").arg(sigs.size() > 1 ? s.sig->name.c_str() : "Value").arg(value)); } + if (pos.x() == 0) pos = ev->pos(); - if (!text_list.isEmpty()) { - value_text->setHtml("
 Time: " + - QString::number(tm, 'f', 3) + " 
" + text_list.join("
") + "
"); - track_line->setLine(pos.x(), plot_area.top(), pos.x(), plot_area.bottom()); - int text_x = pos.x() + 8; - QRectF text_rect = value_text->boundingRect(); - if ((text_x + text_rect.width()) > plot_area.right()) { - text_x = pos.x() - text_rect.width() - 8; - } - value_text->setPos(text_x, pos.y() - text_rect.height() / 2); - track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10); + QString time = QString::number(chart()->mapToValue(pos).x(), 'f', 3); + value_text->setHtml(QString("
 Time: %1  
%2
") + .arg(time).arg(text_list.join("
"))); + + QRectF text_rect = value_text->boundingRect(); + int text_x = pos.x() + 8; + if ((text_x + text_rect.width()) > plot_area.right()) { + text_x = pos.x() - text_rect.width() - 8; } - item_group->setVisible(!text_list.isEmpty()); + value_text->setPos(text_x, pos.y() - text_rect.height() / 2); + track_line->setLine(pos.x(), plot_area.top(), pos.x(), plot_area.bottom()); + track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10); + item_group->setVisible(true); } else { item_group->setVisible(false); } From 96c9b8c50e0413ba92e7729e1820884d88598989 Mon Sep 17 00:00:00 2001 From: protonchang <2095341+protonchang@users.noreply.github.com> Date: Tue, 22 Nov 2022 02:48:30 +0800 Subject: [PATCH 072/185] Update CHT translations (#26537) * Add missing translations * Fix some CHS -> CHT wording --- selfdrive/ui/translations/main_zh-CHT.ts | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 46fb5578a3..0379e926c4 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -113,7 +113,7 @@ Decline, uninstall %1 - 拒絕並卸載 %1 + 拒絕並解除安裝 %1
@@ -132,7 +132,7 @@ Driver Camera - 駕駛員攝像頭 + 駕駛員監控鏡頭 PREVIEW @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 實驗模式 ON CHILL MODE ON - + 輕鬆模式 ON @@ -859,15 +859,15 @@ location set UNINSTALL - 卸載 + 解除安裝 Uninstall %1 - 卸載 %1 + 解除安裝 %1 Are you sure you want to uninstall? - 您確定您要卸載嗎? + 您確定您要解除安裝嗎? CHECK @@ -875,7 +875,7 @@ location set Uninstall - 卸載 + 解除安裝 @@ -1015,19 +1015,19 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + 在本車輛中,openpilot預設將使用原車內建的ACC系統,而非openpilot縱向控制。開啟此開關來啟用openpilot縱向控制,使用此選項時建議一併啟用實驗模式。 Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + 因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。 Enable experimental longitudinal control to allow experimental mode. - + 啟用實驗性縱向控制以使用實驗模式。 openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot 默認以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: + openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: 🌮 End-to-End Longitudinal Control 🌮 @@ -1035,15 +1035,15 @@ location set Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - + 讓駕駛模型來控制油門及煞車。openpilot將會模擬人類的駕駛行為,包含在看見紅燈及停止標示時停車。由於車速將由駕駛模型決定,因此您設定的時速將成為速度上限。本功能仍在早期實驗階段,請預期模型有犯錯的可能性。 New Driving Visualization - + 新的駕駛視覺介面 The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + 低速行駛時,將會切換成路側廣角鏡頭,以完整顯示轉彎路徑,右上角將出現實驗模式圖案。 From 5b8f0db3e5dea3b7fcc90250ee7aefb8fa00555b Mon Sep 17 00:00:00 2001 From: ambientocclusion <1399123+ambientocclusion@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:23:02 -0800 Subject: [PATCH 073/185] Multilang: add missing Japanese translations (#26556) * Add missing Japanese translations * Update some words --- selfdrive/ui/translations/main_ja.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index ebf40cc5c5..8226dd59f9 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 実験モード CHILL MODE ON - + チルモード @@ -1015,15 +1015,15 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + openpilotはこの車の場合、車に内蔵されているACCを標準で利用します。この機能を有効にすることで実験段階のopenpilotによるアクセル制御を利用できます。実験モードと合わせて利用することをお勧めします。 Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + この車のACCがアクセル制御を行うため、実験モードを利用することができません。 Enable experimental longitudinal control to allow experimental mode. - + 実験段階のopenpilotによるアクセル制御を有効にしてください。 openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: @@ -1035,15 +1035,15 @@ location set Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - + openpilotにアクセルとブレーキを任せます。openpilotは赤信号や一時停止サインでの停止を含み、人間と同じように考えて運転を行います。openpilotが運転速度を決定するため、あなたが設定する速度は上限速度になります。この機能は実験段階のため、openpilotの運転ミスに常に備えて注意してください。 New Driving Visualization - + 新しい運転画面 The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + 新しい運転画面では、低速時に広角カメラの映像を表示することで、曲がる際の道路の視覚を向上します。実験段階を表すマークが右上に表示されます。 From ded66e6307825d9327c12145e1bc6acd2e835346 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Mon, 21 Nov 2022 15:25:23 -0800 Subject: [PATCH 074/185] long_mpc: fix e2e source condition (#26546) * fix long_mpc source param * rm print * add back space for formatting --- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 6b79813117..79a9ec4f0c 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -352,7 +352,7 @@ class LongitudinalMpc: x_and_cruise = np.column_stack([x, cruise_target]) x = np.min(x_and_cruise, axis=1) - self.source = 'e2e' if x_and_cruise[0,0] < x_and_cruise[0,1] else 'cruise' + self.source = 'e2e' if x_and_cruise[1,0] < x_and_cruise[1,1] else 'cruise' else: raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner update') From 5402e02785ce2dffa404acf888fcc36d3df133fa Mon Sep 17 00:00:00 2001 From: AlexandreSato <66435071+AlexandreSato@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:28:34 -0300 Subject: [PATCH 075/185] Lexus UX Hybrid 2020 Fingerprint (#26551) Lexus UX 2020 Fingerprint alex.takeda@gmail.com --- selfdrive/car/toyota/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index ba26c7e03b..b4b3ac4131 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -821,6 +821,7 @@ FW_VERSIONS = { b'\x01896637621000\x00\x00\x00\x00', b'\x01896637624000\x00\x00\x00\x00', b'\x01896637626000\x00\x00\x00\x00', + b'\x01896637639000\x00\x00\x00\x00', b'\x01896637648000\x00\x00\x00\x00', b'\x01896637643000\x00\x00\x00\x00', b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', From 62ad278829bbe7e6756f4bb3c44386e9c85d731c Mon Sep 17 00:00:00 2001 From: martinl Date: Tue, 22 Nov 2022 01:34:20 +0200 Subject: [PATCH 076/185] Add FPv2: 2022 Subaru Outback Wilderness (#26540) Add FPv2: 2022 Outback Wilderness / @valtou --- selfdrive/car/subaru/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 7a1e9a8a3d..9975e495dd 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -461,6 +461,7 @@ FW_VERSIONS = { b'\xa1 \x08\x02', b'\xa1 \x06\x02', b'\xa1 \x08\x00', + b'\xa1 "\t\x00', ], (Ecu.eps, 0x746, None): [ b'\x9b\xc0\x10\x00', @@ -482,6 +483,7 @@ FW_VERSIONS = { b'\xe2"`p\x07', b'\xf1\x82\xe2,\xa0@\x07', b'\xbc"`q\x07', + b'\xe3,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xa5\xfe\xf7@\x00', From 548888d6341a4f44ea2e4154fb8c820c3fc38e4e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 21 Nov 2022 15:59:42 -0800 Subject: [PATCH 077/185] docs: update Toyota DSU footnote (#26568) --- docs/CARS.md | 4 ++-- selfdrive/car/docs_definitions.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 59b8145556..a0d22a3d27 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -225,8 +225,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `master-ci`. Using openpilot longitudinal may disable Automatic Emergency Braking (AEB).
-2When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
+1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
+2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
5openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 8e057a1563..6ec9ac5c40 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -73,12 +73,11 @@ CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only"], default class CommonFootnote(Enum): EXP_LONG_AVAIL = CarFootnote( - "Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `master-ci`. " + - "Using openpilot longitudinal may disable Automatic Emergency Braking (AEB).", + "Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. ", Column.LONGITUDINAL, docs_only=True) EXP_LONG_DSU = CarFootnote( - "When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace " + - "stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", + "By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace " + + "stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", Column.LONGITUDINAL) From 94aa39bdd406400ab48765d8b50f08e03ecf9eb8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 21 Nov 2022 16:18:51 -0800 Subject: [PATCH 078/185] enable experimental longitudinal control on devel (#26544) --- selfdrive/controls/controlsd.py | 4 ++-- system/version.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a18bec83a8..a395f85580 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -12,7 +12,7 @@ import cereal.messaging as messaging from common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE from system.swaglog import cloudlog -from system.version import is_tested_branch, get_short_branch +from system.version import is_release_branch, get_short_branch from selfdrive.boardd.boardd import can_list_to_can_capnp from selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can from selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET @@ -132,7 +132,7 @@ class Controls: safety_config.safetyModel = car.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] - if is_tested_branch(): + if is_release_branch(): self.CP.experimentalLongitudinalAvailable = False # Write CarParams for radard diff --git a/system/version.py b/system/version.py index f0817b3a9f..6031531556 100644 --- a/system/version.py +++ b/system/version.py @@ -7,7 +7,8 @@ from functools import lru_cache from common.basedir import BASEDIR from system.swaglog import cloudlog -TESTED_BRANCHES = ['devel', 'release3-staging', 'dashcam3-staging', 'release3', 'dashcam3'] +RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3'] +TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] training_version: bytes = b"0.2.0" terms_version: bytes = b"2" @@ -96,6 +97,9 @@ def is_comma_remote() -> bool: def is_tested_branch() -> bool: return get_short_branch() in TESTED_BRANCHES +@cache +def is_release_branch() -> bool: + return get_short_branch() in RELEASE_BRANCHES @cache def is_dirty() -> bool: From be3d6527adb3f58500e7c9f2d689b4821c233791 Mon Sep 17 00:00:00 2001 From: Erich Moraga <33645296+ErichMoraga@users.noreply.github.com> Date: Mon, 21 Nov 2022 18:22:01 -0600 Subject: [PATCH 079/185] Add missing HIGHLANDERH_TSS2 ABS & engine f/w (#26534) `@shakir07#1323` 2020 Highlander Hybrid DongleID/route ce56baaf82559131|2022-11-16--23-54-01 --- selfdrive/car/toyota/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index b4b3ac4131..cf37a3c2f2 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1015,6 +1015,7 @@ FW_VERSIONS = { b'\x01F15264873500\x00\x00\x00\x00', b'\x01F152648C6300\x00\x00\x00\x00', b'\x01F152648J4000\x00\x00\x00\x00', + b'\x01F152648J5000\x00\x00\x00\x00', b'\x01F152648J6000\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ @@ -1022,6 +1023,7 @@ FW_VERSIONS = { b'\x01896630EA1000\x00\x00\x00\x00', b'\x01896630EE4000\x00\x00\x00\x00', b'\x01896630EE4100\x00\x00\x00\x00', + b'\x01896630EE5000\x00\x00\x00\x00', b'\x01896630EE6000\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', From 23a7a987f6ab75ebee4dd856ba0a0c179b2f90b0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 21 Nov 2022 17:05:13 -0800 Subject: [PATCH 080/185] controlsd: add test around cruise speed gas pressed behavior (#26486) * test * test * debug * test * test * test * clean up * clean up * add test * stash * clean up * clean up * clean up * assert equal --- selfdrive/controls/lib/drive_helpers.py | 4 +-- selfdrive/controls/tests/test_cruise_speed.py | 26 ++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index bdbdb7023a..f0dc2e9467 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -12,6 +12,7 @@ V_CRUISE_MAX = 145 # kph V_CRUISE_MIN = 8 # kph V_CRUISE_ENABLE_MIN = 40 # kph V_CRUISE_INITIAL = 255 # kph +IMPERIAL_INCREMENT = 1.6 # should be CV.MPH_TO_KPH, but this causes rounding errors MIN_SPEED = 1.0 LAT_MPC_N = 16 @@ -73,8 +74,7 @@ class VCruiseHelper: long_press = False button_type = None - # should be CV.MPH_TO_KPH, but this causes rounding errors - v_cruise_delta = 1. if is_metric else 1.6 + v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT for b in CS.buttonEvents: if b.type.raw in self.button_timers and not b.pressed: diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py index 3d6f55931e..a635198ceb 100755 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -3,7 +3,7 @@ import numpy as np from parameterized import parameterized_class import unittest -from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MAX, V_CRUISE_ENABLE_MIN +from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_ENABLE_MIN, IMPERIAL_INCREMENT from cereal import car from common.conversions import Conversions as CV from common.params import Params @@ -110,6 +110,30 @@ class TestVCruiseHelper(unittest.TestCase): should_equal = standstill or pressed self.assertEqual(should_equal, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + def test_set_gas_pressed(self): + """ + Asserts pressing set while enabled with gas pressed sets + the speed to the maximum of vEgo and current cruise speed. + """ + + for v_ego in np.linspace(0, 100, 101): + self.reset_cruise_speed_state() + self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS) + + # first decrement speed, then perform gas pressed logic + expected_v_cruise_kph = self.v_cruise_helper.v_cruise_kph - IMPERIAL_INCREMENT + expected_v_cruise_kph = max(expected_v_cruise_kph, v_ego * CV.MS_TO_KPH) # clip to min of vEgo + expected_v_cruise_kph = float(np.clip(round(expected_v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)) + + CS = car.CarState(vEgo=float(v_ego), gasPressed=True, cruiseState={"available": True}) + CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=False)] + self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) + + # TODO: fix skipping first run due to enabled on rising edge exception + if v_ego == 0.0: + continue + self.assertEqual(expected_v_cruise_kph, self.v_cruise_helper.v_cruise_kph) + def test_initialize_v_cruise(self): """ Asserts allowed cruise speeds on enabling with SET. From 4478241bea02cb673983aa6b97428702a54dd1c2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 21 Nov 2022 17:17:36 -0800 Subject: [PATCH 081/185] spi goes on boardd core --- panda | 2 +- system/hardware/tici/hardware.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/panda b/panda index 5dc5cd8e20..2e90b6f308 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5dc5cd8e20668dfb15d35b175fccbfd3f7b63b09 +Subproject commit 2e90b6f308dc09bf1734f6cb5cc990cb8149486d diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index e2fd20c1be..b5f5e00410 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -416,6 +416,7 @@ class Tici(HardwareBase): # *** IRQ config *** affine_irq(5, 565) # kgsl-3d0 + affine_irq(4, 126) # SPI goes on boardd core affine_irq(4, 740) # xhci-hcd:usb1 goes on the boardd core affine_irq(4, 1069) # xhci-hcd:usb3 goes on the boardd core for irq in range(237, 246): From 1367f84425b5fbdb3ecf8452b134b641998e404a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 21 Nov 2022 18:01:08 -0800 Subject: [PATCH 082/185] Car docs: add a make-specific init function (#26565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add a hook function for makes to implement custom docs logic * don't need this * use the pre-defined list for honda's harnesses * one line 😎 * Update selfdrive/car/docs_definitions.py * i didn't know you didn't need a pass! * don't change docs order --- selfdrive/car/docs.py | 1 + selfdrive/car/docs_definitions.py | 3 ++ selfdrive/car/gm/values.py | 21 +++++++----- selfdrive/car/honda/values.py | 52 +++++++++++++++++------------- selfdrive/car/volkswagen/values.py | 20 ++++++------ 5 files changed, 57 insertions(+), 40 deletions(-) diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index 58afed27eb..03313e2ff6 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -40,6 +40,7 @@ def get_all_car_info() -> List[CarInfo]: for _car_info in car_info: if not hasattr(_car_info, "row"): + _car_info.init_make(CP) _car_info.init(CP, footnotes) all_car_info.append(_car_info) diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 6ec9ac5c40..7cf44514d6 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -172,6 +172,9 @@ class CarInfo: return self + def init_make(self, CP: car.CarParams): + """CarInfo subclasses can add make-specific logic for harness selection, footnotes, etc.""" + def get_detail_sentence(self, CP): if not CP.notCar: sentence_builder = "openpilot upgrades your {car_model} with automated lane centering{alc} and adaptive cruise control{acc}." diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 35f87307d6..0a8cdc6dbb 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -1,5 +1,5 @@ from collections import defaultdict -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum from typing import Dict, List, Union @@ -84,8 +84,13 @@ class Footnote(Enum): @dataclass class GMCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" - harness: Enum = Harness.obd_ii - footnotes: List[Enum] = field(default_factory=lambda: [Footnote.OBD_II]) + + def init_make(self, CP: car.CarParams): + if CP.networkLocation == car.CarParams.NetworkLocation.fwdCamera: + self.harness = Harness.gm + else: + self.harness = Harness.obd_ii + self.footnotes.append(Footnote.OBD_II) CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { @@ -96,13 +101,13 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), - CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23", footnotes=[], harness=Harness.gm), - CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), + CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23"), + CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"), CAR.SILVERADO: [ - GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II", footnotes=[], harness=Harness.gm), - GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE", footnotes=[], harness=Harness.gm), + GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"), + GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE"), ], - CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22", footnotes=[], harness=Harness.gm), + CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22"), } diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 2510f2e1ff..151c2140f5 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -106,40 +106,46 @@ class Footnote(Enum): class HondaCarInfo(CarInfo): package: str = "Honda Sensing" + def init_make(self, CP: car.CarParams): + if CP.carFingerprint in HONDA_BOSCH: + self.harness = Harness.bosch_b if CP.carFingerprint in HONDA_BOSCH_RADARLESS else Harness.bosch_a + else: + self.harness = Harness.nidec + CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { CAR.ACCORD: [ - HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS), + HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS), ], - CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec, video_link="https://youtu.be/-IkImTe1NYE"), + CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE"), CAR.CIVIC_BOSCH: [ - HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS, harness=Harness.bosch_a), - HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS), + HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS), ], CAR.CIVIC_BOSCH_DIESEL: None, # same platform CAR.CIVIC_2022: [ - HondaCarInfo("Honda Civic 2022", "All", harness=Harness.bosch_b), - HondaCarInfo("Honda Civic Hatchback 2022", "All", harness=Harness.bosch_b), + HondaCarInfo("Honda Civic 2022", "All"), + HondaCarInfo("Honda Civic Hatchback 2022", "All"), ], - CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS), + CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring - CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", harness=Harness.nidec), + CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20"), CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey - CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS), } FW_QUERY_CONFIG = FwQueryConfig( diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index cb2343e08f..f24448adbc 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,5 +1,5 @@ from collections import defaultdict, namedtuple -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum from typing import Dict, List, Union @@ -165,7 +165,9 @@ class Footnote(Enum): class VWCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC) & Lane Assist" harness: Enum = Harness.j533 - footnotes: List[Enum] = field(default_factory=lambda: [Footnote.VW_EXP_LONG]) + + def init_make(self, CP: car.CarParams): + self.footnotes.insert(0, Footnote.VW_EXP_LONG) CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { @@ -197,28 +199,28 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Volkswagen Jetta GLI 2021-22"), ], CAR.PASSAT_MK8: [ - VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.PASSAT]), + VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), VWCarInfo("Volkswagen Passat Alltrack 2015-22"), VWCarInfo("Volkswagen Passat GTE 2015-22"), ], CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22"), CAR.POLO_MK6: [ - VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), - VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_MQB_A0]), + VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_MQB_A0]), ], CAR.SHARAN_MK2: [ VWCarInfo("Volkswagen Sharan 2018-22"), VWCarInfo("SEAT Alhambra 2018-20"), ], CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022"), - CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22"), CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2017"), CAR.TRANSPORTER_T61: [ VWCarInfo("Volkswagen Caravelle 2020"), VWCarInfo("Volkswagen California 2021"), ], - CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_MQB_A0]), CAR.AUDI_A3_MK3: [ VWCarInfo("Audi A3 2014-19"), VWCarInfo("Audi A3 Sportback e-tron 2017-18"), @@ -229,10 +231,10 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2019-23"), CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"), CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"), - CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0, Footnote.KAMIQ]), + CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"), - CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_MQB_A0]), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"), CAR.SKODA_OCTAVIA_MK3: [ VWCarInfo("Škoda Octavia 2015, 2018-19"), From c0c94b4961e8e32c3f8dbcfed2b7d91c28000085 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 21 Nov 2022 20:32:23 -0800 Subject: [PATCH 083/185] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 2e90b6f308..c075050d5d 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 2e90b6f308dc09bf1734f6cb5cc990cb8149486d +Subproject commit c075050d5df70570cfadd8c0d7507f25fe67d247 From 8ee07def5c8b21c479b9f342ba490887237b2d0f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 21 Nov 2022 21:18:35 -0800 Subject: [PATCH 084/185] jenkins: set hostname on startup --- selfdrive/test/setup_device_ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 02c7d76637..9e06b98662 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -29,6 +29,7 @@ sudo abctl --set_success # patch sshd config sudo mount -o rw,remount / +echo tici-$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//') | sudo tee /etc/hostname sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config sudo systemctl daemon-reload sudo systemctl restart ssh From e9dcabcef73aaffeee8eaaeab29d48e8277d98a6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 21 Nov 2022 23:13:41 -0800 Subject: [PATCH 085/185] boardd: end peripheral panda discrimination --- selfdrive/boardd/boardd.cc | 3 --- selfdrive/boardd/panda.cc | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 5496902252..1f1249194d 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -528,9 +528,6 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) { cnt++; sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update? - // Other pandas don't have fan/IR to control - if (panda->hw_type != cereal::PandaState::PandaType::UNO && panda->hw_type != cereal::PandaState::PandaType::DOS) continue; - if (sm.updated("deviceState") && !no_fan_control) { // Fan speed uint16_t fan_speed = sm["deviceState"].getDeviceState().getFanSpeedPercentDesired(); diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index deccee3e76..b82de593c8 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -24,7 +24,8 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { (hw_type != cereal::PandaState::PandaType::GREY_PANDA)); has_rtc = (hw_type == cereal::PandaState::PandaType::UNO) || - (hw_type == cereal::PandaState::PandaType::DOS); + (hw_type == cereal::PandaState::PandaType::DOS) || + (hw_type == cereal::PandaState::PandaType::TRES); return; } From 65fa87a96965000986e76604ac868891e35a86bc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 21 Nov 2022 23:43:24 -0800 Subject: [PATCH 086/185] cleanup panda types everywhere (#26574) * cleanup panda types everywhere * one more --- selfdrive/controls/controlsd.py | 11 ++-- selfdrive/controls/lib/events.py | 3 +- selfdrive/debug/test_fw_query_on_routes.py | 2 +- selfdrive/thermald/power_monitoring.py | 9 ++- .../thermald/tests/test_power_monitoring.py | 63 +++++++------------ selfdrive/thermald/thermald.py | 3 +- 6 files changed, 34 insertions(+), 57 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a395f85580..7b95b3b29c 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -275,12 +275,11 @@ class Controls: # self.events.add(EventName.highCpuUsage) # Alert if fan isn't spinning for 5 seconds - if self.sm['peripheralState'].pandaType == PandaType.dos: - if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50: - if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0: - self.events.add(EventName.fanMalfunction) - else: - self.last_functional_fan_frame = self.sm.frame + if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50: + if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0: + self.events.add(EventName.fanMalfunction) + else: + self.last_functional_fan_frame = self.sm.frame # Handle calibration status cal_status = self.sm['liveCalibration'].calStatus diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index a761cceecb..1ed2ffa865 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -250,10 +250,9 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag def no_gps_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - gps_integrated = sm['peripheralState'].pandaType in (log.PandaState.PandaType.uno, log.PandaState.PandaType.dos) return Alert( "Poor GPS reception", - "Hardware malfunctioning if sky is visible" if gps_integrated else "Check GPS antenna placement", + "Hardware malfunctioning if sky is visible", AlertStatus.normal, AlertSize.mid, Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.) diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 595e25e8c3..ba7d96dba0 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -68,7 +68,7 @@ if __name__ == "__main__": CP = None for msg in lr: if msg.which() == "pandaStates": - if msg.pandaStates[0].pandaType not in ('uno', 'blackPanda', 'dos'): + if msg.pandaStates[0].pandaType in ('unknown', 'whitePanda', 'greyPanda', 'pedal'): print("wrong panda type") break diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 7834569088..e62f0f97c3 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -1,7 +1,6 @@ import threading from typing import Optional -from cereal import log from common.params import Params, put_nonblocking from common.realtime import sec_since_boot from system.hardware import HARDWARE @@ -39,12 +38,12 @@ class PowerMonitoring: self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh)) # Calculation tick - def calculate(self, peripheralState, ignition): + def calculate(self, voltage: Optional[int], ignition: bool): try: now = sec_since_boot() # If peripheralState is None, we're probably not in a car, so we don't care - if peripheralState is None or peripheralState.pandaType == log.PandaState.PandaType.unknown: + if voltage is None: with self.integration_lock: self.last_measurement_time = None self.next_pulsed_measurement_time = None @@ -52,8 +51,8 @@ class PowerMonitoring: return # Low-pass battery voltage - self.car_voltage_instant_mV = peripheralState.voltage - self.car_voltage_mV = ((peripheralState.voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K))) + self.car_voltage_instant_mV = voltage + self.car_voltage_mV = ((voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K))) statlog.gauge("car_voltage", self.car_voltage_mV / 1e3) # Cap the car battery power and save it in a param every 10-ish seconds diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py index 5d7463d455..4a5def7740 100755 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ b/selfdrive/thermald/tests/test_power_monitoring.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 import unittest from unittest.mock import patch -from parameterized import parameterized -from cereal import log -import cereal.messaging as messaging from common.params import Params params = Params() @@ -21,7 +18,8 @@ with patch("common.realtime.sec_since_boot", new=mock_sec_since_boot): CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING TEST_DURATION_S = 50 -ALL_PANDA_TYPES = [(log.PandaState.PandaType.dos,)] +GOOD_VOLTAGE = 12 * 1e3 +VOLTAGE_BELOW_PAUSE_CHARGING = (VBATT_PAUSE_CHARGING - 1) * 1e3 def pm_patch(name, value, constant=False): if constant: @@ -34,12 +32,6 @@ class TestPowerMonitoring(unittest.TestCase): params.remove("CarBatteryCapacity") params.remove("DisablePowerDown") - def mock_peripheralState(self, hw_type, car_voltage=12): - ps = messaging.new_message('peripheralState').peripheralState - ps.pandaType = hw_type - ps.voltage = car_voltage * 1e3 - return ps - # Test to see that it doesn't do anything when pandaState is None def test_pandaState_present(self): pm = PowerMonitoring() @@ -49,75 +41,68 @@ class TestPowerMonitoring(unittest.TestCase): self.assertEqual(pm.get_car_battery_capacity(), (CAR_BATTERY_CAPACITY_uWh / 10)) # Test to see that it doesn't integrate offroad when ignition is True - @parameterized.expand(ALL_PANDA_TYPES) - def test_offroad_ignition(self, hw_type): + def test_offroad_ignition(self): pm = PowerMonitoring() for _ in range(10): - pm.calculate(self.mock_peripheralState(hw_type), True) + pm.calculate(GOOD_VOLTAGE, True) self.assertEqual(pm.get_power_used(), 0) # Test to see that it integrates with discharging battery - @parameterized.expand(ALL_PANDA_TYPES) - def test_offroad_integration_discharging(self, hw_type): + def test_offroad_integration_discharging(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), False) + pm.calculate(GOOD_VOLTAGE, False) expected_power_usage = ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) self.assertLess(abs(pm.get_power_used() - expected_power_usage), 10) # Test to check positive integration of car_battery_capacity - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_onroad(self, hw_type): + def test_car_battery_integration_onroad(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = 0 for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), True) + pm.calculate(GOOD_VOLTAGE, True) expected_capacity = ((TEST_DURATION_S/3600) * CAR_CHARGING_RATE_W * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10) # Test to check positive integration upper limit - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_upper_limit(self, hw_type): + def test_car_battery_integration_upper_limit(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - 1000 for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), True) + pm.calculate(GOOD_VOLTAGE, True) estimated_capacity = CAR_BATTERY_CAPACITY_uWh + (CAR_CHARGING_RATE_W / 3600 * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10) # Test to check negative integration of car_battery_capacity - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_offroad(self, hw_type): + def test_car_battery_integration_offroad(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), False) + pm.calculate(GOOD_VOLTAGE, False) expected_capacity = CAR_BATTERY_CAPACITY_uWh - ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10) # Test to check negative integration lower limit - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_lower_limit(self, hw_type): + def test_car_battery_integration_lower_limit(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = 1000 for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), False) + pm.calculate(GOOD_VOLTAGE, False) estimated_capacity = 0 - ((1/3600) * POWER_DRAW * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10) # Test to check policy of stopping charging after MAX_TIME_OFFROAD_S - @parameterized.expand(ALL_PANDA_TYPES) - def test_max_time_offroad(self, hw_type): + def test_max_time_offroad(self): MOCKED_MAX_OFFROAD_TIME = 3600 POWER_DRAW = 0 # To stop shutting down for other reasons with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): @@ -125,25 +110,22 @@ class TestPowerMonitoring(unittest.TestCase): pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh start_time = ssb ignition = False - peripheralState = self.mock_peripheralState(hw_type) while ssb <= start_time + MOCKED_MAX_OFFROAD_TIME: - pm.calculate(peripheralState, ignition) + pm.calculate(GOOD_VOLTAGE, ignition) if (ssb - start_time) % 1000 == 0 and ssb < start_time + MOCKED_MAX_OFFROAD_TIME: self.assertFalse(pm.should_shutdown(ignition, True, start_time, False)) self.assertTrue(pm.should_shutdown(ignition, True, start_time, False)) # Test to check policy of stopping charging when the car voltage is too low - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_voltage(self, hw_type): + def test_car_voltage(self): POWER_DRAW = 0 # To stop shutting down for other reasons TEST_TIME = 100 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = False - peripheralState = self.mock_peripheralState(hw_type, car_voltage=(VBATT_PAUSE_CHARGING - 1)) for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: self.assertEqual(pm.should_shutdown(ignition, True, ssb, True), (pm.car_voltage_mV < VBATT_PAUSE_CHARGING*1e3)) self.assertTrue(pm.should_shutdown(ignition, True, ssb, True)) @@ -157,9 +139,8 @@ class TestPowerMonitoring(unittest.TestCase): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = False - peripheralState = self.mock_peripheralState(log.PandaState.PandaType.uno, car_voltage=(VBATT_PAUSE_CHARGING - 1)) for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) @@ -172,9 +153,8 @@ class TestPowerMonitoring(unittest.TestCase): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = True - peripheralState = self.mock_peripheralState(log.PandaState.PandaType.uno, car_voltage=(VBATT_PAUSE_CHARGING - 1)) for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) @@ -188,9 +168,8 @@ class TestPowerMonitoring(unittest.TestCase): pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = False - peripheralState = self.mock_peripheralState(log.PandaState.PandaType.uno, car_voltage=(VBATT_PAUSE_CHARGING - 1)) for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: self.assertFalse(pm.should_shutdown(ignition, False, ssb, False)) self.assertFalse(pm.should_shutdown(ignition, False, ssb, False)) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 89b81f06ec..eedeff31f1 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -347,7 +347,8 @@ def thermald_thread(end_event, hw_queue): off_ts = sec_since_boot() # Offroad power monitoring - power_monitor.calculate(peripheralState, onroad_conditions["ignition"]) + voltage = None if peripheralState.pandaType == log.PandaState.PandaType.unknown else peripheralState.voltage + power_monitor.calculate(voltage, onroad_conditions["ignition"]) msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity()) current_power_draw = HARDWARE.get_current_power_draw() From ceb94f1bcd7eae99977e132fd596db353e4e11b4 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 22 Nov 2022 11:41:22 -0800 Subject: [PATCH 087/185] Revert "Albert Einstein Model" (#26576) Revert "Albert Einstein Model (#26456)" This reverts commit 34c80a6fbdf8780ad0b75f497c226cf1d9de0ddb. --- selfdrive/modeld/models/supercombo.onnx | 2 +- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index c0db988cf6..8805b3dce8 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c73998c9f428380dd2282b451de6011469b717498ae83578cbf7aa95948910f7 +oid sha256:db746e3753de84367595fedd089c2bd41b06bd401ea28e085663533d0e63d74b size 45962473 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index cdd6ed7a6b..f541b6a6d5 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -ca02aa240e629920ad88a6510213cb0a153af92b +30efb4238327d723e17a3bda7e7c19c18f8a3b18 From 7361d1a11b21cc6482ed9ba8e5505b88081b66f6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 22 Nov 2022 11:46:28 -0800 Subject: [PATCH 088/185] process replay: support old routes with no peripheralState --- selfdrive/controls/controlsd.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 7b95b3b29c..76f049ddd2 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -275,11 +275,12 @@ class Controls: # self.events.add(EventName.highCpuUsage) # Alert if fan isn't spinning for 5 seconds - if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50: - if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0: - self.events.add(EventName.fanMalfunction) - else: - self.last_functional_fan_frame = self.sm.frame + if self.sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown: + if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50: + if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0: + self.events.add(EventName.fanMalfunction) + else: + self.last_functional_fan_frame = self.sm.frame # Handle calibration status cal_status = self.sm['liveCalibration'].calStatus From 4662f1e0be77d6636ef7e3f663fb839448d86af6 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 23 Nov 2022 06:23:49 +0800 Subject: [PATCH 089/185] Cabana: auto theme, detect from system. (#26563) * auto theme * cleanup * get font color from ForegroundRole * fix label color * add padding for header * smaller warning icon * fix bg of binary view * hightlight after init --- tools/cabana/binaryview.cc | 29 ++++++++++++++++------------- tools/cabana/binaryview.h | 4 +++- tools/cabana/cabana.cc | 2 -- tools/cabana/chartswidget.cc | 4 +++- tools/cabana/chartswidget.h | 1 + tools/cabana/detailwidget.cc | 3 +-- tools/cabana/historylog.cc | 17 +++++++++++++++-- tools/cabana/historylog.h | 1 + tools/cabana/settings.cc | 8 -------- tools/cabana/settings.h | 2 -- tools/cabana/signaledit.cc | 2 +- 11 files changed, 41 insertions(+), 32 deletions(-) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index dae4591976..6a4f66dcd4 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -1,6 +1,5 @@ #include "tools/cabana/binaryview.h" -#include #include #include #include @@ -74,14 +73,18 @@ void BinaryView::mousePressEvent(QMouseEvent *event) { event->accept(); } -void BinaryView::mouseMoveEvent(QMouseEvent *event) { - if (auto index = indexAt(event->pos()); index.isValid()) { +void BinaryView::highlightPosition(const QPoint &pos) { + if (auto index = indexAt(viewport()->mapFromGlobal(pos)); index.isValid()) { auto item = (BinaryViewModel::Item *)index.internalPointer(); const Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); highlight(sig); - sig ? QToolTip::showText(event->globalPos(), sig->name.c_str(), this, rect()) + sig ? QToolTip::showText(pos, sig->name.c_str(), this, rect()) : QToolTip::hideText(); } +} + +void BinaryView::mouseMoveEvent(QMouseEvent *event) { + highlightPosition(event->globalPos()); QTableView::mouseMoveEvent(event); } @@ -116,6 +119,7 @@ void BinaryView::setMessage(const QString &message_id) { anchor_index = QModelIndex(); resize_sig = nullptr; hovered_sig = nullptr; + highlightPosition(QCursor::pos()); updateState(); } @@ -232,19 +236,18 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op BinaryView *bin_view = (BinaryView *)parent(); painter->save(); - // background - if (option.state & QStyle::State_Selected) { + if (index.column() == 8) { + painter->setFont(hex_font); + } else if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, selection_color); - } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { + painter->setPen(QApplication::style()->standardPalette().color(QPalette::BrightText)); + } else if (!item->sigs.isEmpty() && (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig))) { painter->fillRect(option.rect, item->bg_color); + painter->setPen(item->sigs.contains(bin_view->hovered_sig) + ? QApplication::style()->standardPalette().color(QPalette::BrightText) + : Qt::black); } - // text - if (index.column() == 8) { // hex column - painter->setFont(hex_font); - } else if (option.state & QStyle::State_Selected || (!bin_view->resize_sig && item->sigs.contains(bin_view->hovered_sig))) { - painter->setPen(Qt::white); - } painter->drawText(option.rect, Qt::AlignCenter, item->val); if (item->is_msb || item->is_lsb) { painter->setFont(small_font); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 0ea111d917..c37b378e44 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -41,7 +42,7 @@ public: } struct Item { - QColor bg_color = QColor(Qt::white); + QColor bg_color = QApplication::style()->standardPalette().color(QPalette::Base); bool is_msb = false; bool is_lsb = false; QString val = "0"; @@ -79,6 +80,7 @@ private: void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void leaveEvent(QEvent *event) override; + void highlightPosition(const QPoint &pt); QModelIndex anchor_index; BinaryViewModel *model; diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 1096487973..e7e3eb213b 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -1,6 +1,5 @@ #include #include -#include #include "selfdrive/ui/qt/util.h" #include "tools/cabana/mainwin.h" @@ -8,7 +7,6 @@ int main(int argc, char *argv[]) { initApp(argc, argv); QApplication app(argc, argv); - app.setStyle(QStyleFactory::create("Fusion")); QCommandLineParser cmd_parser; cmd_parser.addHelpOption(); diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 5d530a8c8a..6e1f2e110c 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -40,6 +40,8 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { main_layout->addWidget(charts_scroll); + use_dark_theme = palette().color(QPalette::WindowText).value() > palette().color(QPalette::Background).value(); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged); QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState); @@ -125,6 +127,7 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bo ChartView *chart = merge && charts.size() > 0 ? charts.back() : nullptr; if (!chart) { chart = new ChartView(this); + chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight); chart->setEventsRange(display_range); auto range = is_zoomed ? zoomed_range : display_range; chart->setDisplayRange(range.first, range.second); @@ -288,7 +291,6 @@ void ChartView::updateTitle() { void ChartView::updateFromSettings() { setFixedHeight(settings.chart_height); - chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); } void ChartView::setEventsRange(const std::pair &range) { diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index c8e5a1040c..c3fa931e6e 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -112,4 +112,5 @@ private: std::pair event_range; std::pair display_range; std::pair zoomed_range; + bool use_dark_theme = false; }; diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 52ae530a56..bf857bd596 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -25,7 +25,6 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart // tabbar tabbar = new QTabBar(this); tabbar->setTabsClosable(true); - tabbar->setDrawBase(false); tabbar->setUsesScrollButtons(true); tabbar->setAutoHide(true); tabbar->setContextMenuPolicy(Qt::CustomContextMenu); @@ -57,7 +56,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart QHBoxLayout *warning_hlayout = new QHBoxLayout(warning_widget); warning_hlayout->setContentsMargins(0, 0, 0, 0); QLabel *warning_icon = new QLabel(this); - warning_icon->setPixmap(style()->standardPixmap(QStyle::SP_MessageBoxWarning)); + warning_icon->setPixmap(style()->standardPixmap(QStyle::SP_MessageBoxWarning).scaledToWidth(24, Qt::SmoothTransformation)); warning_hlayout->addWidget(warning_icon, 0, Qt::AlignTop); warning_label = new QLabel(this); warning_hlayout->addWidget(warning_label, 1, Qt::AlignLeft); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 1b0898afbd..65fea3361e 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -1,6 +1,7 @@ #include "tools/cabana/historylog.h" #include +#include // HistoryLogModel @@ -44,6 +45,8 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i return has_signal ? QString::fromStdString(get_signal(dbc_msg, section - 1).name).replace('_', ' ') : "Data"; } else if (role == Qt::BackgroundRole && section > 0 && has_signal) { return QBrush(QColor(getColor(section - 1))); + } else if (role == Qt::ForegroundRole && section > 0 && has_signal) { + return QBrush(Qt::black); } } return {}; @@ -73,7 +76,18 @@ void HistoryLogModel::updateState() { QSize HeaderView::sectionSizeFromContents(int logicalIndex) const { const QString text = model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString(); const QRect rect = fontMetrics().boundingRect(QRect(0, 0, sectionSize(logicalIndex), 1000), defaultAlignment(), text); - return rect.size() + QSize{10, 5}; + return rect.size() + QSize{10, 6}; +} + +void HeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const { + auto bg_role = model()->headerData(logicalIndex, Qt::Horizontal, Qt::BackgroundRole); + if (bg_role.isValid()) { + QPen pen(model()->headerData(logicalIndex, Qt::Horizontal, Qt::ForegroundRole).value(), 1); + painter->setPen(pen); + painter->fillRect(rect, bg_role.value()); + } + QString text = model()->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString(); + painter->drawText(rect.adjusted(5, 3, 5, 3), defaultAlignment(), text); } // HistoryLog @@ -88,7 +102,6 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { verticalHeader()->setVisible(false); setFrameShape(QFrame::NoFrame); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); } int HistoryLog::sizeHintForColumn(int column) const { diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 21be8fc129..dfe037c13f 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -10,6 +10,7 @@ class HeaderView : public QHeaderView { public: HeaderView(Qt::Orientation orientation, QWidget *parent = nullptr) : QHeaderView(orientation, parent) {} QSize sectionSizeFromContents(int logicalIndex) const override; + void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; }; class HistoryLogModel : public QAbstractTableModel { diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index be806aa705..c90830973b 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -18,7 +18,6 @@ void Settings::save() { s.setValue("log_size", can_msg_log_size); s.setValue("cached_segment", cached_segment_limit); s.setValue("chart_height", chart_height); - s.setValue("chart_theme", chart_theme); s.setValue("max_chart_x_range", max_chart_x_range); s.setValue("last_dir", last_dir); s.setValue("splitter_state", splitter_state); @@ -30,7 +29,6 @@ void Settings::load() { can_msg_log_size = s.value("log_size", 50).toInt(); cached_segment_limit = s.value("cached_segment", 3).toInt(); chart_height = s.value("chart_height", 200).toInt(); - chart_theme = s.value("chart_theme", 0).toInt(); max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt(); last_dir = s.value("last_dir", QDir::homePath()).toString(); splitter_state = s.value("splitter_state").toByteArray(); @@ -72,11 +70,6 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { chart_height->setValue(settings.chart_height); form_layout->addRow(tr("Chart height"), chart_height); - chart_theme = new QComboBox(); - chart_theme->addItems({"Light", "Dark"}); - chart_theme->setCurrentIndex(settings.chart_theme == 1 ? 1 : 0); - form_layout->addRow(tr("Chart theme"), chart_theme); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); form_layout->addRow(buttonBox); @@ -90,7 +83,6 @@ void SettingsDlg::save() { settings.can_msg_log_size = log_size->value(); settings.cached_segment_limit = cached_segment->value(); settings.chart_height = chart_height->value(); - settings.chart_theme = chart_theme->currentIndex(); settings.max_chart_x_range = max_chart_x_range->value() * 60; settings.save(); accept(); diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index 624a1ce33d..ee6541798d 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -17,7 +17,6 @@ public: int can_msg_log_size = 50; int cached_segment_limit = 3; int chart_height = 200; - int chart_theme = 0; int max_chart_x_range = 3 * 60; // 3 minutes QString last_dir; QByteArray splitter_state; @@ -36,7 +35,6 @@ public: QSpinBox *log_size ; QSpinBox *cached_segment; QSpinBox *chart_height; - QComboBox *chart_theme; QSpinBox *max_chart_x_range; }; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 5d627a49da..254b11efaa 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -137,7 +137,7 @@ void SignalEdit::setSignal(const QString &message_id, const Signal *signal) { updateForm(msg_id == message_id && form->isVisible()); msg_id = message_id; color_label->setText(QString::number(form_idx + 1)); - color_label->setStyleSheet(QString("background-color:%1").arg(getColor(form_idx))); + color_label->setStyleSheet(QString("color:black; background-color:%2").arg(getColor(form_idx))); title->setText(sig->name.c_str()); show(); } From 9fdbe9f397b79f33c0653f4e9dd3b1cbb2e92273 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 22 Nov 2022 15:18:10 -0800 Subject: [PATCH 090/185] GM Cam: log when cruise not adaptive (#26581) * bump opendbc * log when nonAdaptive * Update ref_commit --- opendbc | 2 +- selfdrive/car/gm/carstate.py | 4 ++++ selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/opendbc b/opendbc index 296f190000..871e054d9a 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 296f190000a2e71408e207ba21a2257cc853ec15 +Subproject commit 871e054d9a94629d92c22fe89cae71af5b0d3823 diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index df1b4b2866..de0fd2eed6 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -100,6 +100,9 @@ class CarState(CarStateBase): if self.CP.networkLocation == NetworkLocation.fwdCamera: ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0 + # openpilot controls nonAdaptive when not pcmCruise + if self.CP.pcmCruise: + ret.cruiseState.nonAdaptive = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCruiseState"] not in (2, 3) return ret @@ -112,6 +115,7 @@ class CarState(CarStateBase): ("AEBCmdActive", "AEBCmd"), ("RollingCounter", "ASCMLKASteeringCmd"), ("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"), + ("ACCCruiseState", "ASCMActiveCruiseControlStatus"), ] checks += [ ("AEBCmd", 10), diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 4aa9d60ab5..770b4e40f7 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -959e63af52d9efdb62156cab4b773f88b154fd75 +ff508a46616a1a3d66e8d1154d123ffd11025003 From 636ff5979fc19fe0a965fbceae214694c97c1e3d Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Tue, 22 Nov 2022 19:18:42 -0500 Subject: [PATCH 091/185] VW MQB: Redundant brake pressed signals (#26401) * VW MQB: Redundant brake pressed signals * retry CI * update refs * update refs Co-authored-by: Adeeb Shihadeh --- panda | 2 +- selfdrive/car/volkswagen/carstate.py | 11 +++++++---- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/panda b/panda index c075050d5d..e4c4253964 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c075050d5df70570cfadd8c0d7507f25fe67d247 +Subproject commit e4c4253964a25ff980520b70ea9f50aede4a1db6 diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index def14ab019..7d0a3d4dca 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -62,7 +62,9 @@ class CarState(CarStateBase): ret.gas = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] / 100.0 ret.gasPressed = ret.gas > 0 ret.brake = pt_cp.vl["ESP_05"]["ESP_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects - ret.brakePressed = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) + brake_pedal_pressed = bool(pt_cp.vl["Motor_14"]["MO_Fahrer_bremst"]) + brake_pressure_detected = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) + ret.brakePressed = brake_pedal_pressed or brake_pressure_detected ret.parkingBrake = bool(pt_cp.vl["Kombi_01"]["KBI_Handbremse"]) # FIXME: need to include an EPB check as well # Update gear and/or clutch position data. @@ -268,8 +270,9 @@ class CarState(CarStateBase): ("Comfort_Signal_Right", "Blinkmodi_02"), # Right turn signal including comfort blink interval ("AB_Gurtschloss_FA", "Airbag_02"), # Seatbelt status, driver ("AB_Gurtschloss_BF", "Airbag_02"), # Seatbelt status, passenger - ("ESP_Fahrer_bremst", "ESP_05"), # Brake pedal pressed - ("ESP_Bremsdruck", "ESP_05"), # Brake pressure applied + ("ESP_Fahrer_bremst", "ESP_05"), # Driver applied brake pressure over threshold + ("MO_Fahrer_bremst", "Motor_14"), # Brake pedal switch + ("ESP_Bremsdruck", "ESP_05"), # Brake pressure ("MO_Fahrpedalrohwert_01", "Motor_20"), # Accelerator pedal value ("EPS_Lenkmoment", "LH_EPS_03"), # Absolute driver torque input ("EPS_VZ_Lenkmoment", "LH_EPS_03"), # Driver torque input sign @@ -304,6 +307,7 @@ class CarState(CarStateBase): ("ESP_02", 50), # From J104 ABS/ESP controller ("GRA_ACC_01", 33), # From J533 CAN gateway (via LIN from steering wheel controls) ("Gateway_72", 10), # From J533 CAN gateway (aggregated data) + ("Motor_14", 10), # From J623 Engine control module ("Airbag_02", 5), # From J234 Airbag control module ("Kombi_01", 2), # From J285 Instrument cluster ("Blinkmodi_02", 1), # From J519 BCM (sent at 1Hz when no lights active, 50Hz when active) @@ -318,7 +322,6 @@ class CarState(CarStateBase): elif CP.transmissionType == TransmissionType.manual: signals += [("MO_Kuppl_schalter", "Motor_14"), # Clutch switch ("BCM1_Rueckfahrlicht_Schalter", "Gateway_72")] # Reverse light from BCM - checks.append(("Motor_14", 10)) # From J623 Engine control module if CP.networkLocation == NetworkLocation.fwdCamera: # Radars are here on CANBUS.pt diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 770b4e40f7..d4520d2935 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -ff508a46616a1a3d66e8d1154d123ffd11025003 +08ca448a6dcfc1edc4f9fedb2ea94ad6b2b69aa2 \ No newline at end of file From c1e7bed061424387aaf5a91eaa43e96752995cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 22 Nov 2022 20:20:58 -0800 Subject: [PATCH 092/185] Navd map renderer: update to larger image (#26580) * rerender 512 * rerender 512 * Removed unnecessary code * rgb -> grayscale * Add constants Co-authored-by: mitchellgoffpc --- selfdrive/navd/map_renderer.cc | 41 +++++++++++++--------------------- selfdrive/navd/map_renderer.py | 3 ++- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index daf89b2636..247b69c4c5 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -11,33 +11,18 @@ #include "selfdrive/ui/qt/maps/map_helpers.h" const float DEFAULT_ZOOM = 13.5; // Don't go below 13 or features will start to disappear -const int RENDER_HEIGHT = 512, RENDER_WIDTH = 512; -const int HEIGHT = 256, WIDTH = 256; +const int HEIGHT = 512, WIDTH = 512; const int NUM_VIPC_BUFFERS = 4; const int EARTH_CIRCUMFERENCE_METERS = 40075000; const int PIXELS_PER_TILE = 256; -float get_meters_per_pixel(float lat, float zoom) { - float num_tiles = pow(2, zoom+1); - float meters_per_tile = cos(DEG2RAD(lat)) * EARTH_CIRCUMFERENCE_METERS / num_tiles; - return meters_per_tile / PIXELS_PER_TILE; -} - float get_zoom_level_for_scale(float lat, float meters_per_pixel) { float meters_per_tile = meters_per_pixel * PIXELS_PER_TILE; float num_tiles = cos(DEG2RAD(lat)) * EARTH_CIRCUMFERENCE_METERS / meters_per_tile; return log2(num_tiles) - 1; } -void downsample(uint8_t *src, uint8_t *dst) { - for (int r = 0; r < HEIGHT; r++) { - for (int c = 0; c < WIDTH; c++) { - dst[r*WIDTH + c] = src[(r*2*RENDER_WIDTH + c*2) * 3]; - } - } -} - MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_settings(settings) { QSurfaceFormat fmt; @@ -59,7 +44,7 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set gl_functions->initializeOpenGLFunctions(); QOpenGLFramebufferObjectFormat fbo_format; - fbo.reset(new QOpenGLFramebufferObject(RENDER_WIDTH, RENDER_HEIGHT, fbo_format)); + fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); std::string style = util::read_file(STYLE_PATH); m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); @@ -69,7 +54,7 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set m_map->resize(fbo->size()); m_map->setFramebufferObject(fbo->handle(), fbo->size()); - gl_functions->glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT); + gl_functions->glViewport(0, 0, WIDTH, HEIGHT); if (online) { vipc_server.reset(new VisionIpcServer("navd")); @@ -114,9 +99,9 @@ void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { return; } - // Choose a zoom level that matches the scale of zoom level 13 at latitude 80deg - float scale_lat80 = get_meters_per_pixel(80, 13); - float zoom = get_zoom_level_for_scale(position.first, scale_lat80); + // Choose a scale that ensures above 13 zoom level up to and above 75deg of lat + float meters_per_pixel = 2; + float zoom = get_zoom_level_for_scale(position.first, meters_per_pixel); m_map->setCoordinate(position); m_map->setBearing(bearing); @@ -150,13 +135,15 @@ void MapRenderer::sendVipc() { .timestamp_eof = ts, }; - assert(cap.sizeInBytes() >= buf->len*4); + assert(cap.sizeInBytes() >= buf->len); uint8_t* dst = (uint8_t*)buf->addr; uint8_t* src = cap.bits(); - // 2x downsample + rgb to grayscale + // RGB to greyscale memset(dst, 128, buf->len); - downsample(src, dst); + for (int i = 0; i < WIDTH * HEIGHT; i++) { + dst[i] = src[i * 3]; + } vipc_server->send(buf, &extra); @@ -187,8 +174,10 @@ uint8_t* MapRenderer::getImage() { uint8_t* src = cap.bits(); uint8_t* dst = new uint8_t[WIDTH * HEIGHT]; - // 2x downsample + rgb to grayscale - downsample(src, dst); + // RGB to greyscale + for (int i = 0; i < WIDTH * HEIGHT; i++) { + dst[i] = src[i * 3]; + } return dst; } diff --git a/selfdrive/navd/map_renderer.py b/selfdrive/navd/map_renderer.py index 9000622928..868307bb63 100755 --- a/selfdrive/navd/map_renderer.py +++ b/selfdrive/navd/map_renderer.py @@ -9,7 +9,8 @@ from cffi import FFI from common.ffi_wrapper import suffix from common.basedir import BASEDIR -HEIGHT = WIDTH = 256 +HEIGHT = WIDTH = SIZE = 512 +METERS_PER_PIXEL = 2 def get_ffi(): From 13cdddc6c806c963b65c477d409329ea717c7e11 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 22 Nov 2022 22:28:11 -0800 Subject: [PATCH 093/185] Toyota: whitelist FW queries (#26584) whitelist toyota --- selfdrive/car/toyota/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index cf37a3c2f2..6d6c03df71 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -209,16 +209,19 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE], + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps], bus=0, ), Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, StdQueries.OBD_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, StdQueries.OBD_VERSION_RESPONSE], + whitelist_ecus=[Ecu.engine], bus=0, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.EXTENDED_DIAGNOSTIC_REQUEST, StdQueries.UDS_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.EXTENDED_DIAGNOSTIC_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], + whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps], bus=0, ), ], From 059e4f5730618812221b080c69f83fb36b68c5a7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 22 Nov 2022 23:43:23 -0800 Subject: [PATCH 094/185] Toyota: always log cruise standstill (#26586) these cars def are in cruise standstill, no sense to not log --- selfdrive/car/toyota/carstate.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 8efb2c79e3..dbbb8a6f04 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -6,7 +6,7 @@ from common.realtime import DT_CTRL from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.toyota.values import ToyotaFlags, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR +from selfdrive.car.toyota.values import ToyotaFlags, DBC, STEER_THRESHOLD, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR class CarState(CarStateBase): @@ -120,12 +120,7 @@ class CarState(CarStateBase): self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] - if self.CP.carFingerprint in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor: - # ignore standstill in hybrid vehicles, since pcm allows to restart without - # receiving any special command. Also if interceptor is detected - ret.cruiseState.standstill = False - else: - ret.cruiseState.standstill = self.pcm_acc_status == 7 + ret.cruiseState.standstill = self.pcm_acc_status == 7 ret.cruiseState.enabled = bool(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"]) ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in (1, 2, 3, 4, 5, 6) From 6dec92df439183ff15155654e558b37ffa861631 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 24 Nov 2022 01:48:24 +0800 Subject: [PATCH 095/185] Cabana: fixed form glitches (#26587) fix visual glitches --- tools/cabana/detailwidget.cc | 15 ++++++--------- tools/cabana/detailwidget.h | 1 - tools/cabana/signaledit.cc | 2 +- tools/cabana/signaledit.h | 5 ++--- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index bf857bd596..c20e2b672e 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -153,10 +153,10 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { for (auto sig : msg->getSignals()) { SignalEdit *form = i < signal_list.size() ? signal_list[i] : nullptr; if (!form) { - form = new SignalEdit(i); + form = new SignalEdit(i, this); QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); - QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showFormClicked); + QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); QObject::connect(form, &SignalEdit::showChart, charts, &ChartsWidget::showChart); @@ -196,16 +196,13 @@ void DetailWidget::updateState(const QHash * msgs) { history_log->updateState(); } -void DetailWidget::showFormClicked() { - auto s = qobject_cast(sender()); - showForm(s->sig); -} - void DetailWidget::showForm(const Signal *sig) { setUpdatesEnabled(false); for (auto f : signal_list) { - f->updateForm(f->sig == sig && !f->isFormVisible()); - if (f->sig == sig) scroll->ensureWidgetVisible(f); + f->updateForm(f->sig == sig && !f->form->isVisible()); + if (f->sig == sig && f->form->isVisible()) { + QTimer::singleShot(0, [=]() { scroll->ensureWidgetVisible(f); }); + } } setUpdatesEnabled(true); } diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 41fa0edd41..75409fa706 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -29,7 +29,6 @@ public: private: void showForm(const Signal *sig); - void showFormClicked(); void updateChartState(const QString &id, const Signal *sig, bool opened); void showTabBarContextMenu(const QPoint &pt); void addSignal(int start_bit, int size, bool little_endian); diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 254b11efaa..85476c2302 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -122,7 +122,7 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa save_timer->setSingleShot(true); save_timer->callOnTimeout(this, &SignalEdit::saveSignal); - QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); + QObject::connect(title, &ElidedLabel::clicked, [this]() { emit showFormClicked(sig); }); QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); }); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index f035797e72..205be4bb78 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -36,8 +36,8 @@ public: void setChartOpened(bool opened); void signalHovered(const Signal *sig); void updateForm(bool show); - inline bool isFormVisible() const { return form->isVisible(); } const Signal *sig = nullptr; + SignalForm *form = nullptr; QString msg_id; signals: @@ -45,14 +45,13 @@ signals: void showChart(const QString &name, const Signal *sig, bool show, bool merge); void remove(const Signal *sig); void save(const Signal *sig, const Signal &new_sig); - void showFormClicked(); + void showFormClicked(const Signal *sig); protected: void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; void saveSignal(); - SignalForm *form = nullptr; ElidedLabel *title; QLabel *color_label; QLabel *icon; From 2153bfe27a99b2ecd36ff91fcd10c3e71d952bbe Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 23 Nov 2022 13:08:04 -0800 Subject: [PATCH 096/185] camerad: minor ae improvement to OX (#26566) --- system/camerad/cameras/camera_qcom2.cc | 6 +++++- system/camerad/cameras/camera_qcom2.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 2ee06e372a..df5b9e8bf0 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -96,10 +96,12 @@ const uint32_t ox03c10_analog_gains_reg[] = { const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x +const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; +const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms @@ -532,6 +534,7 @@ void CameraState::camera_set_parameters() { analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_AR0231; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; for (int i=0; i<=analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; } @@ -548,6 +551,7 @@ void CameraState::camera_set_parameters() { analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_OX03C10; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; for (int i=0; i<=analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; } @@ -1110,7 +1114,7 @@ void CameraState::set_camera_exposure(float grey_frac) { // LOGE("cam: %d - gain: %d, t: %d (%.2f), score %.2f, score + gain %.2f, %.3f, %.3f", camera_num, g, t, desired_ev / gain, score, score + std::abs(g - gain_idx) * (score + 1.0) / 10.0, desired_ev, min_ev); // Small penalty on changing gain - score += std::abs(g - gain_idx) * (score + 1.0) / 10.0; + score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (g - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * std::abs(g - gain_idx) * (score + 1.0) / 10.0; if (score < best_ev_score) { new_t = t; diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 1e25e605c5..150ea0a1c0 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -41,6 +41,7 @@ public: int analog_gain_min_idx; int analog_gain_max_idx; int analog_gain_rec_idx; + int analog_gain_cost_delta; float cur_ev[3]; float min_ev, max_ev; From b15018387c774b9f470e88f22b96861d62917656 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 23 Nov 2022 14:03:15 -0800 Subject: [PATCH 097/185] tuning: measure steering accuracy for existing routes (#25722) * support parsing route logs * optionally use cached data * support segment or route names * get control type from log --- tools/tuning/measure_steering_accuracy.py | 153 +++++++++++++--------- 1 file changed, 89 insertions(+), 64 deletions(-) diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 7abfb6358a..4819be770f 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -8,24 +8,13 @@ import signal from collections import defaultdict import cereal.messaging as messaging +from tools.lib.logreader import logreader_from_route_or_segment def sigint_handler(signal, frame): - print("handler!") exit(0) signal.signal(signal.SIGINT, sigint_handler) -if __name__ == "__main__": - - parser = argparse.ArgumentParser(description='Sniff a communication socket') - parser.add_argument('control_type', help="[pid|indi|lqr|angle]") - parser.add_argument('--addr', default='127.0.0.1', help="IP address for optional ZMQ listener, default to msgq") - parser.add_argument('--group', default='all', help="speed group to display, [crawl|slow|medium|fast|veryfast|germany|all], default to all") - args = parser.parse_args() - - if args.addr != "127.0.0.1": - os.environ["ZMQ"] = "1" - messaging.context = messaging.Context() - +class SteeringAccuracyTool: all_groups = {"germany": (45, "45 - up m/s // 162 - up km/h // 101 - up mph"), "veryfast": (35, "35 - 45 m/s // 126 - 162 km/h // 78 - 101 mph"), "fast": (25, "25 - 35 m/s // 90 - 126 km/h // 56 - 78 mph"), @@ -33,39 +22,28 @@ if __name__ == "__main__": "slow": (5, " 5 - 15 m/s // 18 - 54 km/h // 11 - 34 mph"), "crawl": (0, " 0 - 5 m/s // 0 - 18 km/h // 0 - 11 mph")} - if args.group == "all": - display_groups = all_groups.keys() - elif args.group in all_groups.keys(): - display_groups = [args.group] - else: - raise ValueError("invalid speed group, see help") - - speed_group_stats = {} - for group in all_groups: - speed_group_stats[group] = defaultdict(lambda: {'err': 0, "cnt": 0, "=": 0, "+": 0, "-": 0, "steer": 0, "limited": 0, "saturated": 0, "dpp": 0}) - - carControl = messaging.sub_sock('carControl', addr=args.addr, conflate=True) - sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'lateralPlan'], addr=args.addr) - time.sleep(1) # Make sure all submaster data is available before going further - - msg_cnt = 0 - cnt = 0 - total_error = 0 - - while messaging.recv_one(carControl): - sm.update() - msg_cnt += 1 - - if args.control_type == "pid": - control_state = sm['controlsState'].lateralControlState.pidState - elif args.control_type == "indi": - control_state = sm['controlsState'].lateralControlState.indiState - elif args.control_type == "lqr": - control_state = sm['controlsState'].lateralControlState.lqrState - elif args.control_type == "angle": - control_state = sm['controlsState'].lateralControlState.angleState + def __init__(self, args): + self.msg_cnt = 0 + self.cnt = 0 + self.total_error = 0 + + if args.group == "all": + self.display_groups = self.all_groups.keys() + elif args.group in self.all_groups.keys(): + self.display_groups = [args.group] else: - raise ValueError("invalid lateral control type, see help") + raise ValueError("invalid speed group, see help") + + self.speed_group_stats = {} + for group in self.all_groups: + self.speed_group_stats[group] = defaultdict(lambda: {'err': 0, "cnt": 0, "=": 0, "+": 0, "-": 0, "steer": 0, "limited": 0, "saturated": 0, "dpp": 0}) + + def update(self, sm): + self.msg_cnt += 1 + + lateralControlState = sm['controlsState'].lateralControlState + control_type = list(lateralControlState.to_dict().keys())[0] + control_state = lateralControlState.__getattr__(control_type) v_ego = sm['carState'].vEgo active = sm['controlsState'].active @@ -77,10 +55,10 @@ if __name__ == "__main__": d_path_points = sm['lateralPlan'].dPathPoints # must be engaged, not at standstill, not overriding steering, and not changing lanes if active and not standstill and not overriding and not changing_lanes: - cnt += 1 + self.cnt += 1 # wait 5 seconds after engage / standstill / override / lane change - if cnt >= 500: + if self.cnt >= 500: actual_angle = control_state.steeringAngleDeg desired_angle = control_state.steeringAngleDesiredDeg @@ -91,41 +69,88 @@ if __name__ == "__main__": angle_error = round(angle_error, 2) angle_abs = int(abs(round(desired_angle, 0))) - for group, group_props in all_groups.items(): + for group, group_props in self.all_groups.items(): if v_ego > group_props[0]: # collect stats - speed_group_stats[group][angle_abs]["cnt"] += 1 - speed_group_stats[group][angle_abs]["err"] += angle_error - speed_group_stats[group][angle_abs]["steer"] += abs(steer) + self.speed_group_stats[group][angle_abs]["cnt"] += 1 + self.speed_group_stats[group][angle_abs]["err"] += angle_error + self.speed_group_stats[group][angle_abs]["steer"] += abs(steer) if len(d_path_points): - speed_group_stats[group][angle_abs]["dpp"] += abs(d_path_points[0]) + self.speed_group_stats[group][angle_abs]["dpp"] += abs(d_path_points[0]) if steer_limited: - speed_group_stats[group][angle_abs]["limited"] += 1 + self.speed_group_stats[group][angle_abs]["limited"] += 1 if control_state.saturated: - speed_group_stats[group][angle_abs]["saturated"] += 1 + self.speed_group_stats[group][angle_abs]["saturated"] += 1 if actual_angle == desired_angle: - speed_group_stats[group][angle_abs]["="] += 1 + self.speed_group_stats[group][angle_abs]["="] += 1 else: if desired_angle == 0.: overshoot = True else: overshoot = desired_angle < actual_angle if desired_angle > 0. else desired_angle > actual_angle - speed_group_stats[group][angle_abs]["+" if overshoot else "-"] += 1 + self.speed_group_stats[group][angle_abs]["+" if overshoot else "-"] += 1 break else: - cnt = 0 + self.cnt = 0 - if msg_cnt % 100 == 0: + if self.msg_cnt % 100 == 0: print(chr(27) + "[2J") - if cnt != 0: + if self.cnt != 0: print("COLLECTING ...\n") else: print("DISABLED (not active, standstill, steering override, or lane change)\n") - for group in display_groups: - if len(speed_group_stats[group]) > 0: - print(f"speed group: {group:10s} {all_groups[group][1]:>96s}") + for group in self.display_groups: + if len(self.speed_group_stats[group]) > 0: + print(f"speed group: {group:10s} {self.all_groups[group][1]:>96s}") print(f" {'-'*118}") - for k in sorted(speed_group_stats[group].keys()): - v = speed_group_stats[group][k] + for k in sorted(self.speed_group_stats[group].keys()): + v = self.speed_group_stats[group][k] print(f' {k:#2}° | actuator:{int(v["steer"] / v["cnt"] * 100):#3}% | error: {round(v["err"] / v["cnt"], 2):2.2f}° | -:{int(v["-"] / v["cnt"] * 100):#3}% | =:{int(v["="] / v["cnt"] * 100):#3}% | +:{int(v["+"] / v["cnt"] * 100):#3}% | lim:{v["limited"]:#5} | sat:{v["saturated"]:#5} | path dev: {round(v["dpp"] / v["cnt"], 2):2.2f}m | total: {v["cnt"]:#5}') print("") + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Steering accuracy measurement tool') + parser.add_argument('--route', help="route name") + parser.add_argument('--addr', default='127.0.0.1', help="IP address for optional ZMQ listener, default to msgq") + parser.add_argument('--group', default='all', help="speed group to display, [crawl|slow|medium|fast|veryfast|germany|all], default to all") + parser.add_argument('--cache', default=False, action='store_true', help="use cached data, default to False") + args = parser.parse_args() + + if args.cache: + os.environ['FILEREADER_CACHE'] = '1' + + tool = SteeringAccuracyTool(args) + + if args.route is not None: + print(f"loading {args.route}...") + lr = logreader_from_route_or_segment(args.route, sort_by_time=True) + + sm = {} + for msg in lr: + if msg.which() == 'carState': + sm['carState'] = msg.carState + elif msg.which() == 'carControl': + sm['carControl'] = msg.carControl + elif msg.which() == 'controlsState': + sm['controlsState'] = msg.controlsState + elif msg.which() == 'lateralPlan': + sm['lateralPlan'] = msg.lateralPlan + + if msg.which() == 'carControl' and 'carState' in sm and 'controlsState' in sm and 'lateralPlan' in sm: + tool.update(sm) + + else: + if args.addr != "127.0.0.1": + os.environ["ZMQ"] = "1" + messaging.context = messaging.Context() + + carControl = messaging.sub_sock('carControl', addr=args.addr, conflate=True) + sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'lateralPlan'], addr=args.addr) + time.sleep(1) # Make sure all submaster data is available before going further + + print("waiting for messages...") + while messaging.recv_one(carControl): + sm.update() + tool.update(sm) From a19b5b91d2bcc0cd405599193828b9e9e187c52e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 24 Nov 2022 19:17:36 -0700 Subject: [PATCH 098/185] longcontrol: ignore cruise standstill if interceptor (#26597) * ignore standstill * cmt * standstill * rm cmt * flip --- selfdrive/controls/lib/longcontrol.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 92a4f1f99b..545a4c43ff 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -10,12 +10,14 @@ LongCtrlState = car.CarControl.Actuators.LongControlState def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, v_target_1sec, brake_pressed, cruise_standstill): + # Ignore cruise standstill if car has a gas interceptor + cruise_standstill = cruise_standstill and not CP.enableGasInterceptor accelerating = v_target_1sec > v_target planned_stop = (v_target < CP.vEgoStopping and v_target_1sec < CP.vEgoStopping and not accelerating) stay_stopped = (v_ego < CP.vEgoStopping and - (brake_pressed or cruise_standstill)) + (brake_pressed or cruise_standstill)) stopping_condition = planned_stop or stay_stopped starting_condition = (v_target_1sec > CP.vEgoStarting and From 1efc8b9c78880778f4f604058c639ebdb2e12ff5 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Fri, 25 Nov 2022 13:20:20 +0100 Subject: [PATCH 099/185] Fix Tesla cancel button definition (#26596) misinterpreted the dbc --- selfdrive/car/tesla/values.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index e28666c625..750fe885e8 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -99,8 +99,8 @@ BUTTONS = [ Button(car.CarState.ButtonEvent.Type.rightBlinker, "STW_ACTN_RQ", "TurnIndLvr_Stat", [2]), Button(car.CarState.ButtonEvent.Type.accelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [4, 16]), Button(car.CarState.ButtonEvent.Type.decelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [8, 32]), - Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]), - Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]), + Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]), + Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]), ] class CarControllerParams: From 3253b8590002fa5f56e003add3f5f47a21870873 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 25 Nov 2022 17:00:58 -0700 Subject: [PATCH 100/185] Toyota: alert when in standstill (#26585) * add resume required alert * possibly need this (pressing resume with brake held does nothing) * better text (has both meanings) * try this * Revert "try this" This reverts commit 5b2991929b62f5bcd9bfa0767d709b5fe83b094b. * no alert with interceptor * check long --- selfdrive/car/toyota/interface.py | 23 +++++++++++++---------- selfdrive/controls/lib/events.py | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 3f4edb36d2..0139c692c1 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -247,16 +247,19 @@ class CarInterface(CarInterfaceBase): # events events = self.create_common_events(ret) - if self.CS.low_speed_lockout and self.CP.openpilotLongitudinalControl: - events.add(EventName.lowSpeedLockout) - if ret.vEgo < self.CP.minEnableSpeed and self.CP.openpilotLongitudinalControl: - events.add(EventName.belowEngageSpeed) - if c.actuators.accel > 0.3: - # some margin on the actuator to not false trigger cancellation while stopping - events.add(EventName.speedTooLow) - if ret.vEgo < 0.001: - # while in standstill, send a user alert - events.add(EventName.manualRestart) + if self.CP.openpilotLongitudinalControl: + if ret.cruiseState.standstill and not ret.brakePressed and not self.CP.enableGasInterceptor: + events.add(EventName.resumeRequired) + if self.CS.low_speed_lockout: + events.add(EventName.lowSpeedLockout) + if ret.vEgo < self.CP.minEnableSpeed: + events.add(EventName.belowEngageSpeed) + if c.actuators.accel > 0.3: + # some margin on the actuator to not false trigger cancellation while stopping + events.add(EventName.speedTooLow) + if ret.vEgo < 0.001: + # while in standstill, send a user alert + events.add(EventName.manualRestart) ret.events = events.to_msg() diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 1ed2ffa865..ad10b8f0bd 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -501,7 +501,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { EventName.resumeRequired: { ET.WARNING: Alert( "STOPPED", - "Press Resume to Go", + "Press Resume to Exit Standstill", AlertStatus.userPrompt, AlertSize.mid, Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), }, From 38cd262de3436a3a239210c6e8c71933e6a79871 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 28 Nov 2022 17:55:09 -0500 Subject: [PATCH 101/185] VW MQB: Add FW for 2022 Volkswagen Arteon (#26603) --- selfdrive/car/volkswagen/values.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index f24448adbc..5423f09658 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -280,12 +280,14 @@ FW_VERSIONS = { CAR.ARTEON_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x873G0906259F \xf1\x890004', + b'\xf1\x873G0906259N \xf1\x890004', b'\xf1\x873G0906259P \xf1\x890001', b'\xf1\x875NA907115H \xf1\x890002', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158L \xf1\x893611', b'\xf1\x870GC300011L \xf1\x891401', + b'\xf1\x870GC300014M \xf1\x892802', b'\xf1\x870GC300040P \xf1\x891401', ], (Ecu.srs, 0x715, None): [ @@ -297,8 +299,10 @@ FW_VERSIONS = { b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571B00817A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567B0020800', + b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002MB4092M7N', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x875Q0907572J \xf1\x890654', ], From 82158c8378a34ab0d82566d1149e1769e877c4ac Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 28 Nov 2022 17:55:57 -0500 Subject: [PATCH 102/185] VW PQ: Fix cruise fault detection, cleanup (#26588) --- selfdrive/car/volkswagen/carstate.py | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 7d0a3d4dca..c82dc08007 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -222,10 +222,11 @@ class CarState(CarStateBase): # Update ACC radar status. self.acc_type = 0 # TODO: this is ACC "basic" with nonzero min speed, support FtS (1) later ret.cruiseState.available = bool(pt_cp.vl["Motor_5"]["GRA_Hauptschalter"]) - ret.cruiseState.enabled = bool(pt_cp.vl["Motor_2"]["GRA_Status"]) + ret.cruiseState.enabled = pt_cp.vl["Motor_2"]["GRA_Status"] in (1, 2) if self.CP.pcmCruise: ret.accFaulted = ext_cp.vl["ACC_GRA_Anziege"]["ACA_StaACC"] in (6, 7) - # TODO: update opendbc with PQ TSK state for OP long accFaulted + else: + ret.accFaulted = pt_cp.vl["Motor_2"]["GRA_Status"] == 3 # Update ACC setpoint. When the setpoint reads as 255, the driver has not # yet established an ACC setpoint, so treat it as zero. @@ -485,15 +486,15 @@ class MqbExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ ("ACC_Wunschgeschw_02", "ACC_02"), # ACC set speed - ("ACC_Typ", "ACC_06"), # Basic vs F2S vs SNG + ("ACC_Typ", "ACC_06"), # Basic vs FtS vs SnG ("AWV2_Freigabe", "ACC_10"), # FCW brake jerk release ("ANB_Teilbremsung_Freigabe", "ACC_10"), # AEB partial braking release ("ANB_Zielbremsung_Freigabe", "ACC_10"), # AEB target braking release ] fwd_radar_checks = [ - ("ACC_06", 50), # From J428 ACC radar control module - ("ACC_10", 50), # From J428 ACC radar control module - ("ACC_02", 17), # From J428 ACC radar control module + ("ACC_06", 50), # From J428 ACC radar control module + ("ACC_10", 50), # From J428 ACC radar control module + ("ACC_02", 17), # From J428 ACC radar control module ] bsm_radar_signals = [ ("SWA_Infostufe_SWA_li", "SWA_01"), # Blind spot object info, left @@ -502,24 +503,24 @@ class MqbExtraSignals: ("SWA_Warnung_SWA_re", "SWA_01"), # Blind spot object warning, right ] bsm_radar_checks = [ - ("SWA_01", 20), # From J1086 Lane Change Assist + ("SWA_01", 20), # From J1086 Lane Change Assist ] class PqExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ - ("ACA_StaACC", "ACC_GRA_Anziege", 0), # ACC drivetrain coordinator status - ("ACA_V_Wunsch", "ACC_GRA_Anziege", 0), # ACC set speed + ("ACA_StaACC", "ACC_GRA_Anziege"), # ACC drivetrain coordinator status + ("ACA_V_Wunsch", "ACC_GRA_Anziege"), # ACC set speed ] fwd_radar_checks = [ - ("ACC_GRA_Anziege", 25), # From J428 ACC radar control module + ("ACC_GRA_Anziege", 25), # From J428 ACC radar control module ] bsm_radar_signals = [ - ("SWA_Infostufe_SWA_li", "SWA_1", 0), # Blind spot object info, left - ("SWA_Warnung_SWA_li", "SWA_1", 0), # Blind spot object warning, left - ("SWA_Infostufe_SWA_re", "SWA_1", 0), # Blind spot object info, right - ("SWA_Warnung_SWA_re", "SWA_1", 0), # Blind spot object warning, right + ("SWA_Infostufe_SWA_li", "SWA_1"), # Blind spot object info, left + ("SWA_Warnung_SWA_li", "SWA_1"), # Blind spot object warning, left + ("SWA_Infostufe_SWA_re", "SWA_1"), # Blind spot object info, right + ("SWA_Warnung_SWA_re", "SWA_1"), # Blind spot object warning, right ] bsm_radar_checks = [ - ("SWA_1", 20), # From J1086 Lane Change Assist + ("SWA_1", 20), # From J1086 Lane Change Assist ] From 08b49e5671c04aaa17d8b56e2351d1bd14195dae Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 28 Nov 2022 15:00:37 -0800 Subject: [PATCH 103/185] IsoTpParallelQuery: log errors in qlogs (#26609) log these in qlogs --- selfdrive/car/isotp_parallel_query.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 4b4bdcc0ca..d9c658a14c 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -137,17 +137,17 @@ class IsoTpParallelQuery: else: response_timeouts[tx_addr] = 0 request_done[tx_addr] = True - cloudlog.warning(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}") + cloudlog.error(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}") cur_time = time.monotonic() if cur_time - max(response_timeouts.values()) > 0: for tx_addr in msgs: if request_counter[tx_addr] > 0 and not request_done[tx_addr]: - cloudlog.warning(f"iso-tp query timeout after receiving response: {tx_addr}") + cloudlog.error(f"iso-tp query timeout after receiving response: {tx_addr}") break if cur_time - start_time > total_timeout: - cloudlog.warning("iso-tp query timeout while receiving data") + cloudlog.error("iso-tp query timeout while receiving data") break return results From 909a884dc5d2aaaff270e86d2707b51037c87bc4 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 29 Nov 2022 07:43:27 +0800 Subject: [PATCH 104/185] Cabana: fix segfault in dragging zoom (#26598) fix segfault --- tools/cabana/chartswidget.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 6e1f2e110c..a3faab0a20 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -398,8 +398,8 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { rubber->hide(); QRectF rect = rubber->geometry().normalized(); - double min = chart()->mapToValue(rect.topLeft()).x(); - double max = chart()->mapToValue(rect.bottomRight()).x(); + double min = std::floor(chart()->mapToValue(rect.topLeft()).x() * 10.0) / 10.0; + double max = std::floor(chart()->mapToValue(rect.bottomRight()).x() * 10.0) / 10.0; if (rubber->width() <= 0) { // no rubber dragged, seek to mouse position can->seekTo(min); From 12ecc7b4bc42f70d73729367c32d200cca5afcc3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 28 Nov 2022 16:01:54 -0800 Subject: [PATCH 105/185] Hyundai: remove custom accel limit function for default values (#26611) * Update interface.py * Update interface.py --- selfdrive/car/hyundai/interface.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 8738aabd17..3ecf5289ff 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -2,7 +2,7 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase @@ -17,10 +17,6 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu class CarInterface(CarInterfaceBase): - @staticmethod - def get_pid_accel_limits(CP, current_speed, cruise_speed): - return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX - @staticmethod def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value ret = CarInterfaceBase.get_std_params(candidate, fingerprint) From a48ec655ac4983145bc93c712ecabac75b886e11 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 29 Nov 2022 01:13:54 +0100 Subject: [PATCH 106/185] CI: gps test hackrf support (#26364) * first ignore * init gps test * make LimeGPS git clone * revert ignore * . * remove prebuilt bins * gps test v1 * add static signal gen script * update readme * remove LD_PRELOAD by using rpath, update values after testing * . * remove LD_PRELOAD * . * fix README * . * . * . * gps test v1 * cleanUp * init * update fuzzy tests * . * finalize qcom gps tests * . * . * . * add downloader * finalize unit tests * . * . * inc limeGPS startup time * tmp * loosen init time * . * . * . * add ublox warmstart test * . * improve location tests * . * . * . * . * . * . * . * . * last cleanUp * . * . * add hackrf support * hackrf support * . * . * . * . * . * . Co-authored-by: Kurt Nistelberger --- tools/gpstest/.gitignore | 4 +- tools/gpstest/patches/hackrf.patch | 44 +++++++++++ tools/gpstest/setup_hackrf.sh | 21 +++++ tools/gpstest/simulate_gps_signal.py | 110 +++++++++++++++++++-------- 4 files changed, 145 insertions(+), 34 deletions(-) create mode 100644 tools/gpstest/patches/hackrf.patch create mode 100755 tools/gpstest/setup_hackrf.sh diff --git a/tools/gpstest/.gitignore b/tools/gpstest/.gitignore index f11597286e..992088ef34 100644 --- a/tools/gpstest/.gitignore +++ b/tools/gpstest/.gitignore @@ -1,2 +1,4 @@ LimeGPS/ -LimeSuite/ \ No newline at end of file +LimeSuite/ +hackrf/ +gps-sdr-sim/ diff --git a/tools/gpstest/patches/hackrf.patch b/tools/gpstest/patches/hackrf.patch new file mode 100644 index 0000000000..afc9ac437b --- /dev/null +++ b/tools/gpstest/patches/hackrf.patch @@ -0,0 +1,44 @@ +diff --git a/host/hackrf-tools/src/CMakeLists.txt b/host/hackrf-tools/src/CMakeLists.txt +index 7115151c..a51388ba 100644 +--- a/host/hackrf-tools/src/CMakeLists.txt ++++ b/host/hackrf-tools/src/CMakeLists.txt +@@ -23,20 +23,20 @@ + + set(INSTALL_DEFAULT_BINDIR "bin" CACHE STRING "Appended to CMAKE_INSTALL_PREFIX") + +-find_package(FFTW REQUIRED) +-include_directories(${FFTW_INCLUDES}) +-get_filename_component(FFTW_LIBRARY_DIRS ${FFTW_LIBRARIES} DIRECTORY) +-link_directories(${FFTW_LIBRARY_DIRS}) ++#find_package(FFTW REQUIRED) ++#include_directories(${FFTW_INCLUDES}) ++#get_filename_component(FFTW_LIBRARY_DIRS ${FFTW_LIBRARIES} DIRECTORY) ++#link_directories(${FFTW_LIBRARY_DIRS}) + + SET(TOOLS + hackrf_transfer +- hackrf_spiflash +- hackrf_cpldjtag ++ #hackrf_spiflash ++ #hackrf_cpldjtag + hackrf_info +- hackrf_debug +- hackrf_clock +- hackrf_sweep +- hackrf_operacake ++ #hackrf_debug ++ #hackrf_clock ++ #hackrf_sweep ++ #hackrf_operacake + ) + + if(MSVC) +@@ -45,7 +45,7 @@ if(MSVC) + ) + LIST(APPEND TOOLS_LINK_LIBS ${FFTW_LIBRARIES}) + else() +- LIST(APPEND TOOLS_LINK_LIBS m fftw3f) ++ LIST(APPEND TOOLS_LINK_LIBS m)# fftw3f) + endif() + + if(NOT libhackrf_SOURCE_DIR) diff --git a/tools/gpstest/setup_hackrf.sh b/tools/gpstest/setup_hackrf.sh new file mode 100755 index 0000000000..e504ec9447 --- /dev/null +++ b/tools/gpstest/setup_hackrf.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if [ ! -d gps-sdr-sim ]; then + git clone https://github.com/osqzss/gps-sdr-sim.git + cd gps-sdr-sim + make + cd .. +fi + +if [ ! -d hackrf ]; then + git clone https://github.com/greatscottgadgets/hackrf.git + cd hackrf/host + git apply ../../patches/hackrf.patch + cmake . + make +fi + diff --git a/tools/gpstest/simulate_gps_signal.py b/tools/gpstest/simulate_gps_signal.py index f1e5ad2028..a6aca1c404 100755 --- a/tools/gpstest/simulate_gps_signal.py +++ b/tools/gpstest/simulate_gps_signal.py @@ -36,38 +36,7 @@ def get_random_coords(lat, lon) -> Tuple[int, int]: # jump around the world return get_coords(lat, lon, 20, 20, 10, 20) -def check_availability() -> bool: - cmd = ["LimeSuite/builddir/LimeUtil/LimeUtil", "--find"] - output = sp.check_output(cmd) - - if output.strip() == b"": - return False - - print(f"Device: {output.strip().decode('utf-8')}") - return True - -def main(lat, lon, jump_sim, contin_sim): - if not os.path.exists('LimeGPS'): - print("LimeGPS not found run 'setup.sh' first") - return - - if not os.path.exists('LimeSuite'): - print("LimeSuite not found run 'setup.sh' first") - return - - if not check_availability(): - print("No limeSDR device found!") - return - - rinex_file = download_rinex() - - if lat == 0 and lon == 0: - lat, lon = get_random_coords(47.2020, 15.7403) - - timeout = None - if jump_sim: - timeout = 30 - +def run_limeSDR_loop(lat, lon, contin_sim, rinex_file, timeout): while True: try: print(f"starting LimeGPS, Location: {lat},{lon}") @@ -86,17 +55,92 @@ def main(lat, lon, jump_sim, contin_sim): print(f"LimeGPS crashed: {str(e)}") print(f"stderr:\n{e.stderr.decode('utf-8')}")# pylint:disable=no-member + return if contin_sim: lat, lon = get_continuous_coords(lat, lon) else: lat, lon = get_random_coords(lat, lon) +def run_hackRF_loop(lat, lon, rinex_file, timeout): + + if timeout is not None: + print("no jump mode for hackrf!") + return + + try: + print(f"starting gps-sdr-sim, Location: {lat},{lon}") + # create 30second file and replay with hackrf endless + cmd = ["gps-sdr-sim/gps-sdr-sim", "-e", rinex_file, "-l", f"{lat},{lon},100", "-d", "30"] + sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) + # created in current working directory + except Exception: + print("Failed to generate gpssim.bin") + + try: + print("starting hackrf_transfer") + # create 30second file and replay with hackrf endless + cmd = ["hackrf/host/hackrf-tools/src/hackrf_transfer", "-t", "gpssim.bin", + "-f", "1575420000", "-s", "2600000", "-a", "1", "-R"] + sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) + except KeyboardInterrupt: + print("stopping hackrf_transfer") + return + except Exception as e: + print(f"hackrf_transfer crashed:{str(e)}") + + +def main(lat, lon, jump_sim, contin_sim, hackrf_mode): + + if hackrf_mode: + if not os.path.exists('hackrf'): + print("hackrf not found run 'setup_hackrf.sh' first") + return + + if not os.path.exists('gps-sdr-sim'): + print("gps-sdr-sim not found run 'setup_hackrf.sh' first") + return + + output = sp.check_output(["hackrf/host/hackrf-tools/src/hackrf_info"]) + if output.strip() == b"" or b"No HackRF boards found." in output: + print("No HackRF boards found!") + return + + else: + if not os.path.exists('LimeGPS'): + print("LimeGPS not found run 'setup.sh' first") + return + + if not os.path.exists('LimeSuite'): + print("LimeSuite not found run 'setup.sh' first") + return + + output = sp.check_output(["LimeSuite/builddir/LimeUtil/LimeUtil", "--find"]) + if output.strip() == b"": + print("No LimeSDR device found!") + return + print(f"Device: {output.strip().decode('utf-8')}") + + if lat == 0 and lon == 0: + lat, lon = get_random_coords(47.2020, 15.7403) + + rinex_file = download_rinex() + + timeout = None + if jump_sim: + timeout = 30 + + if not hackrf_mode: + run_limeSDR_loop(lat, lon, contin_sim, rinex_file, timeout) + else: + run_hackRF_loop(lat, lon, rinex_file, timeout) + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Simulate static [or random jumping] GPS signal.") parser.add_argument("lat", type=float, nargs='?', default=0) parser.add_argument("lon", type=float, nargs='?', default=0) parser.add_argument("--jump", action="store_true", help="signal that jumps around the world") parser.add_argument("--contin", action="store_true", help="continuously/slowly moving around the world") + parser.add_argument("--hackrf", action="store_true", help="hackrf mode (DEFAULT: LimeSDR)") args = parser.parse_args() - main(args.lat, args.lon, args.jump, args.contin) + main(args.lat, args.lon, args.jump, args.contin, args.hackrf) From 7b1122e831f0219c2f4c660cca6f45c1e8a3162a Mon Sep 17 00:00:00 2001 From: Scott Leibrand Date: Tue, 29 Nov 2022 00:38:18 -0800 Subject: [PATCH 107/185] One-word grammar fix to docs/INTEGRATION.md (#26621) Grammar fix --- docs/INTEGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md index 97b72e39d3..ba6291c1e3 100644 --- a/docs/INTEGRATION.md +++ b/docs/INTEGRATION.md @@ -8,4 +8,4 @@ Additionally, on specific supported cars (see ACC column in [supported cars](CAR * Stock ACC is replaced by openpilot ACC. * openpilot FCW operates in addition to stock FCW. -openpilot should preserve all other vehicle's stock features, including, but are not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning. +openpilot should preserve all other vehicle's stock features, including, but not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning. From b18dfef8a6fdd1b38be316193f704b380e8f0dd2 Mon Sep 17 00:00:00 2001 From: Erich Moraga <33645296+ErichMoraga@users.noreply.github.com> Date: Tue, 29 Nov 2022 03:03:02 -0600 Subject: [PATCH 108/185] Add missing RAV4_TSS2 ABS f/w (#26617) `@IDJ#0879` 2019 Toyota RAV4 (ICE) DongleID/route e7cd4ae3bee72b52|2022-11-29--14-33-40 --- selfdrive/car/toyota/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 6d6c03df71..4f2ab478c7 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1326,6 +1326,7 @@ FW_VERSIONS = { b'\x01F15260R302\x00\x00\x00\x00\x00\x00', b'\x01F152642551\x00\x00\x00\x00\x00\x00', b'\x01F152642561\x00\x00\x00\x00\x00\x00', + b'\x01F152642601\x00\x00\x00\x00\x00\x00', b'\x01F152642700\x00\x00\x00\x00\x00\x00', b'\x01F152642701\x00\x00\x00\x00\x00\x00', b'\x01F152642710\x00\x00\x00\x00\x00\x00', From 67fb3441463976d016a6ca0499abdc8691238f2d Mon Sep 17 00:00:00 2001 From: James <91348155+FrogAi@users.noreply.github.com> Date: Tue, 29 Nov 2022 02:05:38 -0700 Subject: [PATCH 109/185] Fixed typo in CARLA README.md (#26615) --- tools/sim/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sim/README.md b/tools/sim/README.md index 40603f3f71..69a89aefab 100644 --- a/tools/sim/README.md +++ b/tools/sim/README.md @@ -6,7 +6,7 @@ openpilot implements a [bridge](bridge.py) that allows it to run in the [CARLA s ## System Requirements openpilot doesn't have any extreme hardware requirements, however CARLA requires an NVIDIA graphics card and is very resource-intensive and may not run smoothly on your system. -For this case, we have a the simulator in low quality by default. +For this case, we have the simulator in low quality by default. You can also check out the [CARLA python documentation](https://carla.readthedocs.io/en/latest/python_api/) to find more parameters to tune that might increase performance on your system. From b2fd699c21656d6cc7959c3ecd900b5a08456950 Mon Sep 17 00:00:00 2001 From: uppercaseVar <33802760+uppercaseVar@users.noreply.github.com> Date: Tue, 29 Nov 2022 01:08:58 -0800 Subject: [PATCH 110/185] Add ECU fw for 2016 Golf GTI (#26591) Added ECU for 2016 Golf GTI --- selfdrive/car/volkswagen/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 5423f09658..c96f6decbf 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -377,6 +377,7 @@ FW_VERSIONS = { b'\xf1\x870EA906016Q \xf1\x895993', b'\xf1\x870EA906016S \xf1\x897207', b'\xf1\x875G0906259 \xf1\x890007', + b'\xf1\x875G0906259D \xf1\x890002', b'\xf1\x875G0906259J \xf1\x890002', b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259N \xf1\x890003', @@ -412,6 +413,7 @@ FW_VERSIONS = { b'\xf1\x870D9300012 \xf1\x895045', b'\xf1\x870D9300014M \xf1\x895004', b'\xf1\x870D9300014Q \xf1\x895006', + b'\xf1\x870D9300020J \xf1\x894902', b'\xf1\x870D9300020Q \xf1\x895201', b'\xf1\x870D9300020S \xf1\x895201', b'\xf1\x870D9300040A \xf1\x893613', @@ -459,6 +461,7 @@ FW_VERSIONS = { b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561A01612A0', b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566A0J612A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A00514A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', From f64e69eb11e1b9efc5fb3f9b893d1e42a9809d0f Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 29 Nov 2022 01:13:43 -0800 Subject: [PATCH 111/185] Hyundai: add more Kia K5 FW (#26590) * add some values * consistent , * Update selfdrive/car/hyundai/values.py * Update selfdrive/car/hyundai/values.py Co-authored-by: Shane Smiskol --- selfdrive/car/hyundai/values.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index ecba7b7494..16ff68f08a 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1021,11 +1021,13 @@ FW_VERSIONS = { b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', + b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', + b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', @@ -1036,6 +1038,7 @@ FW_VERSIONS = { b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200', b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', + b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', ], (Ecu.engine, 0x7E0, None): [ b'\xf1\x87391212MKT0', @@ -1048,6 +1051,7 @@ FW_VERSIONS = { b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', ], }, CAR.KONA_EV: { From b4da592a3ca9bfda7766ecb5021a8ce17dfd6abe Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 29 Nov 2022 01:54:59 -0800 Subject: [PATCH 112/185] mock: remove steering angle (#26614) * fix yaw rate in mock interface * clean that up * revert * clean up --- selfdrive/car/mock/interface.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index a3194cd79e..2be1bcb4a3 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -1,31 +1,22 @@ #!/usr/bin/env python3 -import math from cereal import car -from common.conversions import Conversions as CV from system.swaglog import cloudlog import cereal.messaging as messaging from selfdrive.car import gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase -# mocked car interface to work with chffrplus -TS = 0.01 # 100Hz -YAW_FR = 0.2 # ~0.8s time constant on yaw rate filter -# low pass gain -LPG = 2 * math.pi * YAW_FR * TS / (1 + 2 * math.pi * YAW_FR * TS) - +# mocked car interface to work with chffrplus class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) cloudlog.debug("Using Mock Car Interface") - self.sm = messaging.SubMaster(['gyroscope', 'gpsLocation', 'gpsLocationExternal']) + self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) self.speed = 0. self.prev_speed = 0. - self.yaw_rate = 0. - self.yaw_rate_meas = 0. @staticmethod def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): @@ -45,11 +36,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): self.sm.update(0) - - # get basic data from phone and gps since CAN isn't connected - if self.sm.updated['gyroscope']: - self.yaw_rate_meas = -self.sm['gyroscope'].gyroUncalibrated.v[0] - gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' if self.sm.updated[gps_sock]: self.prev_speed = self.speed @@ -61,10 +47,9 @@ class CarInterface(CarInterfaceBase): # speeds ret.vEgo = self.speed ret.vEgoRaw = self.speed - a = self.speed - self.prev_speed - ret.aEgo = a - ret.brakePressed = a < -0.5 + ret.aEgo = self.speed - self.prev_speed + ret.brakePressed = ret.aEgo < -0.5 ret.standstill = self.speed < 0.01 ret.wheelSpeeds.fl = self.speed @@ -72,10 +57,6 @@ class CarInterface(CarInterfaceBase): ret.wheelSpeeds.rl = self.speed ret.wheelSpeeds.rr = self.speed - self.yawRate = LPG * self.yaw_rate_meas + (1. - LPG) * self.yaw_rate - curvature = self.yaw_rate / max(self.speed, 1.) - ret.steeringAngleDeg = curvature * self.CP.steerRatio * self.CP.wheelbase * CV.RAD_TO_DEG - return ret def apply(self, c): From 0706b03e8cf41f7d51c9a42f01bda2084bfe3be6 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 29 Nov 2022 10:47:13 -0800 Subject: [PATCH 113/185] [controlsd] Toyota Prius 2017 (#26455) * increase lag on prius * better offline values computation * split prius ecu more, set deadzone to 0 * add deadzone back * replace deadzone with slack * reduce deadzone to 0.2 * update refs --- selfdrive/car/interfaces.py | 4 ++-- selfdrive/car/torque_data/params.yaml | 3 ++- selfdrive/car/toyota/interface.py | 4 ++-- selfdrive/controls/lib/drive_helpers.py | 6 ++++++ selfdrive/test/process_replay/ref_commit | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 8e8872a539..820e429cc4 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -11,7 +11,7 @@ from common.kalman.simple_kalman import KF1D from common.numpy_fast import interp from common.realtime import DT_CTRL from selfdrive.car import apply_hysteresis, gen_empty_fingerprint -from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_deadzone +from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_slack from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel @@ -109,7 +109,7 @@ class CarInterfaceBase(ABC): def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation): # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) friction_interp = interp( - apply_deadzone(lateral_accel_error, lateral_accel_deadzone), + apply_slack(lateral_accel_error, lateral_accel_deadzone), [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-torque_params.friction, torque_params.friction] ) diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index a9023b4edc..b4dbaf8564 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -77,7 +77,8 @@ TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054 TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600] TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993] TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] -TOYOTA PRIUS 2017: [1.746445, 1.5023147650693636, 0.151515] +TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515] +TOYOTA PRIUS 2017 x060: [1.746445, 1.5023147650693636, 0.17] TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968] TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] TOYOTA RAV4 2019: [2.331293, 2.0993589721530252, 0.129822] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 0139c692c1..7ee5ac0d0e 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -42,7 +42,8 @@ class CarInterface(CarInterfaceBase): # Only give steer angle deadzone to for bad angle sensor prius for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': - steering_angle_deadzone_deg = 1.0 + steering_angle_deadzone_deg = 0.2 + ret.steerActuatorDelay = 0.25 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) elif candidate == CAR.PRIUS_V: @@ -51,7 +52,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 17.4 tire_stiffness_factor = 0.5533 ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) elif candidate in (CAR.RAV4, CAR.RAV4H): stop_and_go = True if (candidate in CAR.RAV4H) else False diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index f0dc2e9467..acef4bee8e 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -149,6 +149,12 @@ def apply_deadzone(error, deadzone): return error +def apply_slack(error, deadzone): + if (error > - deadzone) and (error < deadzone): + error = 0. + return error + + def rate_limit(new_value, last_value, dw_step, up_step): return clip(new_value, last_value + dw_step, last_value + up_step) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index d4520d2935..71da0dff44 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -08ca448a6dcfc1edc4f9fedb2ea94ad6b2b69aa2 \ No newline at end of file +ddfaab44ae64a0d064b847e81080993b51b6b423 \ No newline at end of file From 44e9ea23bd6900d9d43216855bbc68704e4358ba Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Tue, 29 Nov 2022 14:15:16 -0500 Subject: [PATCH 114/185] VW PQ: Follow-to-Stop support (#26616) --- selfdrive/car/volkswagen/carstate.py | 4 +++- selfdrive/car/volkswagen/interface.py | 1 - selfdrive/car/volkswagen/pqcan.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index c82dc08007..263edbfc9d 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -220,7 +220,7 @@ class CarState(CarStateBase): ret.stockAeb = False # Update ACC radar status. - self.acc_type = 0 # TODO: this is ACC "basic" with nonzero min speed, support FtS (1) later + self.acc_type = ext_cp.vl["ACC_System"]["ACS_Typ_ACC"] ret.cruiseState.available = bool(pt_cp.vl["Motor_5"]["GRA_Hauptschalter"]) ret.cruiseState.enabled = pt_cp.vl["Motor_2"]["GRA_Status"] in (1, 2) if self.CP.pcmCruise: @@ -509,10 +509,12 @@ class MqbExtraSignals: class PqExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ + ("ACS_Typ_ACC", "ACC_System"), # Basic vs FtS (no SnG support on PQ) ("ACA_StaACC", "ACC_GRA_Anziege"), # ACC drivetrain coordinator status ("ACA_V_Wunsch", "ACC_GRA_Anziege"), # ACC set speed ] fwd_radar_checks = [ + ("ACC_System", 50), # From J428 ACC radar control module ("ACC_GRA_Anziege", 25), # From J428 ACC radar control module ] bsm_radar_signals = [ diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 816e7fcf34..e0bb917960 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -137,7 +137,6 @@ class CarInterface(CarInterfaceBase): elif candidate == CAR.SHARAN_MK2: ret.mass = 1639 + STD_CARGO_KG ret.wheelbase = 2.92 - ret.minEnableSpeed = 30 * CV.KPH_TO_MS ret.minSteerSpeed = 50 * CV.KPH_TO_MS ret.steerActuatorDelay = 0.2 diff --git a/selfdrive/car/volkswagen/pqcan.py b/selfdrive/car/volkswagen/pqcan.py index 0bcbf6abb3..6beb90c092 100644 --- a/selfdrive/car/volkswagen/pqcan.py +++ b/selfdrive/car/volkswagen/pqcan.py @@ -66,6 +66,7 @@ def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, "ACS_Sta_ADR": acc_control, "ACS_StSt_Info": acc_control != 1, "ACS_Typ_ACC": acc_type, + "ACS_Anhaltewunsch": acc_type == 1 and stopping, "ACS_Sollbeschl": accel if acc_control == 1 else 3.01, "ACS_zul_Regelabw": 0.2 if acc_control == 1 else 1.27, "ACS_max_AendGrad": 3.0 if acc_control == 1 else 5.08, From f9bb024a464f8714a1edc7190985fcb612415eec Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 30 Nov 2022 03:42:56 +0800 Subject: [PATCH 115/185] Cabana: execute generate_dbc_json.py after building (#26620) * use env.command to generete json after build * Update tools/cabana/SConscript Co-authored-by: Shane Smiskol --- tools/cabana/SConscript | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 3ff4862800..52fe9e346d 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -17,10 +17,13 @@ cabana_env = qt_env.Clone() prev_moc_path = cabana_env['QT_MOCHPREFIX'] cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' -cabana_env.Execute('./generate_dbc_json.py --out car_fingerprint_to_dbc.json') cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', 'canmessages.cc', 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('test'): cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) + +def generate_dbc_json(target, source, env): + env.Execute('tools/cabana/generate_dbc_json.py --out tools/cabana/car_fingerprint_to_dbc.json') +cabana_env.Command('generate_dbc_json', [], generate_dbc_json) From 860f441e2f122173c5034656aeed5706fd41e38f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 29 Nov 2022 11:49:13 -0800 Subject: [PATCH 116/185] Car interface: set common params after port (#26613) * remove pylint exception, _get_params takes no defaults * clean up * mock uses it too * unused * unused * fix that * bump * Update selfdrive/car/interfaces.py --- selfdrive/car/body/interface.py | 9 ++------- selfdrive/car/chrysler/interface.py | 10 ++-------- selfdrive/car/ford/interface.py | 14 +++----------- selfdrive/car/gm/interface.py | 9 ++------- selfdrive/car/honda/interface.py | 9 ++------- selfdrive/car/hyundai/interface.py | 10 ++-------- selfdrive/car/interfaces.py | 26 ++++++++++++++++++++++---- selfdrive/car/mazda/interface.py | 10 ++-------- selfdrive/car/mock/interface.py | 6 ++---- selfdrive/car/nissan/interface.py | 10 ++-------- selfdrive/car/subaru/interface.py | 10 ++-------- selfdrive/car/tesla/interface.py | 6 ++---- selfdrive/car/toyota/interface.py | 10 ++-------- selfdrive/car/volkswagen/interface.py | 11 +++-------- 14 files changed, 50 insertions(+), 100 deletions(-) diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py index ae7ab89aab..638134be7f 100644 --- a/selfdrive/car/body/interface.py +++ b/selfdrive/car/body/interface.py @@ -2,16 +2,13 @@ import math from cereal import car from common.realtime import DT_CTRL -from selfdrive.car import scale_rot_inertia, scale_tire_stiffness, get_safety_config +from selfdrive.car import scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.body.values import SPEED_FROM_RPM class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=None, car_fw=None, experimental_long=False): - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.notCar = True ret.carName = "body" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.body)] @@ -31,8 +28,6 @@ class CarInterface(CarInterfaceBase): ret.openpilotLongitudinalControl = True ret.steerControlType = car.CarParams.SteerControlType.angle - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) return ret diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 245e10650c..9b5e5a5ce5 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,21 +1,18 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.chrysler.values import CAR, DBC, RAM_HD, RAM_DT from selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "chrysler" - ret.dashcamOnly = candidate in RAM_HD ret.radarOffCan = DBC[candidate]['radar'] is None - ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 @@ -76,9 +73,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.44 - # starting with reasonable value for civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 4943db076f..913c3c926d 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter from selfdrive.car.interfaces import CarInterfaceBase @@ -10,12 +10,7 @@ CarParams = car.CarParams class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - if car_fw is None: - car_fw = [] - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "ford" ret.dashcamOnly = True ret.safetyConfigs = [get_safety_config(CarParams.SafetyModel.ford)] @@ -24,7 +19,6 @@ class CarInterface(CarInterfaceBase): ret.steerControlType = CarParams.SteerControlType.angle ret.steerActuatorDelay = 0.4 ret.steerLimitTimer = 1.0 - tire_stiffness_factor = 1.0 if candidate == CAR.ESCAPE_MK4: ret.wheelbase = 2.71 @@ -60,10 +54,8 @@ class CarInterface(CarInterfaceBase): ret.minSteerSpeed = 0. ret.autoResumeSng = ret.minEnableSpeed == -1. - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.44 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, - tire_stiffness_factor=tire_stiffness_factor) + ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) return ret def _update(self, c): diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index eb5ab7329a..7d38c60900 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -4,7 +4,7 @@ from math import fabs from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR from selfdrive.car.interfaces import CarInterfaceBase @@ -44,8 +44,7 @@ class CarInterface(CarInterfaceBase): return CarInterfaceBase.get_steer_feedforward_default @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "gm" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] ret.autoResumeSng = False @@ -195,10 +194,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index e397f02838..990238ae5d 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -4,7 +4,7 @@ from panda import Panda from common.conversions import Conversions as CV from common.numpy_fast import interp from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS -from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu @@ -29,8 +29,7 @@ class CarInterface(CarInterfaceBase): return CarControllerParams.NIDEC_ACCEL_MIN, interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS) @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "honda" if candidate in HONDA_BOSCH: @@ -291,10 +290,6 @@ class CarInterface(CarInterfaceBase): stop_and_go = candidate in (HONDA_BOSCH | {CAR.CIVIC}) or ret.enableGasInterceptor ret.minEnableSpeed = -1. if stop_and_go else 25.5 * CV.MPH_TO_MS - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 3ecf5289ff..c1fe8be4c2 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -4,7 +4,7 @@ from panda import Panda from common.conversions import Conversions as CV from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR -from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu @@ -18,9 +18,7 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "hyundai" ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None @@ -267,10 +265,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.4 - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 820e429cc4..e03f1cfada 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -10,7 +10,7 @@ from common.conversions import Conversions as CV from common.kalman.simple_kalman import KF1D from common.numpy_fast import interp from common.realtime import DT_CTRL -from selfdrive.car import apply_hysteresis, gen_empty_fingerprint +from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_slack from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel @@ -87,10 +87,28 @@ class CarInterfaceBase(ABC): def get_pid_accel_limits(CP, current_speed, cruise_speed): return ACCEL_MIN, ACCEL_MAX + @classmethod + def get_params(cls, candidate: str, fingerprint: Optional[Dict[int, Dict[int, int]]] = None, car_fw: Optional[List[car.CarParams.CarFw]] = None, experimental_long: bool = False): + if fingerprint is None: + fingerprint = gen_empty_fingerprint() + + if car_fw is None: + car_fw = list() + + ret = CarInterfaceBase.get_std_params(candidate) + ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long) + + # Set common params using fields set by the car interface + # TODO: get actual value, for now starting with reasonable value for + # civic and scaling by mass and wheelbase + ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) + + return ret + @staticmethod @abstractmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - pass + def _get_params(ret: car.CarParams, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool): + raise NotImplementedError @staticmethod def init(CP, logcan, sendcan): @@ -121,7 +139,7 @@ class CarInterfaceBase(ABC): # returns a set of default params to avoid repetition in car specific params @staticmethod - def get_std_params(candidate, fingerprint): + def get_std_params(candidate): ret = car.CarParams.new_message() ret.carFingerprint = candidate diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 7c42431e33..fdd2439ff9 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -2,7 +2,7 @@ from cereal import car from common.conversions import Conversions as CV from selfdrive.car.mazda.values import CAR, LKAS_LIMITS -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -11,9 +11,7 @@ EventName = car.CarEvent.EventName class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "mazda" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)] ret.radarOffCan = True @@ -48,10 +46,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.41 - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index 2be1bcb4a3..3ac487dbb7 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -2,7 +2,7 @@ from cereal import car from system.swaglog import cloudlog import cereal.messaging as messaging -from selfdrive.car import gen_empty_fingerprint, get_safety_config +from selfdrive.car import get_safety_config from selfdrive.car.interfaces import CarInterfaceBase @@ -19,12 +19,10 @@ class CarInterface(CarInterfaceBase): self.prev_speed = 0. @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "mock" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput)] ret.mass = 1700. - ret.rotationalInertia = 2500. ret.wheelbase = 2.70 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 13. # reasonable diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index e095ceb461..194d7f06ae 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.nissan.values import CAR @@ -8,9 +8,7 @@ from selfdrive.car.nissan.values import CAR class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "nissan" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.nissan)] ret.autoResumeSng = False @@ -38,10 +36,6 @@ class CarInterface(CarInterfaceBase): ret.steerControlType = car.CarParams.SteerControlType.angle ret.radarOffCan = True - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index a920c02534..7dba8eaf23 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS @@ -9,9 +9,7 @@ from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "subaru" ret.radarOffCan = True ret.dashcamOnly = candidate in PREGLOBAL_CARS @@ -103,10 +101,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"unknown car: {candidate}") - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index 2eb29efb41..6573f4e4d9 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -2,14 +2,13 @@ from cereal import car from panda import Panda from selfdrive.car.tesla.values import CANBUS, CAR -from selfdrive.car import STD_CARGO_KG, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "tesla" # There is no safe way to do steer blending with user torque, @@ -51,7 +50,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"Unsupported car: {candidate}") - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) return ret diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 7ee5ac0d0e..d3835d175b 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -3,7 +3,7 @@ from cereal import car from common.conversions import Conversions as CV from panda import Panda from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, CarControllerParams, NO_STOP_TIMER_CAR -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase EventName = car.CarEvent.EventName @@ -15,9 +15,7 @@ class CarInterface(CarInterfaceBase): return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "toyota" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.toyota)] ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate] @@ -190,10 +188,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.44 - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index e0bb917960..997cdfcfc1 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,8 +1,7 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, \ - gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter @@ -22,8 +21,7 @@ class CarInterface(CarInterfaceBase): self.cp_ext = self.cp_cam @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "volkswagen" ret.radarOffCan = True @@ -74,7 +72,6 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 ret.steerRatio = 15.6 # Let the params learner figure this out - tire_stiffness_factor = 1.0 # Let the params learner figure this out ret.lateralTuning.pid.kpBP = [0.] ret.lateralTuning.pid.kiBP = [0.] ret.lateralTuning.pid.kf = 0.00006 @@ -213,10 +210,8 @@ class CarInterface(CarInterfaceBase): raise ValueError(f"unsupported car {candidate}") ret.autoResumeSng = ret.minEnableSpeed == -1 - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.45 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, - tire_stiffness_factor=tire_stiffness_factor) + ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) return ret # returns a car.CarState From 3a4f19f0ee454a9c8d7c55d66e794ef20c4b4f01 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 29 Nov 2022 12:46:08 -0800 Subject: [PATCH 117/185] [controlsd] Refactor apply_deadzone in Lateral Torque Control (#26626) rename apply_slack to apply_center_deadzone, remove unused prius offline values --- selfdrive/car/interfaces.py | 4 ++-- selfdrive/car/torque_data/params.yaml | 1 - selfdrive/controls/lib/drive_helpers.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index e03f1cfada..e062e783e5 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -11,7 +11,7 @@ from common.kalman.simple_kalman import KF1D from common.numpy_fast import interp from common.realtime import DT_CTRL from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia -from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_slack +from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_center_deadzone from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel @@ -127,7 +127,7 @@ class CarInterfaceBase(ABC): def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation): # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) friction_interp = interp( - apply_slack(lateral_accel_error, lateral_accel_deadzone), + apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone), [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-torque_params.friction, torque_params.friction] ) diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index b4dbaf8564..124cf3d3b9 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -78,7 +78,6 @@ TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600] TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993] TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515] -TOYOTA PRIUS 2017 x060: [1.746445, 1.5023147650693636, 0.17] TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968] TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] TOYOTA RAV4 2019: [2.331293, 2.0993589721530252, 0.129822] diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index acef4bee8e..3d5ec8ac1d 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -149,7 +149,7 @@ def apply_deadzone(error, deadzone): return error -def apply_slack(error, deadzone): +def apply_center_deadzone(error, deadzone): if (error > - deadzone) and (error < deadzone): error = 0. return error From 712b9014d708ce77e87e2d1bb4dfe2d7665a8414 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 29 Nov 2022 12:50:27 -0800 Subject: [PATCH 118/185] Car interface: set tire stiffness in common function (#26625) * common stiffness * GM uses factors --- selfdrive/car/body/interface.py | 4 +--- selfdrive/car/chrysler/interface.py | 7 +------ selfdrive/car/ford/interface.py | 3 +-- selfdrive/car/interfaces.py | 8 +++++++- selfdrive/car/nissan/interface.py | 12 ++++-------- selfdrive/car/subaru/interface.py | 6 +----- selfdrive/car/tesla/interface.py | 4 +--- selfdrive/car/tests/test_car_interfaces.py | 2 ++ selfdrive/car/volkswagen/interface.py | 3 +-- 9 files changed, 19 insertions(+), 30 deletions(-) diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py index 638134be7f..bc5b36e2ee 100644 --- a/selfdrive/car/body/interface.py +++ b/selfdrive/car/body/interface.py @@ -2,7 +2,7 @@ import math from cereal import car from common.realtime import DT_CTRL -from selfdrive.car import scale_tire_stiffness, get_safety_config +from selfdrive.car import get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.body.values import SPEED_FROM_RPM @@ -28,8 +28,6 @@ class CarInterface(CarInterfaceBase): ret.openpilotLongitudinalControl = True ret.steerControlType = car.CarParams.SteerControlType.angle - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret def _update(self, c): diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 9b5e5a5ce5..5e8fe3c44e 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.chrysler.values import CAR, DBC, RAM_HD, RAM_DT from selfdrive.car.interfaces import CarInterfaceBase @@ -72,11 +72,6 @@ class CarInterface(CarInterfaceBase): raise ValueError(f"Unsupported car: {candidate}") ret.centerToFront = ret.wheelbase * 0.44 - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - ret.enableBsm = 720 in fingerprint[0] return ret diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 913c3c926d..f3d77bc05a 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter from selfdrive.car.interfaces import CarInterfaceBase @@ -55,7 +55,6 @@ class CarInterface(CarInterfaceBase): ret.autoResumeSng = ret.minEnableSpeed == -1. ret.centerToFront = ret.wheelbase * 0.44 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) return ret def _update(self, c): diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index e062e783e5..0458178ee3 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -10,7 +10,7 @@ from common.conversions import Conversions as CV from common.kalman.simple_kalman import KF1D from common.numpy_fast import interp from common.realtime import DT_CTRL -from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia +from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_center_deadzone from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel @@ -103,6 +103,12 @@ class CarInterfaceBase(ABC): # civic and scaling by mass and wheelbase ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) + # TODO: some car interfaces set stiffness factor + if ret.tireStiffnessFront == 0 or ret.tireStiffnessRear == 0: + # TODO: start from empirically derived lateral slip stiffness for the civic and scale by + # mass and CG position, so all cars will have approximately similar dyn behaviors + ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) + return ret @staticmethod diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 194d7f06ae..386e859089 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.nissan.values import CAR @@ -18,6 +18,9 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 ret.steerRatio = 17 + ret.steerControlType = car.CarParams.SteerControlType.angle + ret.radarOffCan = True + if candidate in (CAR.ROGUE, CAR.XTRAIL): ret.mass = 1610 + STD_CARGO_KG ret.wheelbase = 2.705 @@ -33,13 +36,6 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.824 ret.centerToFront = ret.wheelbase * 0.44 - ret.steerControlType = car.CarParams.SteerControlType.angle - ret.radarOffCan = True - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret # returns a car.CarState diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 7dba8eaf23..22468801ec 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS @@ -101,10 +101,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"unknown car: {candidate}") - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret # returns a car.CarState diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index 6573f4e4d9..49e06d8923 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -2,7 +2,7 @@ from cereal import car from panda import Panda from selfdrive.car.tesla.values import CANBUS, CAR -from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase @@ -50,8 +50,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"Unsupported car: {candidate}") - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret def _update(self, c): diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 8d22173671..48d85584b3 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -31,6 +31,8 @@ class TestCarInterfaces(unittest.TestCase): assert car_interface self.assertGreater(car_params.mass, 1) + self.assertGreater(car_params.wheelbase, 0) + self.assertGreater(car_params.centerToFront, 0) self.assertGreater(car_params.maxLateralAccel, 0) if car_params.steerControlType != car.CarParams.SteerControlType.angle: diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 997cdfcfc1..d90a36ed3e 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,7 +1,7 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter @@ -211,7 +211,6 @@ class CarInterface(CarInterfaceBase): ret.autoResumeSng = ret.minEnableSpeed == -1 ret.centerToFront = ret.wheelbase * 0.45 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) return ret # returns a car.CarState From 8ad8d4e1c2887bcd22ab7048960ed6a1a7f8e3f6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 29 Nov 2022 13:11:03 -0800 Subject: [PATCH 119/185] controls: preEnabled doesn't consider noEntry (#26627) preEnabled doesn't consider noEntry --- selfdrive/controls/controlsd.py | 5 +---- selfdrive/controls/tests/test_state_machine.py | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 76f049ddd2..d99357fbb6 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -518,10 +518,7 @@ class Controls: # PRE ENABLING elif self.state == State.preEnabled: - if self.events.any(ET.NO_ENTRY): - self.state = State.disabled - self.current_alert_types.append(ET.NO_ENTRY) - elif not self.events.any(ET.PRE_ENABLE): + if not self.events.any(ET.PRE_ENABLE): self.state = State.enabled else: self.current_alert_types.append(ET.PRE_ENABLE) diff --git a/selfdrive/controls/tests/test_state_machine.py b/selfdrive/controls/tests/test_state_machine.py index 36535dfdaf..8f263a98e7 100755 --- a/selfdrive/controls/tests/test_state_machine.py +++ b/selfdrive/controls/tests/test_state_machine.py @@ -79,7 +79,7 @@ class TestStateMachine(unittest.TestCase): self.assertEqual(self.controlsd.state, State.disabled) def test_no_entry(self): - # disabled with enable events + # Make sure noEntry keeps us disabled for et in ENABLE_EVENT_TYPES: self.controlsd.events.add(make_event([ET.NO_ENTRY, et])) self.controlsd.state_transition(self.CS) @@ -87,11 +87,11 @@ class TestStateMachine(unittest.TestCase): self.controlsd.events.clear() def test_no_entry_pre_enable(self): - # preEnabled with preEnabled event + # preEnabled with noEntry event self.controlsd.state = State.preEnabled self.controlsd.events.add(make_event([ET.NO_ENTRY, ET.PRE_ENABLE])) self.controlsd.state_transition(self.CS) - self.assertEqual(self.controlsd.state, State.disabled) + self.assertEqual(self.controlsd.state, State.preEnabled) def test_maintain_states(self): # Given current state's event type, we should maintain state From c3e3047080cdcfd5fbe275534fdf6980903aebdf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 29 Nov 2022 16:05:46 -0800 Subject: [PATCH 120/185] boardd: cleanup defs from panda (#26628) * more stuff * bump pnada --- panda | 2 +- selfdrive/boardd/panda.cc | 9 ++++++--- selfdrive/boardd/panda.h | 12 ++++-------- selfdrive/boardd/tests/test_boardd_usbprotocol.cc | 2 -- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/panda b/panda index e4c4253964..80dac4cd94 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e4c4253964a25ff980520b70ea9f50aede4a1db6 +Subproject commit 80dac4cd94de8711304b2695b4d348d2152a8e3e diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index b82de593c8..788cdf5cf1 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -6,7 +6,6 @@ #include #include "cereal/messaging/messaging.h" -#include "panda/board/dlc_to_len.h" #include "common/swaglog.h" #include "common/util.h" @@ -255,8 +254,12 @@ bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &o canData.busTime = 0; canData.address = header.addr; canData.src = header.bus + bus_offset; - if (header.rejected) { canData.src += CANPACKET_REJECTED; } - if (header.returned) { canData.src += CANPACKET_RETURNED; } + if (header.rejected) { + canData.src += CAN_REJECTED_BUS_OFFSET; + } + if (header.returned) { + canData.src += CAN_RETURNED_BUS_OFFSET; + } const uint8_t data_len = dlc_to_len[header.data_len_code]; canData.dat.assign((char *)&recv_buf[pos + CANPACKET_HEAD_SIZE], data_len); diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index 5b3cbb9a3e..b20d8f0afa 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -11,20 +11,16 @@ #include "cereal/gen/cpp/car.capnp.h" #include "cereal/gen/cpp/log.capnp.h" #include "panda/board/health.h" +#include "panda/board/can_definitions.h" #include "selfdrive/boardd/panda_comms.h" - -#define PANDA_CAN_CNT 3 -#define PANDA_BUS_CNT 4 - #define USB_TX_SOFT_LIMIT (0x100U) #define USBPACKET_MAX_SIZE (0x40) #define RECV_SIZE (0x4000U) -#define CANPACKET_HEAD_SIZE 5U -#define CANPACKET_MAX_SIZE 72U -#define CANPACKET_REJECTED (0xC0U) -#define CANPACKET_RETURNED (0x80U) + +#define CAN_REJECTED_BUS_OFFSET 0xC0U +#define CAN_RETURNED_BUS_OFFSET 0x80U struct __attribute__((packed)) can_header { uint8_t reserved : 1; diff --git a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc index 6a13cbd71f..c7d14fb0ef 100644 --- a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc +++ b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc @@ -6,8 +6,6 @@ #include "cereal/messaging/messaging.h" #include "selfdrive/boardd/panda.h" -const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; - int random_int(int min, int max) { std::random_device dev; std::mt19937 rng(dev()); From dbe512d167a51f02fe276e17a8794de2137d0e14 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 29 Nov 2022 17:34:10 -0800 Subject: [PATCH 121/185] controlsd: preEnable on brake at a standstill (#26273) * pre-enable on brake at standstill * test by getting rid of noTarget * Update events.py * rename * stash what we got so far * cmt * not sure if best way to fix, but enabled with current states defs is fine * no noEntry in preEnabled * bumpcereal * uncomment * Update ref_commit Co-authored-by: Adeeb Shihadeh --- cereal | 2 +- selfdrive/controls/controlsd.py | 5 ++++- selfdrive/controls/lib/events.py | 4 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cereal b/cereal index 3bae09cf65..7f55399fde 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 3bae09cf6527674d7eda3a9956242aad94a8f3d2 +Subproject commit 7f55399fde0b6a70c1bf7df4f86b90b0060e31ac diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index d99357fbb6..e5c981aa6b 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -249,6 +249,9 @@ class Controls: (CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)): self.events.add(EventName.pedalPressed) + if CS.brakePressed and CS.standstill: + self.events.add(EventName.preEnableStandstill) + if CS.gasPressed: self.events.add(EventName.gasPressedOverride) @@ -579,7 +582,7 @@ class Controls: # Check which actuators can be enabled CC.latActive = self.active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ CS.vEgo > self.CP.minSteerSpeed and not CS.standstill - CC.longActive = self.active and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl + CC.longActive = self.enabled and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl actuators = CC.actuators actuators.longControlState = self.LoC.long_control_state diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index ad10b8f0bd..5d5000ff54 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -615,9 +615,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { visual_alert=VisualAlert.brakePressed), }, - EventName.pedalPressedPreEnable: { + EventName.preEnableStandstill: { ET.PRE_ENABLE: Alert( - "Release Pedal to Engage", + "Release Brake to Engage", "", AlertStatus.normal, AlertSize.small, Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.), diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 71da0dff44..4641299458 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -ddfaab44ae64a0d064b847e81080993b51b6b423 \ No newline at end of file +f17412941a0e8229eea308c33189a5bdb1a17ae8 From 41610fe8ae7019264f09fbbb5db64e48b45b7ca0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 29 Nov 2022 23:12:58 -0800 Subject: [PATCH 122/185] controls: remove noTarget event (#26632) remove noTarget alert --- selfdrive/controls/controlsd.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index e5c981aa6b..342ecc52fe 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -420,16 +420,6 @@ class Controls: if self.sm['liveLocationKalman'].excessiveResets: self.events.add(EventName.localizerMalfunction) - # Only allow engagement with brake pressed when stopped behind another stopped car - speeds = self.sm['longitudinalPlan'].speeds - if len(speeds) > 1: - v_future = speeds[-1] - else: - v_future = 100.0 - if CS.brakePressed and v_future >= self.CP.vEgoStarting \ - and self.CP.openpilotLongitudinalControl and CS.vEgo < 0.3: - self.events.add(EventName.noTarget) - def data_sample(self): """Receive data from sockets and update carState""" From bea73943733a2a38a8cacd7de25d78b067460f2b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 30 Nov 2022 00:08:55 -0800 Subject: [PATCH 123/185] bump panda (#26637) --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 80dac4cd94..e8bd1df511 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 80dac4cd94de8711304b2695b4d348d2152a8e3e +Subproject commit e8bd1df5119e457ed2162ea36777bf47a99527f2 From 5409895ab2668284df7b688ce513fa0ef1e57be2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 30 Nov 2022 02:32:47 -0800 Subject: [PATCH 124/185] Deprecate noTarget event (#26638) deprecate notarget --- cereal | 2 +- selfdrive/controls/lib/events.py | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/cereal b/cereal index 7f55399fde..19a0c46b71 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 7f55399fde0b6a70c1bf7df4f86b90b0060e31ac +Subproject commit 19a0c46b71150a8dabc5644eb24f261feee45b9c diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 5d5000ff54..5578a83a23 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -916,15 +916,6 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.NO_ENTRY: NoEntryAlert("Harness Relay Malfunction"), }, - EventName.noTarget: { - ET.IMMEDIATE_DISABLE: Alert( - "openpilot Canceled", - "No close lead car", - AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), - ET.NO_ENTRY: NoEntryAlert("No Close Lead Car"), - }, - EventName.speedTooLow: { ET.IMMEDIATE_DISABLE: Alert( "openpilot Canceled", From 2b916044f5e7e6466709bd45301765d0b7599eec Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 1 Dec 2022 04:40:36 +0800 Subject: [PATCH 125/185] Cabana: sort signals by address in logs view (#26639) cleanup --- tools/cabana/historylog.cc | 48 +++++++++++++++++--------------------- tools/cabana/historylog.h | 13 ++++------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 65fea3361e..5457555db4 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -5,20 +5,15 @@ // HistoryLogModel -inline const Signal &get_signal(const DBCMsg *m, int index) { - return std::next(m->sigs.begin(), index)->second; -} - QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { - bool has_signal = dbc_msg && !dbc_msg->sigs.empty(); if (role == Qt::DisplayRole) { const auto &m = messages[index.row()]; if (index.column() == 0) { return QString::number(m.ts, 'f', 2); } - return has_signal ? QString::number(get_raw_value((uint8_t *)m.dat.begin(), m.dat.size(), get_signal(dbc_msg, index.column() - 1))) - : toHex(m.dat); - } else if (role == Qt::FontRole && index.column() == 1 && !has_signal) { + return !sigs.empty() ? QString::number(get_raw_value((uint8_t *)m.dat.data(), m.dat.size(), *sigs[index.column() - 1])) + : toHex(m.dat); + } else if (role == Qt::FontRole && index.column() == 1 && sigs.empty()) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); } return {}; @@ -27,25 +22,25 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { void HistoryLogModel::setMessage(const QString &message_id) { beginResetModel(); msg_id = message_id; - dbc_msg = dbc()->msg(message_id); - column_count = (dbc_msg && !dbc_msg->sigs.empty() ? dbc_msg->sigs.size() : 1) + 1; - row_count = 0; + sigs.clear(); + messages.clear(); + if (auto dbc_msg = dbc()->msg(message_id)) { + sigs = dbc_msg->getSignals(); + } endResetModel(); - updateState(); } QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { - bool has_signal = dbc_msg && !dbc_msg->sigs.empty(); if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { if (section == 0) { return "Time"; } - return has_signal ? QString::fromStdString(get_signal(dbc_msg, section - 1).name).replace('_', ' ') : "Data"; - } else if (role == Qt::BackgroundRole && section > 0 && has_signal) { + return !sigs.empty() ? QString::fromStdString(sigs[section - 1]->name).replace('_', ' ') : "Data"; + } else if (role == Qt::BackgroundRole && section > 0 && !sigs.empty()) { return QBrush(QColor(getColor(section - 1))); - } else if (role == Qt::ForegroundRole && section > 0 && has_signal) { + } else if (role == Qt::ForegroundRole && section > 0 && !sigs.empty()) { return QBrush(Qt::black); } } @@ -53,21 +48,20 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i } void HistoryLogModel::updateState() { - if (msg_id.isEmpty()) return; - - int prev_row_count = row_count; - messages = can->messages(msg_id); - row_count = messages.size(); - int delta = row_count - prev_row_count; + int prev_row_count = messages.size(); + if (!msg_id.isEmpty()) { + messages = can->messages(msg_id); + } + int delta = messages.size() - prev_row_count; if (delta > 0) { - beginInsertRows({}, prev_row_count, row_count - 1); + beginInsertRows({}, prev_row_count, messages.size() - 1); endInsertRows(); } else if (delta < 0) { - beginRemoveRows({}, row_count, prev_row_count - 1); + beginRemoveRows({}, messages.size(), prev_row_count - 1); endRemoveRows(); } - if (row_count > 0) { - emit dataChanged(index(0, 0), index(row_count - 1, column_count - 1), {Qt::DisplayRole}); + if (!messages.empty()) { + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), {Qt::DisplayRole}); } } @@ -106,5 +100,5 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { int HistoryLog::sizeHintForColumn(int column) const { // sizeHintForColumn is only called for column 0 (ResizeToContents) - return itemDelegate()->sizeHint(viewOptions(), model->index(0, 0)).width() + 1; // +1 for grid + return itemDelegate()->sizeHint(viewOptions(), model->index(0, 0)).width() + 5; } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index dfe037c13f..9ca6f427c7 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -14,32 +14,27 @@ public: }; class HistoryLogModel : public QAbstractTableModel { - Q_OBJECT - public: HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {} void setMessage(const QString &message_id); void updateState(); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } - int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return messages.size(); } + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return std::max(1ul, sigs.size()) + 1; } private: QString msg_id; - int row_count = 0; - int column_count = 2; - const DBCMsg *dbc_msg = nullptr; std::deque messages; + std::vector sigs; }; class HistoryLog : public QTableView { - Q_OBJECT - public: HistoryLog(QWidget *parent); void setMessage(const QString &message_id) { model->setMessage(message_id); } void updateState() { model->updateState(); } + private: int sizeHintForColumn(int column) const override; HistoryLogModel *model; From 553da4ef30b709d754fb89b5519ce182a9ace3e2 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 30 Nov 2022 13:00:36 -0800 Subject: [PATCH 126/185] DM: no reset on steer if distracted (#26567) * only for attentive fast resets * oops wheeltouch * update test --- selfdrive/monitoring/driver_monitor.py | 15 ++++++++++----- selfdrive/monitoring/test_monitoring.py | 8 +++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index e3f6a5094d..a2cddc2462 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -156,6 +156,11 @@ class DriverStatus(): self._set_timers(active_monitoring=True) + def _reset_awareness(self): + self.awareness = 1. + self.awareness_active = 1. + self.awareness_passive = 1. + def _set_timers(self, active_monitoring): if self.active_monitoring_mode and self.awareness <= self.threshold_prompt: if active_monitoring: @@ -289,17 +294,17 @@ class DriverStatus(): self.hi_stds = 0 def update_events(self, events, driver_engaged, ctrl_active, standstill): - if (driver_engaged and self.awareness > 0) or not ctrl_active: - # reset only when on disengagement if red reached - self.awareness = 1. - self.awareness_active = 1. - self.awareness_passive = 1. + if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or not ctrl_active: # reset only when on disengagement if red reached + self._reset_awareness() return driver_attentive = self.driver_distraction_filter.x < 0.37 awareness_prev = self.awareness if (driver_attentive and self.face_detected and self.pose.low_std and self.awareness > 0): + if driver_engaged: + self._reset_awareness() + return # only restore awareness when paying attention and alert is not red self.awareness = min(self.awareness + ((self.settings._RECOVERY_FACTOR_MAX-self.settings._RECOVERY_FACTOR_MIN)*(1.-self.awareness)+self.settings._RECOVERY_FACTOR_MIN)*self.step_change, 1.) if self.awareness == 1.: diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 43b5e7747e..f72b4a3aaa 100755 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -101,11 +101,12 @@ class TestMonitoring(unittest.TestCase): ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0], EventName.driverUnresponsive) # engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel - # - should have short orange recovery time and no green afterwards; should recover rightaway on wheel touch + # - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention def test_normal_driver(self): ds_vector = [msg_DISTRACTED] * int(DISTRACTED_SECONDS_TO_ORANGE/DT_DMON) + \ [msg_ATTENTIVE] * int(DISTRACTED_SECONDS_TO_ORANGE/DT_DMON) + \ - [msg_DISTRACTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*2/DT_DMON)) + [msg_DISTRACTED] * int((DISTRACTED_SECONDS_TO_ORANGE+2)/DT_DMON) + \ + [msg_ATTENTIVE] * (int(TEST_TIMESPAN/DT_DMON)-int((DISTRACTED_SECONDS_TO_ORANGE*3+2)/DT_DMON)) interaction_vector = [car_interaction_NOT_DETECTED] * int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON) + \ [car_interaction_DETECTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) events, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) @@ -113,7 +114,8 @@ class TestMonitoring(unittest.TestCase): self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) self.assertEqual(len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]), 0) self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) - self.assertEqual(len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)]), 0) + self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) + self.assertEqual(len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]), 0) # engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ # driver dodges, and then touches wheel to no avail, disengages and reengages From cb4fef580c8c71744b9b5b91be17f73b6fcdd097 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 30 Nov 2022 13:11:50 -0800 Subject: [PATCH 127/185] Update RELEASES.md --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 97c92dfa47..e1051acd9a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,7 +1,7 @@ Version 0.9.1 (2022-12-XX) ======================== * New driving model - +* Removed driver monitoring timer resetting on interaction if face detected and distracted Version 0.9.0 (2022-11-21) ======================== From 821d8ff12f3b9c9c9ba852dd7160f5a72d3d761d Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 30 Nov 2022 15:11:26 -0800 Subject: [PATCH 128/185] tools: pyenv setup fix (#26601) * setup pyenv immediately * sudo * fix --- tools/ubuntu_setup.sh | 18 ++++++++++++++---- update_requirements.sh | 9 +++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 7e021bcc23..403437bfb3 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -4,13 +4,23 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" ROOT="$(cd $DIR/../ && pwd)" +SUDO="" # NOTE: this is used in a docker build, so do not run any scripts here. +# Use sudo if not root +if [[ ! $(id -u) -eq 0 ]]; then + if [[ -z $(which sudo) ]]; then + echo "Please install sudo or run as root" + exit 1 + fi + SUDO="sudo" +fi + # Install packages present in all supported versions of Ubuntu function install_ubuntu_common_requirements() { - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ + $SUDO apt-get update + $SUDO apt-get install -y --no-install-recommends \ autoconf \ build-essential \ ca-certificates \ @@ -74,7 +84,7 @@ function install_ubuntu_common_requirements() { function install_ubuntu_jammy_requirements() { install_ubuntu_common_requirements - sudo apt-get install -y --no-install-recommends \ + $SUDO apt-get install -y --no-install-recommends \ qtbase5-dev \ qtchooser \ qt5-qmake \ @@ -86,7 +96,7 @@ function install_ubuntu_jammy_requirements() { function install_ubuntu_focal_requirements() { install_ubuntu_common_requirements - sudo apt-get install -y --no-install-recommends \ + $SUDO apt-get install -y --no-install-recommends \ libavresample-dev \ qt5-default \ python-dev diff --git a/update_requirements.sh b/update_requirements.sh index 8511a0a4d6..9195799ca3 100755 --- a/update_requirements.sh +++ b/update_requirements.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" @@ -22,8 +22,13 @@ if [ -z "\$PYENV_ROOT" ]; then eval "\$(pyenv virtualenv-init -)" fi EOF + + # setup now without restarting shell + export PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH + export PYENV_ROOT="$HOME/.pyenv" + eval "$(pyenv init -)" + eval "$(pyenv virtualenv-init -)" fi -source $RC_FILE export MAKEFLAGS="-j$(nproc)" From 02b5b6fe1f9f919f0cafea3efc6be2403a3f7139 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 30 Nov 2022 15:36:25 -0800 Subject: [PATCH 129/185] soundd: change system sound mixer volume (#26633) * test changing sound volume * create system/hardware/pc/hardware.h * soundd: use Hardware::set_volume * implement Hardware::set_volume using pactl * Revert "test changing sound volume" This reverts commit 4bbd870746ec86d1c9871a6175def96cf7f751a6. * don't run command in background * pactl: use default sink --- release/files_common | 1 + selfdrive/ui/soundd/sound.cc | 8 ++------ system/hardware/base.h | 1 + system/hardware/hw.h | 10 +--------- system/hardware/pc/hardware.h | 21 +++++++++++++++++++++ system/hardware/tici/hardware.h | 7 +++++++ 6 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 system/hardware/pc/hardware.h diff --git a/release/files_common b/release/files_common index a294e1e5b5..2400c3c0dc 100644 --- a/release/files_common +++ b/release/files_common @@ -216,6 +216,7 @@ system/hardware/tici/amplifier.py system/hardware/tici/updater system/hardware/tici/iwlist.py system/hardware/pc/__init__.py +system/hardware/pc/hardware.h system/hardware/pc/hardware.py selfdrive/locationd/__init__.py diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 6830450d8f..841bea3b8b 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -20,7 +20,6 @@ Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) QObject::connect(s, &QSoundEffect::statusChanged, [=]() { assert(s->status() != QSoundEffect::Error); }); - s->setVolume(Hardware::MIN_VOLUME); s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); sounds[alert] = {s, loops}; } @@ -49,12 +48,9 @@ void Sound::update() { // scale volume with speed if (sm.updated("carState")) { - float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.0f); + float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.f); volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); - volume = util::map_val(volume, 0.f, 1.f, Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); - for (auto &[s, loops] : sounds) { - s->setVolume(std::round(100 * volume) / 100); - } + Hardware::set_volume(volume); } setAlert(Alert::get(sm, started_frame)); diff --git a/system/hardware/base.h b/system/hardware/base.h index b70948d482..f6e0b42d73 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -20,6 +20,7 @@ public: static void poweroff() {} static void set_brightness(int percent) {} static void set_display_power(bool on) {} + static void set_volume(float volume) {} static bool get_ssh_enabled() { return false; } static void set_ssh_enabled(bool enabled) {} diff --git a/system/hardware/hw.h b/system/hardware/hw.h index f50e94abe1..5599e79186 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -7,15 +7,7 @@ #include "system/hardware/tici/hardware.h" #define Hardware HardwareTici #else -class HardwarePC : public HardwareNone { -public: - static std::string get_os_version() { return "openpilot for PC"; } - static std::string get_name() { return "pc"; }; - static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }; - static bool PC() { return true; } - static bool TICI() { return util::getenv("TICI", 0) == 1; } - static bool AGNOS() { return util::getenv("TICI", 0) == 1; } -}; +#include "system/hardware/pc/hardware.h" #define Hardware HardwarePC #endif diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h new file mode 100644 index 0000000000..529b4bfe9d --- /dev/null +++ b/system/hardware/pc/hardware.h @@ -0,0 +1,21 @@ +#pragma once + +#include "system/hardware/base.h" + +class HardwarePC : public HardwareNone { +public: + static std::string get_os_version() { return "openpilot for PC"; } + static std::string get_name() { return "pc"; }; + static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }; + static bool PC() { return true; } + static bool TICI() { return util::getenv("TICI", 0) == 1; } + static bool AGNOS() { return util::getenv("TICI", 0) == 1; } + + static void set_volume(float volume) { + volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); + + char volume_str[6]; + snprintf(volume_str, sizeof(volume_str), "%.3f", volume); + std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); + } +}; diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index dcccb9f3d1..02becb76e4 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -38,6 +38,13 @@ public: bl_power_control.close(); } }; + static void set_volume(float volume) { + volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); + + char volume_str[6]; + snprintf(volume_str, sizeof(volume_str), "%.3f", volume); + std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); + } static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); }; static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); }; From 01dac5d06bcd518e6c2daab8c41979b0553b73a6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 30 Nov 2022 20:27:55 -0800 Subject: [PATCH 130/185] Build panda/SConscript (#26646) * panda/SConscript * think this is right? * bump panda * fix test_models * use a filter * bump panda to mater * more aesthetic ordering * alphabetical :( * alphabetical :( --- SConstruct | 9 +++------ panda | 2 +- selfdrive/car/tests/test_models.py | 18 +++++++----------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/SConstruct b/SConstruct index 033e10a1f0..54b008004e 100644 --- a/SConstruct +++ b/SConstruct @@ -300,7 +300,7 @@ if arch == "Darwin": else: qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip() qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip() - + qt_env['QTDIR'] = qt_install_prefix qt_dirs = [ f"{qt_install_headers}", @@ -409,10 +409,10 @@ if arch != "Darwin": # build submodules SConscript([ - 'cereal/SConscript', 'body/board/SConscript', - 'panda/board/SConscript', + 'cereal/SConscript', 'opendbc/can/SConscript', + 'panda/SConscript', ]) SConscript(['third_party/SConscript']) @@ -442,9 +442,6 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): Export('opendbc') SConscript(['tools/cabana/SConscript']) -if GetOption('test'): - SConscript('panda/tests/safety/SConscript') - external_sconscript = GetOption('external_sconscript') if external_sconscript: SConscript([external_sconscript]) diff --git a/panda b/panda index e8bd1df511..2ae7b9a4d5 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e8bd1df5119e457ed2162ea36777bf47a99527f2 +Subproject commit 2ae7b9a4d59101c0a1a9015feb3e835763db5686 diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 56530dd738..641c109316 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -9,7 +9,6 @@ from parameterized import parameterized_class from cereal import log, car from common.realtime import DT_CTRL -from selfdrive.boardd.boardd import can_capnp_to_can_list from selfdrive.car.fingerprints import all_known_cars from selfdrive.car.car_helpers import interfaces from selfdrive.car.gm.values import CAR as GM @@ -20,8 +19,7 @@ from selfdrive.test.openpilotci import get_url from tools.lib.logreader import LogReader from tools.lib.route import Route -from panda.tests.safety import libpandasafety_py -from panda.tests.safety.common import package_can_msg +from panda.tests.libpanda import libpanda_py PandaType = log.PandaState.PandaType @@ -118,7 +116,7 @@ class TestCarModelBase(unittest.TestCase): assert self.CI # TODO: check safetyModel is in release panda build - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda cfg = self.CP.safetyConfigs[-1] set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam) @@ -192,7 +190,7 @@ class TestCarModelBase(unittest.TestCase): if msg.src >= 64: continue - to_send = package_can_msg([msg.address, 0, msg.dat, msg.src % 4]) + to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) if self.safety.safety_rx_hook(to_send) != 1: failed_addrs[hex(msg.address)] += 1 @@ -215,8 +213,8 @@ class TestCarModelBase(unittest.TestCase): # warm up pass, as initial states may be different for can in self.can_msgs[:300]: self.CI.update(CC, (can.as_builder().to_bytes(), )) - for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): - to_send = package_can_msg(msg) + for msg in filter(lambda m: m.src in range(64), can.can): + to_send = libpanda_py.make_CANPacket(msg.address, msg.src, msg.dat) self.safety.safety_rx_hook(to_send) controls_allowed_prev = False @@ -224,10 +222,8 @@ class TestCarModelBase(unittest.TestCase): checks = defaultdict(lambda: 0) for idx, can in enumerate(self.can_msgs): CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) - for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): - msg = list(msg) - msg[3] %= 4 - to_send = package_can_msg(msg) + for msg in filter(lambda m: m.src in range(64), can.can): + to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) ret = self.safety.safety_rx_hook(to_send) self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") From 997b2e11aef4ccc801a075dea13ed4aa9a5de27c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 30 Nov 2022 20:36:19 -0800 Subject: [PATCH 131/185] Honda Nidec: put max PCM gas into CarControllerParams (#26645) * limit gas * bump panda * not sure why it was represented like this --- selfdrive/car/honda/carcontroller.py | 6 +++--- selfdrive/car/honda/values.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 790dce1810..66a1485dc6 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -189,14 +189,14 @@ class CarController: clip(CS.out.vEgo + 0.0, 0.0, 100.0), clip(CS.out.vEgo + 5.0, 0.0, 100.0)] pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V) - pcm_accel = int(1.0 * 0xc6) + pcm_accel = int(1.0 * self.params.NIDEC_GAS_MAX) else: pcm_speed_V = [0.0, clip(CS.out.vEgo - 2.0, 0.0, 100.0), clip(CS.out.vEgo + 2.0, 0.0, 100.0), clip(CS.out.vEgo + 5.0, 0.0, 100.0)] pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V) - pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * 0xc6) + pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * self.params.NIDEC_GAS_MAX) if not self.CP.openpilotLongitudinalControl: if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: # radarless cars don't have supplemental message @@ -254,7 +254,7 @@ class CarController: self.speed = pcm_speed if not self.CP.enableGasInterceptor: - self.gas = pcm_accel / 0xc6 + self.gas = pcm_accel / self.params.NIDEC_GAS_MAX new_actuators = actuators.copy() new_actuators.speed = self.speed diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 151c2140f5..f32e7bef2f 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -27,6 +27,7 @@ class CarControllerParams: NIDEC_MAX_ACCEL_V = [0.5, 2.4, 1.4, 0.6] NIDEC_MAX_ACCEL_BP = [0.0, 4.0, 10., 20.] + NIDEC_GAS_MAX = 198 # 0xc6 NIDEC_BRAKE_MAX = 1024 // 4 BOSCH_ACCEL_MIN = -3.5 # m/s^2 From 470fe9ce34eeccc8cb0ab27f7b8cee603904d5a1 Mon Sep 17 00:00:00 2001 From: Jason Wen <47793918+sunnyhaibin@users.noreply.github.com> Date: Wed, 30 Nov 2022 23:57:05 -0500 Subject: [PATCH 132/185] Hyundai: Add FW for 2022 Tucson Hybrid (#26647) --- selfdrive/car/hyundai/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 16ff68f08a..81cc4bb54f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1421,6 +1421,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', From 3d2f6c1dc05c716acb40ed612719f7d0ba3d2a8f Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 30 Nov 2022 21:45:44 -0800 Subject: [PATCH 133/185] Fix OpenCL driver URL (#26650) --- Dockerfile.openpilot_base_cl | 2 +- tools/webcam/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.openpilot_base_cl b/Dockerfile.openpilot_base_cl index 7652b7e4e6..4c8ecfc78d 100644 --- a/Dockerfile.openpilot_base_cl +++ b/Dockerfile.openpilot_base_cl @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends\ # Intel OpenCL driver ARG INTEL_DRIVER=l_opencl_p_18.1.0.015.tgz -ARG INTEL_DRIVER_URL=http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532 +ARG INTEL_DRIVER_URL=https://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532 RUN mkdir -p /tmp/opencl-driver-intel WORKDIR /tmp/opencl-driver-intel RUN echo INTEL_DRIVER is $INTEL_DRIVER && \ diff --git a/tools/webcam/README.md b/tools/webcam/README.md index f896e19b86..237e07cdb6 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -18,7 +18,7 @@ git clone https://github.com/commaai/openpilot.git - Follow [this readme](https://github.com/commaai/openpilot/tree/master/tools) to install the requirements - Add line "export PYTHONPATH=$HOME/openpilot" to your ~/.bashrc - Install tensorflow 2.2 and nvidia drivers: nvidia-xxx/cuda10.0/cudnn7.6.5 -- Install [OpenCL Driver](http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532/l_opencl_p_18.1.0.015.tgz) +- Install [OpenCL Driver](https://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532/l_opencl_p_18.1.0.015.tgz) - Install [OpenCV4](https://www.pyimagesearch.com/2018/08/15/how-to-install-opencv-4-on-ubuntu/) (ignore the Python part) ## Build openpilot for webcam From 108ff15f5dc16f79a36a2d33397b36dba42d70cf Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 30 Nov 2022 21:56:03 -0800 Subject: [PATCH 134/185] micd: scale sound volume with ambient noise level (#26399) * test changing sound volume * test changing sound volume * create system/hardware/pc/hardware.h * implement Hardware::set_volume using pactl * soundd: use Hardware::set_volume * add sounddevice dependency * sounddevice example * simple micd * cleanup * remove this * fix process config * add to release files * hardware: get sound input device * no more offroad * debug * calculate volume from all measurements since last update * use microphone noise level to update sound volume * fix scale * mute microphone during alerts * log raw noise level * hardware: reduce tici min volume * improve scale * add package * clear measurements on muted * change default to min volume and respond quicker * fixes Co-authored-by: Shane Smiskol * logarithmic scaling * fix * respond quicker * fixes * tweak scaling * specify default device * Revert "hardware: get sound input device" This reverts commit 50f594f7a3bab005023482bc793147a8c8dae5d7. * tuning * forgot to update submaster * tuning * don't mute microphone, and clip measurement * remove submaster * fixes * tuning * implement Hardware::set_volume using pactl * Revert "test changing sound volume" This reverts commit 4bbd870746ec86d1c9871a6175def96cf7f751a6. * draft * draft * calculate sound pressure level in dB * fix setting * faster filter * start at initial value * don't run command in background * pactl: use default sink * use sound pressure db * tuning * bump up max volume threshold * update filter slower * fix divide by zero * bump cereal Co-authored-by: Shane Smiskol --- cereal | 2 +- poetry.lock | 32 +++++++++----- pyproject.toml | 1 + release/files_common | 1 + selfdrive/manager/process_config.py | 1 + selfdrive/ui/soundd/sound.cc | 6 +-- system/hardware/tici/hardware.h | 2 +- system/micd.py | 67 +++++++++++++++++++++++++++++ tools/ubuntu_setup.sh | 1 + 9 files changed, 98 insertions(+), 15 deletions(-) create mode 100755 system/micd.py diff --git a/cereal b/cereal index 19a0c46b71..dbc9846ac9 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 19a0c46b71150a8dabc5644eb24f261feee45b9c +Subproject commit dbc9846ac9c9e735ee2f4a281ce079cfff7ea285 diff --git a/poetry.lock b/poetry.lock index d79a3f035f..fbe0009a26 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3696,6 +3696,20 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "sounddevice" +version = "0.4.5" +description = "Play and Record Sound with Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +CFFI = ">=1.0" + +[package.extras] +numpy = ["NumPy"] + [[package]] name = "soupsieve" version = "2.3.2.post1" @@ -4386,7 +4400,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "d2854112975a9d83a9540175b2d430487e40e0292d48a1ba6c591db60a08c136" +content-hash = "a41b1d669ec4f0a94abe12af8a9a7d709b2bb56e9c83aa87801cdf5d9af074f8" [metadata.files] adal = [ @@ -5392,7 +5406,6 @@ gevent = [ {file = "gevent-22.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2ea4ce36c09355379bc038be2bd50118f97d2eb6381b7096de4d05aa4c3e241"}, {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e73c9f71aa2a6795ecbec9b57282b002375e863e283558feb87b62840c8c1ac"}, {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc3758f0dc95007c1780d28a9fd2150416a79c50f308f62a674d78a845ea1b9"}, - {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c10ca0beeab0c6be516030471ea630447ddd1f649d3335e5b162097cd4130a"}, {file = "gevent-22.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fe2c0ff095171c49f78f1d4e6dc89fa58253783c7b6dccab9f1d76e2ee391f10"}, {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d18fcc324f39a3b21795022eb47c7752d6e4f4ed89d8cca41f1cc604553265b3"}, {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ea39c70ce166c4a1d4386c7fae96cb8d84ad799527b3378406051104d15443"}, @@ -6563,11 +6576,6 @@ pillow-avif-plugin = [ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017e5e52cb4320414e8ce3e2089eae2cb87c22c73ff6012b17ae326fc5753b20"}, {file = "pillow_avif_plugin-1.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a57136d4866de5dc80cfb24d66655955fbdd87acf1d11d88c8dc2ab41023e46"}, {file = "pillow_avif_plugin-1.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:f339511d0fccb69e3a5e3af39f8fe6700b0a07279015006ea56f8f49e7fecff4"}, - {file = "pillow_avif_plugin-1.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05e821ecd90bb0b8d2dc7610625372cc47de9cb893d09662528bad572f669d1c"}, - {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33886a5f9796fe9a8a3bc25ccfdeba7db119adb50b7004f1928a14b07d0213a"}, - {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75b7ed186c2f740dd26e556f6a966c59a170b70263e429a2c81920fe444da8a7"}, - {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11aef6b79078b8dad25c928e5871c146ab94424472851d5bf539ba62abde9ac"}, - {file = "pillow_avif_plugin-1.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10696c536d68a14cefea3b98edb8d5a7ae29e8e07458f1d59c5d1cd780a8bf2a"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1a7291d6a5fb7336e72685a31d193e0b3a6bee9986c9ac4d8bd4b68dbe6d4f7f"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:14b9c5dbf237e7dc12f69819ea181a457b3bd4f59f8cd71d028d3635fd3bcab4"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:799cbfbeee831332d280c80df9ce16b5c3b1224c318264e97e89df8da32e870e"}, @@ -6956,13 +6964,11 @@ pyprof2calltree = [ ] pyproj = [ {file = "pyproj-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f343725566267a296b09ee7e591894f1fdc90f84f8ad5ec476aeb53bd4479c07"}, - {file = "pyproj-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5816807ca0bdc7256558770c6206a6783a3f02bcf844f94ee245f197bb5f7285"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e609903572a56cca758bbaee5c1663c3e829ddce5eec4f368e68277e37022b"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fd425ee8b6781c249c7adb7daa2e6c41ce573afabe4f380f5eecd913b56a3be"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954b068136518b3174d0a99448056e97af62b63392a95c420894f7de2229dae6"}, {file = "pyproj-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:4a23d84c5ffc383c7d9f0bde3a06fc1f6697b1b96725597f8f01e7b4bef0a2b5"}, {file = "pyproj-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f9c100fd0fd80edbc7e4daa303600a8cbef6f0de43d005617acb38276b88dc0"}, - {file = "pyproj-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa5171f700f174777a9e9ed8f4655583243967c0f9cf2c90e3f54e54ff740134"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a496d9057b2128db9d733e66b206f2d5954bbae6b800d412f562d780561478c"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e54796e2d9554a5eb8f11df4748af1fbbc47f76aa234d6faf09216a84554c5"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a454a7c4423faa2a14e939d08ef293ee347fa529c9df79022b0585a6e1d8310c"}, @@ -6973,7 +6979,6 @@ pyproj = [ {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f80adda8c54b84271a93829477a01aa57bc178c834362e9f74e1de1b5033c74c"}, {file = "pyproj-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:221d8939685e0c43ee594c9f04b6a73a10e8e1cc0e85f28be0b4eb2f1bc8777d"}, {file = "pyproj-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d94afed99f31673d3d19fe750283621e193e2a53ca9e0443bf9d092c3905833b"}, - {file = "pyproj-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fff9c3a991508f16027be27d153f6c5583d03799443639d13c681e60f49e2d7"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b85acf09e5a9e35cd9ee72989793adb7089b4e611be02a43d3d0bda50ad116b"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45554f47d1a12a84b0620e4abc08a2a1b5d9f273a4759eaef75e74788ec7162a"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12f62c20656ac9b6076ebb213e9a635d52f4f01fef95310121d337e62e910cb6"}, @@ -7515,6 +7520,13 @@ sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +sounddevice = [ + {file = "sounddevice-0.4.5-py3-none-any.whl", hash = "sha256:5cea4afd9412e731f50ae09a54d68b10628a604cfd56b42a976c54d424c6c39d"}, + {file = "sounddevice-0.4.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:0875173595a8bd5a66b5a03a3d958e7b89c3b956b8befbe4491a24a3ce7784c0"}, + {file = "sounddevice-0.4.5-py3-none-win32.whl", hash = "sha256:442adf53850916374a58f902200aaf9412227378181264e60c966da64be47d41"}, + {file = "sounddevice-0.4.5-py3-none-win_amd64.whl", hash = "sha256:d3216c5d3d678c3301058e9aac7000879e255140c524c9ef98730091b67ea676"}, + {file = "sounddevice-0.4.5.tar.gz", hash = "sha256:2fe0d41299e4f3037dad2acede4eff0666b34a1fa3da5335e47120373964bef5"}, +] soupsieve = [ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, diff --git a/pyproject.toml b/pyproject.toml index 7e76b9cdfc..c890b78875 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ sentry-sdk = "^1.6.0" setproctitle = "^1.2.3" six = "^1.16.0" smbus2 = "^0.4.2" +sounddevice = "^0.4.5" sympy = "^1.10.1" timezonefinder = "^6.0.1" tqdm = "^4.64.0" diff --git a/release/files_common b/release/files_common index 2400c3c0dc..aa6c0ac55c 100644 --- a/release/files_common +++ b/release/files_common @@ -71,6 +71,7 @@ selfdrive/rtshield.py selfdrive/statsd.py system/logmessaged.py +system/micd.py system/swaglog.py system/version.py diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index dbccb8d4a9..50c19610ed 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -24,6 +24,7 @@ procs = [ NativeProcess("logcatd", "system/logcatd", ["./logcatd"]), NativeProcess("proclogd", "system/proclogd", ["./proclogd"]), PythonProcess("logmessaged", "system.logmessaged", offroad=True), + PythonProcess("micd", "system.micd"), PythonProcess("timezoned", "system.timezoned", enabled=not PC, offroad=True), DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 841bea3b8b..3deb6ceca0 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -12,7 +12,7 @@ // TODO: detect when we can't play sounds // TODO: detect when we can't display the UI -Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) { +Sound::Sound(QObject *parent) : sm({"controlsState", "deviceState", "microphone"}) { qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); for (auto &[alert, fn, loops] : sound_list) { @@ -47,8 +47,8 @@ void Sound::update() { } // scale volume with speed - if (sm.updated("carState")) { - float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.f); + if (sm.updated("microphone")) { + float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureDb(), 58.f, 77.f, 0.f, 1.f); volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); Hardware::set_volume(volume); } diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index 02becb76e4..d388f9c48a 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -10,7 +10,7 @@ class HardwareTici : public HardwareNone { public: static constexpr float MAX_VOLUME = 0.9; - static constexpr float MIN_VOLUME = 0.2; + static constexpr float MIN_VOLUME = 0.1; static bool TICI() { return true; } static bool AGNOS() { return true; } static std::string get_os_version() { diff --git a/system/micd.py b/system/micd.py new file mode 100755 index 0000000000..d2a5a2849f --- /dev/null +++ b/system/micd.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +import sounddevice as sd +import numpy as np + +from cereal import messaging +from common.filter_simple import FirstOrderFilter +from common.realtime import Ratekeeper +from system.swaglog import cloudlog + +RATE = 10 +DT_MIC = 1. / RATE +REFERENCE_SPL = 2 * 10 ** -5 # newtons/m^2 + + +class Mic: + def __init__(self, pm): + self.pm = pm + self.rk = Ratekeeper(RATE) + + self.measurements = np.empty(0) + self.spl_filter = FirstOrderFilter(0, 4, DT_MIC, initialized=False) + + def update(self): + # self.measurements contains amplitudes from -1 to 1 which we use to + # calculate an uncalibrated sound pressure level + if len(self.measurements) > 0: + # https://www.engineeringtoolbox.com/sound-pressure-d_711.html + sound_pressure = np.sqrt(np.mean(self.measurements ** 2)) # RMS of amplitudes + sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) if sound_pressure > 0 else 0 # dB + self.spl_filter.update(sound_pressure_level) + else: + sound_pressure = 0 + sound_pressure_level = 0 + + self.measurements = np.empty(0) + + msg = messaging.new_message('microphone') + msg.microphone.soundPressure = float(sound_pressure) + msg.microphone.soundPressureDb = float(sound_pressure_level) + msg.microphone.filteredSoundPressureDb = float(self.spl_filter.x) + + self.pm.send('microphone', msg) + self.rk.keep_time() + + def callback(self, indata, frames, time, status): + self.measurements = np.concatenate((self.measurements, indata[:, 0])) + + def micd_thread(self, device=None): + if device is None: + device = "sysdefault" + + with sd.InputStream(device=device, channels=1, samplerate=44100, callback=self.callback) as stream: + cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") + while True: + self.update() + + +def main(pm=None, sm=None): + if pm is None: + pm = messaging.PubMaster(['microphone']) + + mic = Mic(pm) + mic.micd_thread() + + +if __name__ == "__main__": + main() diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 403437bfb3..89d33a6127 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -56,6 +56,7 @@ function install_ubuntu_common_requirements() { libomp-dev \ libopencv-dev \ libpng16-16 \ + libportaudio2 \ libssl-dev \ libsqlite3-dev \ libusb-1.0-0-dev \ From 17e06bdc93c2fac37e72eec5ac569307023cfb29 Mon Sep 17 00:00:00 2001 From: royjr Date: Thu, 1 Dec 2022 01:21:48 -0500 Subject: [PATCH 135/185] Honda: Add missing modified firmware comment (#26648) Update values.py --- selfdrive/car/honda/values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index f32e7bef2f..905c9f4b4f 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -243,7 +243,7 @@ FW_VERSIONS = { b'39990-TVA-A340\x00\x00', b'39990-TVA-X030\x00\x00', b'39990-TVA-X040\x00\x00', - b'39990-TVA,A150\x00\x00', + b'39990-TVA,A150\x00\x00', # modified firmware b'39990-TVE-H130\x00\x00', ], (Ecu.unknown, 0x18da3af1, None): [ From 996a6c06d4522100357e04fea0182d33dc6e7f23 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 30 Nov 2022 23:12:35 -0800 Subject: [PATCH 136/185] sim: disable micd (#26653) --- tools/sim/launch_openpilot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index adabc40c2e..2100f05b67 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -6,7 +6,7 @@ export SIMULATION="1" export SKIP_FW_QUERY="1" export FINGERPRINT="HONDA CIVIC 2016" -export BLOCK="camerad,loggerd,encoderd" +export BLOCK="camerad,loggerd,encoderd,micd" if [[ "$CI" ]]; then # TODO: offscreen UI should work export BLOCK="${BLOCK},ui" From 50f4328b904cf4debd278834adcf83107a3d4e5c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 30 Nov 2022 23:16:48 -0800 Subject: [PATCH 137/185] Update RELEASES.md --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index e1051acd9a..1ae4a233be 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ Version 0.9.1 (2022-12-XX) ======================== -* New driving model +* Adjust alert volume using ambient noise level * Removed driver monitoring timer resetting on interaction if face detected and distracted Version 0.9.0 (2022-11-21) From 9e0607806fcb5d1a5a00eb8ec0a0ef05fb6fd7f3 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Wed, 30 Nov 2022 23:27:02 -0800 Subject: [PATCH 138/185] [torqued] Set Hyundai Ioniq 5 2022 values (#26554) set ioniq values, different from ev6, from offline data --- selfdrive/car/torque_data/params.yaml | 1 + selfdrive/car/torque_data/substitute.yaml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 124cf3d3b9..7901972fc2 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -27,6 +27,7 @@ HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] HYUNDAI ELANTRA 2021: [3.169, 2.1259108157250735, 0.0819] HYUNDAI GENESIS 2015-2016: [1.8466226943929824, 1.5552063647830634, 0.0984484465421171] +HYUNDAI IONIQ 5 2022: [3.172929, 3.0, 0.096019] HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index 77236e393e..df696bc0fc 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -29,7 +29,6 @@ HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019 HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA HYBRID 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019 -HYUNDAI IONIQ 5 2022: KIA EV6 2022 HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 From 92ddd3c57e03f2ba602b147790eed4bb7b97a24c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 1 Dec 2022 00:28:40 -0800 Subject: [PATCH 139/185] add new panda SConscript to release files --- release/files_common | 1 + 1 file changed, 1 insertion(+) diff --git a/release/files_common b/release/files_common index aa6c0ac55c..2f7f2d4d61 100644 --- a/release/files_common +++ b/release/files_common @@ -486,6 +486,7 @@ cereal/visionipc/*.pxd panda/.gitignore panda/__init__.py +panda/SConscript panda/board/** panda/certs/** panda/crypto/** From fee6f2efacf41d2bd964754d0e5e193e18e16f16 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Thu, 1 Dec 2022 18:12:16 +0100 Subject: [PATCH 140/185] Simple CAN chunks (#25373) * simple chunks * more sizeofs * fix unit tests * bump panda * bump panda * don't fail for too little data * bump panda * bump panda * bump panda Co-authored-by: Adeeb Shihadeh --- panda | 2 +- selfdrive/boardd/panda.cc | 55 +++++++++---------- .../boardd/tests/test_boardd_usbprotocol.cc | 19 +++---- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/panda b/panda index 2ae7b9a4d5..288e14cde9 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 2ae7b9a4d59101c0a1a9015feb3e835763db5686 +Subproject commit 288e14cde904a5755ebb32b0853631ac7fbf5968 diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 788cdf5cf1..db9bcbab51 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -169,22 +169,15 @@ static uint8_t len_to_dlc(uint8_t len) { } } -static void write_packet(uint8_t *dest, int *write_pos, const uint8_t *src, size_t size) { - for (int i = 0, &pos = *write_pos; i < size; ++i, ++pos) { - // Insert counter every 64 bytes (first byte of 64 bytes USB packet) - if (pos % USBPACKET_MAX_SIZE == 0) { - dest[pos] = pos / USBPACKET_MAX_SIZE; - pos++; - } - dest[pos] = src[i]; - } -} - void Panda::pack_can_buffer(const capnp::List::Reader &can_data_list, std::function write_func) { int32_t pos = 0; uint8_t send_buf[2 * USB_TX_SOFT_LIMIT]; + uint32_t magic = CAN_TRANSACTION_MAGIC; + memcpy(&send_buf[0], &magic, sizeof(uint32_t)); + pos += sizeof(uint32_t); + for (auto cmsg : can_data_list) { // check if the message is intended for this panda uint8_t bus = cmsg.getSrc(); @@ -202,16 +195,19 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data header.data_len_code = data_len_code; header.bus = bus - bus_offset; - write_packet(send_buf, &pos, (uint8_t *)&header, sizeof(can_header)); - write_packet(send_buf, &pos, (uint8_t *)can_data.begin(), can_data.size()); + memcpy(&send_buf[pos], (uint8_t *)&header, sizeof(can_header)); + pos += sizeof(can_header); + memcpy(&send_buf[pos], (uint8_t *)can_data.begin(), can_data.size()); + pos += can_data.size(); + if (pos >= USB_TX_SOFT_LIMIT) { write_func(send_buf, pos); - pos = 0; + pos = sizeof(uint32_t); } } // send remaining packets - if (pos > 0) write_func(send_buf, pos); + if (pos > sizeof(uint32_t)) write_func(send_buf, pos); } void Panda::can_send(capnp::List::Reader can_data_list) { @@ -235,20 +231,23 @@ bool Panda::can_receive(std::vector& out_vec) { bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec) { recv_buf.clear(); - for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) { - if (data[i] != i / USBPACKET_MAX_SIZE) { - LOGE("CAN: MALFORMED USB RECV PACKET"); - handle->comms_healthy = false; - return false; - } - int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i)); - recv_buf.insert(recv_buf.end(), &data[i + 1], &data[i + chunk_len]); + + if (size < sizeof(uint32_t)) { + return true; + } + + uint32_t magic; + memcpy(&magic, &data[0], sizeof(uint32_t)); + if (magic != CAN_TRANSACTION_MAGIC) { + LOGE("CAN: MALFORMED CAN RECV PACKET"); + handle->comms_healthy = false; + return false; } - int pos = 0; - while (pos < recv_buf.size()) { + int pos = sizeof(uint32_t); + while (pos < size) { can_header header; - memcpy(&header, &recv_buf[pos], CANPACKET_HEAD_SIZE); + memcpy(&header, &data[pos], sizeof(can_header)); can_frame &canData = out_vec.emplace_back(); canData.busTime = 0; @@ -262,9 +261,9 @@ bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &o } const uint8_t data_len = dlc_to_len[header.data_len_code]; - canData.dat.assign((char *)&recv_buf[pos + CANPACKET_HEAD_SIZE], data_len); + canData.dat.assign((char *)&data[pos + sizeof(can_header)], data_len); - pos += CANPACKET_HEAD_SIZE + data_len; + pos += sizeof(can_header) + data_len; } return true; } diff --git a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc index c7d14fb0ef..cc3b4bce9a 100644 --- a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc +++ b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc @@ -48,7 +48,7 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState can.setAddress(i); can.setSrc(random_int(0, 3) + bus_offset); can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); - total_pakets_size += CANPACKET_HEAD_SIZE + dat.size(); + total_pakets_size += sizeof(can_header) + dat.size(); } can_data_list = can_list.asReader(); @@ -58,14 +58,11 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState void PandaTest::test_can_send() { std::vector unpacked_data; this->pack_can_buffer(can_data_list, [&](uint8_t *chunk, size_t size) { - int size_left = size; - for (int i = 0, counter = 0; i < size; i += USBPACKET_MAX_SIZE, counter++) { - REQUIRE(chunk[i] == counter); - - const int len = std::min(USBPACKET_MAX_SIZE, size_left); - unpacked_data.insert(unpacked_data.end(), &chunk[i + 1], &chunk[i + len]); - size_left -= len; - } + uint32_t magic; + memcpy(&magic, &chunk[0], sizeof(uint32_t)); + + REQUIRE(magic == CAN_TRANSACTION_MAGIC); + unpacked_data.insert(unpacked_data.end(), &chunk[sizeof(uint32_t)], &chunk[size]); }); REQUIRE(unpacked_data.size() == total_pakets_size); @@ -73,9 +70,9 @@ void PandaTest::test_can_send() { INFO("test can message integrity"); for (int pos = 0, pckt_len = 0; pos < unpacked_data.size(); pos += pckt_len) { can_header header; - memcpy(&header, &unpacked_data[pos], CANPACKET_HEAD_SIZE); + memcpy(&header, &unpacked_data[pos], sizeof(can_header)); const uint8_t data_len = dlc_to_len[header.data_len_code]; - pckt_len = CANPACKET_HEAD_SIZE + data_len; + pckt_len = sizeof(can_header) + data_len; REQUIRE(header.addr == cnt); REQUIRE(test_data.find(data_len) != test_data.end()); From 74c0ac3deced5ac85b72ec1a4e51dae851cdb440 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 2 Dec 2022 01:17:02 +0800 Subject: [PATCH 141/185] Cabana: setSectionResizeMode to QHeaderView::Fixed (#26655) fixed header --- tools/cabana/binaryview.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 6a4f66dcd4..e655bf0c04 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -23,6 +23,7 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { setItemDelegate(delegate); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); verticalHeader()->setSectionsClickable(false); + verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); horizontalHeader()->hide(); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); From 92e985c583bfb23c569008157037e77b6080a6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 1 Dec 2022 11:56:25 -0800 Subject: [PATCH 142/185] voltage cleanup thermald (#26658) --- selfdrive/thermald/power_monitoring.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index e62f0f97c3..b748064d2b 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -18,6 +18,7 @@ VBATT_PAUSE_CHARGING = 11.0 # Lower limit on the LPF car battery volta VBATT_INSTANT_PAUSE_CHARGING = 7.0 # Lower limit on the instant car battery voltage measurements to avoid triggering on instant power loss MAX_TIME_OFFROAD_S = 30*3600 MIN_ON_TIME_S = 3600 +VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 60 class PowerMonitoring: def __init__(self): @@ -114,8 +115,12 @@ class PowerMonitoring: now = sec_since_boot() should_shutdown = False - should_shutdown |= (now - offroad_timestamp) > MAX_TIME_OFFROAD_S - should_shutdown |= (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3)) and (self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3)) + offroad_time = (now - offroad_timestamp) + low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and + self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3) and + offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) + should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S + should_shutdown |= low_voltage_shutdown should_shutdown |= (self.car_battery_capacity_uWh <= 0) should_shutdown &= not ignition should_shutdown &= (not self.params.get_bool("DisablePowerDown")) From 86cd919a57be22fa0ccf324a8767999309df60e4 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 1 Dec 2022 12:52:06 -0800 Subject: [PATCH 143/185] micd: don't update filtered sound level if playing sound (#26652) * add is_sound_playing to hardware.py * micd: don't update filtered sound level if playing sound --- system/hardware/base.py | 4 ++++ system/hardware/pc/hardware.py | 4 ++++ system/hardware/tici/hardware.py | 3 +++ system/micd.py | 4 +++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index 31df1babe0..16ed9621c1 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -43,6 +43,10 @@ class HardwareBase(ABC): def get_sound_card_online(self): pass + @abstractmethod + def is_sound_playing(self): + pass + @abstractmethod def get_imei(self, slot) -> str: pass diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 564f9e483a..2c83eb35f4 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -1,4 +1,5 @@ import random +import subprocess from cereal import log from system.hardware.base import HardwareBase, ThermalConfig @@ -17,6 +18,9 @@ class Pc(HardwareBase): def get_sound_card_online(self): return True + def is_sound_playing(self): + return "RUNNING" in subprocess.check_output(["pactl", "list", "short", "sinks"]).decode('utf8') + def reboot(self, reason=None): print("REBOOT!") diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index b5f5e00410..8371d6ef70 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -96,6 +96,9 @@ class Tici(HardwareBase): return (os.path.isfile('/proc/asound/card0/state') and open('/proc/asound/card0/state').read().strip() == 'ONLINE') + def is_sound_playing(self): + return "RUNNING" in subprocess.check_output(["pactl", "list", "short", "sinks"]).decode('utf8') + def reboot(self, reason=None): subprocess.check_output(["sudo", "reboot"]) diff --git a/system/micd.py b/system/micd.py index d2a5a2849f..27b6cb5f19 100755 --- a/system/micd.py +++ b/system/micd.py @@ -5,6 +5,7 @@ import numpy as np from cereal import messaging from common.filter_simple import FirstOrderFilter from common.realtime import Ratekeeper +from system.hardware import HARDWARE from system.swaglog import cloudlog RATE = 10 @@ -27,7 +28,8 @@ class Mic: # https://www.engineeringtoolbox.com/sound-pressure-d_711.html sound_pressure = np.sqrt(np.mean(self.measurements ** 2)) # RMS of amplitudes sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) if sound_pressure > 0 else 0 # dB - self.spl_filter.update(sound_pressure_level) + if not HARDWARE.is_sound_playing(): + self.spl_filter.update(sound_pressure_level) else: sound_pressure = 0 sound_pressure_level = 0 From 7a9d96759d3a4db277edcce2d5e3ffda9c6823d5 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Thu, 1 Dec 2022 13:11:31 -0800 Subject: [PATCH 144/185] Revert "voltage cleanup thermald (#26658)" This reverts commit 92e985c583bfb23c569008157037e77b6080a6f6. --- selfdrive/thermald/power_monitoring.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index b748064d2b..e62f0f97c3 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -18,7 +18,6 @@ VBATT_PAUSE_CHARGING = 11.0 # Lower limit on the LPF car battery volta VBATT_INSTANT_PAUSE_CHARGING = 7.0 # Lower limit on the instant car battery voltage measurements to avoid triggering on instant power loss MAX_TIME_OFFROAD_S = 30*3600 MIN_ON_TIME_S = 3600 -VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 60 class PowerMonitoring: def __init__(self): @@ -115,12 +114,8 @@ class PowerMonitoring: now = sec_since_boot() should_shutdown = False - offroad_time = (now - offroad_timestamp) - low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and - self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3) and - offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) - should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S - should_shutdown |= low_voltage_shutdown + should_shutdown |= (now - offroad_timestamp) > MAX_TIME_OFFROAD_S + should_shutdown |= (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3)) and (self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3)) should_shutdown |= (self.car_battery_capacity_uWh <= 0) should_shutdown &= not ignition should_shutdown &= (not self.params.get_bool("DisablePowerDown")) From 196caa095b1c028926e580bb9c671338d08d3fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 1 Dec 2022 13:57:11 -0800 Subject: [PATCH 145/185] thermald: add minimum offroad time to low voltage shutdown (#26660) * voltage cleanup thermald * Fix voltage shutdown test --- selfdrive/thermald/power_monitoring.py | 9 +++++++-- selfdrive/thermald/tests/test_power_monitoring.py | 10 +++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index e62f0f97c3..b748064d2b 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -18,6 +18,7 @@ VBATT_PAUSE_CHARGING = 11.0 # Lower limit on the LPF car battery volta VBATT_INSTANT_PAUSE_CHARGING = 7.0 # Lower limit on the instant car battery voltage measurements to avoid triggering on instant power loss MAX_TIME_OFFROAD_S = 30*3600 MIN_ON_TIME_S = 3600 +VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 60 class PowerMonitoring: def __init__(self): @@ -114,8 +115,12 @@ class PowerMonitoring: now = sec_since_boot() should_shutdown = False - should_shutdown |= (now - offroad_timestamp) > MAX_TIME_OFFROAD_S - should_shutdown |= (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3)) and (self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3)) + offroad_time = (now - offroad_timestamp) + low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and + self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3) and + offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) + should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S + should_shutdown |= low_voltage_shutdown should_shutdown |= (self.car_battery_capacity_uWh <= 0) should_shutdown &= not ignition should_shutdown &= (not self.params.get_bool("DisablePowerDown")) diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py index 4a5def7740..d41e92454b 100755 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ b/selfdrive/thermald/tests/test_power_monitoring.py @@ -120,15 +120,19 @@ class TestPowerMonitoring(unittest.TestCase): def test_car_voltage(self): POWER_DRAW = 0 # To stop shutting down for other reasons TEST_TIME = 100 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): + VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 50 + with pm_patch("VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S", VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = False + start_time = ssb for i in range(TEST_TIME): pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: - self.assertEqual(pm.should_shutdown(ignition, True, ssb, True), (pm.car_voltage_mV < VBATT_PAUSE_CHARGING*1e3)) - self.assertTrue(pm.should_shutdown(ignition, True, ssb, True)) + self.assertEqual(pm.should_shutdown(ignition, True, start_time, True), + (pm.car_voltage_mV < VBATT_PAUSE_CHARGING*1e3 and + (ssb - start_time) > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)) + self.assertTrue(pm.should_shutdown(ignition, True, start_time, True)) # Test to check policy of not stopping charging when DisablePowerDown is set def test_disable_power_down(self): From 1adf106da69c7d73d465f41ad2836a562ca64f9c Mon Sep 17 00:00:00 2001 From: Jason Wen <47793918+sunnyhaibin@users.noreply.github.com> Date: Thu, 1 Dec 2022 17:11:12 -0500 Subject: [PATCH 146/185] HKG: Car Port for Kia Sorento Plug-in Hybrid 2022 (#26635) * HKG: Car Port for Kia Sorento Plug-in Hybrid 2022 * Add torque params * Typo * This car has SCC on bus 4, needs radar disable to use openpilot longitudinal :/ * Need to check on bus 4 as well * Different unit signal * Unit signal cleanup * Add test route * Regenerate car docs * Different logic * 2023 should be the same * Fix 0x1a0 check race condition * Update RELEASES.md Co-authored-by: Adeeb Shihadeh --- RELEASES.md | 1 + docs/CARS.md | 3 ++- opendbc | 2 +- selfdrive/car/hyundai/carstate.py | 10 +++++++--- selfdrive/car/hyundai/interface.py | 4 ++++ selfdrive/car/hyundai/values.py | 17 ++++++++++++++--- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/override.yaml | 1 + 8 files changed, 31 insertions(+), 8 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 1ae4a233be..7b51e6dff3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,7 @@ Version 0.9.1 (2022-12-XX) ======================== * Adjust alert volume using ambient noise level * Removed driver monitoring timer resetting on interaction if face detected and distracted +* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! Version 0.9.0 (2022-11-21) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index a0d22a3d27..f8455b25ea 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 215 Supported Cars +# 216 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -104,6 +104,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| +|Kia|Sorento Plug-in Hybrid 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| diff --git a/opendbc b/opendbc index 871e054d9a..18bff2369b 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 871e054d9a94629d92c22fe89cae71af5b0d3823 +Subproject commit 18bff2369b44ecb36638885c974aae1891616ae5 diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 2c309fa0df..dfb33f2eb4 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -6,7 +6,7 @@ from cereal import car from common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine -from selfdrive.car.hyundai.values import HyundaiFlags, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams from selfdrive.car.interfaces import CarStateBase PREV_BUTTON_SAMPLES = 8 @@ -197,7 +197,9 @@ class CarState(CarStateBase): ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0 ret.cruiseState.available = True - self.is_metric = cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] != 1 + cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" + distance_unit_msg = cruise_btn_msg if self.CP.carFingerprint == CAR.KIA_SORENTO_PHEV_4TH_GEN else "CLUSTER_INFO" + self.is_metric = cp.vl[distance_unit_msg]["DISTANCE_UNIT"] != 1 if not self.CP.openpilotLongitudinalControl: speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp @@ -206,7 +208,6 @@ class CarState(CarStateBase): ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"]) - cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" self.prev_cruise_buttons = self.cruise_buttons[-1] self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"]) @@ -446,6 +447,9 @@ class CarState(CarStateBase): ("DRIVER_SEATBELT_LATCHED", "DOORS_SEATBELTS"), ] + if CP.carFingerprint == CAR.KIA_SORENTO_PHEV_4TH_GEN: + signals.append(("DISTANCE_UNIT", cruise_btn_msg)) + checks = [ ("WHEEL_SPEEDS", 100), (gear_msg, 100), diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index c1fe8be4c2..cb4d9a9688 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -185,6 +185,10 @@ class CarInterface(CarInterfaceBase): ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only ret.wheelbase = 2.756 ret.steerRatio = 13.6 + elif candidate == CAR.KIA_SORENTO_PHEV_4TH_GEN: + ret.mass = 4095.8 * CV.LB_TO_KG + STD_CARGO_KG # weight from EX and above trims, average of FWD and AWD versions (EX, X-Line EX AWD, SX, SX Pestige, X-Line SX Prestige AWD) + ret.wheelbase = 2.81 + ret.steerRatio = 13.27 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sorento-phev/2022/specifications # Genesis elif candidate == CAR.GENESIS_G70: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 81cc4bb54f..7ecb07e7d7 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -93,6 +93,7 @@ class CAR: KIA_SELTOS = "KIA SELTOS 2021" KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN" KIA_SORENTO = "KIA SORENTO GT LINE 2018" + KIA_SORENTO_PHEV_4TH_GEN = "KIA SORENTO PLUG-IN HYBRID 4TH GEN" KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" KIA_STINGER_2022 = "KIA STINGER 2022" @@ -180,6 +181,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), ], + CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "Smart Cruise Control (SCC)", harness=Harness.hyundai_a), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", harness=Harness.hyundai_k), @@ -1394,6 +1396,14 @@ FW_VERSIONS = { b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00' ], }, + CAR.KIA_SORENTO_PHEV_4TH_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + ] + }, CAR.KIA_EV6: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', @@ -1478,15 +1488,15 @@ FEATURES = { "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # The radar does SCC on these cars when HDA I, rather than the camera -CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, } +CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } -HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN} # these cars use a different gas signal +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # these cars use a different gas signal EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5} # these cars require a special panda safety mode due to missing counters and checksums in the messages @@ -1543,4 +1553,5 @@ DBC = { CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index cd61528439..da19e1f367 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -98,6 +98,7 @@ routes = [ CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), + CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_PHEV_4TH_GEN), CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2 CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 6ec782444c..1eda931e3e 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -34,6 +34,7 @@ HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.1] KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1] KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1] GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] +KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] From 30165134e8963e260245c1b6596ca3ae3cc08fea Mon Sep 17 00:00:00 2001 From: EdwardApollo <51210836+EdwardApollo@users.noreply.github.com> Date: Thu, 1 Dec 2022 17:18:32 -0500 Subject: [PATCH 147/185] Increase Minimum Offroad Battery Voltage (#26661) * Reduce Min Idle Battery Voltage and Idle Time Updated minimum offroad battery voltage to reduce wear on car batteries and updated max offroad time to reflect the increased power consumption at idle based on EPHOT hardware power draw (this may have changed). Most vehicles make use of lead acid batteries which during normal use shouldn't ever decrease below ~12V (sources give a range of 11.8-12V). Increasing this limit will prevent premature battery failure by preventing sulfation of the cells. * Voltage cutoff to 11.8V, max offroad to 30h Changed lower voltage limit to 11.8V, removed power usage comment entirely, reverted change to max offroad time to 30 hours * Added Offroad Shutdown conditional Added IsOffroad Shutdown conditional statement to prevent shutting down due to voltage floor. * LPF Gain: Tau = 45s Tau modified to 45s to result in a LPF gain of ~0.011. Assuming a dT of 0.5s: (0.5/45)/((0.5/45)+1) * Revert IsOffroad check Removed due to unnecessary redundancy. --- selfdrive/thermald/power_monitoring.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index b748064d2b..85e9510eb7 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -7,14 +7,13 @@ from system.hardware import HARDWARE from system.swaglog import cloudlog from selfdrive.statsd import statlog -CAR_VOLTAGE_LOW_PASS_K = 0.091 # LPF gain for 5s tau (dt/tau / (dt/tau + 1)) +CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) -# A C2 uses about 1W while idling, and 30h seens like a good shutoff for most cars # While driving, a battery charges completely in about 30-60 minutes CAR_BATTERY_CAPACITY_uWh = 30e6 CAR_CHARGING_RATE_W = 45 -VBATT_PAUSE_CHARGING = 11.0 # Lower limit on the LPF car battery voltage +VBATT_PAUSE_CHARGING = 11.8 # Lower limit on the LPF car battery voltage VBATT_INSTANT_PAUSE_CHARGING = 7.0 # Lower limit on the instant car battery voltage measurements to avoid triggering on instant power loss MAX_TIME_OFFROAD_S = 30*3600 MIN_ON_TIME_S = 3600 From c210640b7cc42b18efd27ce4f0bd7c7328674ade Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 1 Dec 2022 14:21:57 -0800 Subject: [PATCH 148/185] bump opendbc --- opendbc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc b/opendbc index 18bff2369b..3737675445 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 18bff2369b44ecb36638885c974aae1891616ae5 +Subproject commit 373767544559718d74bff58dd23890eea1d38a29 From 753c3d5897d46f9c070f9267d7385b3578185b2e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 1 Dec 2022 15:32:59 -0800 Subject: [PATCH 149/185] jenkins: move test_manager to common devices (#26663) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index c34d253585..e8323bca7d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -115,7 +115,6 @@ pipeline { ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], - ["test manager", "python selfdrive/manager/test/test_manager.py"], ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], ["test car interfaces", "cd selfdrive/car/tests/ && ./test_car_interfaces.py"], ]) @@ -141,6 +140,7 @@ pipeline { ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"], ["test pigeond", "python selfdrive/sensord/tests/test_pigeond.py"], + ["test manager", "python selfdrive/manager/test/test_manager.py"], ]) } } From dad438158ec60df10dc7e4b4fb8f225af190defd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 1 Dec 2022 20:14:10 -0800 Subject: [PATCH 150/185] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 288e14cde9..616450c525 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 288e14cde904a5755ebb32b0853631ac7fbf5968 +Subproject commit 616450c525a7c1ef508f70f30e29547a606139aa From 1f4554b0c030cfc2623d7c7db413e9c61dabaef4 Mon Sep 17 00:00:00 2001 From: Vybhav Date: Thu, 1 Dec 2022 22:03:15 -0800 Subject: [PATCH 151/185] Toyota: Add FW for 2023 Toyota Camry Hybrid (#26654) * Toyota: Add FW for 2023 Toyota Camry Hybrid Toyota: Updating CAR_INFO list to show 2023 for Camry * Update CARS.md for 2023 Toyota Camry * remove duplicate fw, sort alphabetically Co-authored-by: Vybhav Achar Bhargav Co-authored-by: Vybhav Achar Bhargav Co-authored-by: Cameron Clough --- docs/CARS.md | 2 +- selfdrive/car/toyota/values.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index f8455b25ea..df0dda742c 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -166,7 +166,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|Camry 2018-20|All|Stock|0 mph[5](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Camry 2021-22|All|openpilot|0 mph[5](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|Camry Hybrid 2021-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 4f2ab478c7..e112ebdb80 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -113,7 +113,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.CAMRY: ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-22", footnotes=[Footnote.CAMRY]), - CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-22"), + CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-23"), CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-21"), CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-19"), CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), @@ -551,10 +551,12 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F152633D00\x00\x00\x00\x00\x00\x00', b'F152633D60\x00\x00\x00\x00\x00\x00', + b'F152633310\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', b'\x018966306Q7000\x00\x00\x00\x00', + b'\x018966306V1000\x00\x00\x00\x00', b'\x01896633T20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 15): [ @@ -565,6 +567,7 @@ FW_VERSIONS = { b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CHR: { From d087fab2c0af6baf104591dade313311aeed1e4b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 2 Dec 2022 00:11:29 -0800 Subject: [PATCH 152/185] micd: apply A-weighting to the sound pressure level (#26668) * record * record * draft * some clean up * some clean up * wishful tuning * log pressure level (db) for debugging * fix * tuning * ignore complex to real warning * remove this * Update selfdrive/ui/soundd/sound.cc * Update system/micd.py * remove warning supp * bump cereal to master Co-authored-by: Cameron Clough --- cereal | 2 +- selfdrive/ui/soundd/sound.cc | 2 +- system/micd.py | 62 ++++++++++++++++++++++++++++-------- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/cereal b/cereal index dbc9846ac9..7765176413 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit dbc9846ac9c9e735ee2f4a281ce079cfff7ea285 +Subproject commit 7765176413c0bb14143fe2469d5390ea0ea61a1e diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 3deb6ceca0..73d65eb1f7 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -48,7 +48,7 @@ void Sound::update() { // scale volume with speed if (sm.updated("microphone")) { - float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureDb(), 58.f, 77.f, 0.f, 1.f); + float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 55.f, 0.f, 1.f); volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); Hardware::set_volume(volume); } diff --git a/system/micd.py b/system/micd.py index 27b6cb5f19..150dcb9cbe 100755 --- a/system/micd.py +++ b/system/micd.py @@ -10,7 +10,35 @@ from system.swaglog import cloudlog RATE = 10 DT_MIC = 1. / RATE -REFERENCE_SPL = 2 * 10 ** -5 # newtons/m^2 +REFERENCE_SPL = 2e-5 # newtons/m^2 +SAMPLE_RATE = 44100 + + +def calculate_spl(measurements): + # https://www.engineeringtoolbox.com/sound-pressure-d_711.html + sound_pressure = np.sqrt(np.mean(measurements ** 2)) # RMS of amplitudes + if sound_pressure > 0: + sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) # dB + else: + sound_pressure_level = 0 + return sound_pressure, sound_pressure_level + + +def apply_a_weighting(measurements: np.ndarray) -> np.ndarray: + # Generate a Hanning window of the same length as the audio measurements + hanning_window = np.hanning(len(measurements)) + measurements_windowed = measurements * hanning_window + + # Calculate the frequency axis for the signal + freqs = np.fft.fftfreq(measurements_windowed.size, d=1 / SAMPLE_RATE) + + # Calculate the A-weighting filter + # https://en.wikipedia.org/wiki/A-weighting + A = 12194 ** 2 * freqs ** 4 / ((freqs ** 2 + 20.6 ** 2) * (freqs ** 2 + 12194 ** 2) * np.sqrt((freqs ** 2 + 107.7 ** 2) * (freqs ** 2 + 737.9 ** 2))) + A /= np.max(A) # Normalize the filter + + # Apply the A-weighting filter to the signal + return np.abs(np.fft.ifft(np.fft.fft(measurements_windowed) * A)) class Mic: @@ -19,27 +47,35 @@ class Mic: self.rk = Ratekeeper(RATE) self.measurements = np.empty(0) - self.spl_filter = FirstOrderFilter(0, 4, DT_MIC, initialized=False) + self.spl_filter_weighted = FirstOrderFilter(0, 2.5, DT_MIC, initialized=False) def update(self): - # self.measurements contains amplitudes from -1 to 1 which we use to - # calculate an uncalibrated sound pressure level + """ + Using amplitude measurements, calculate an uncalibrated sound pressure and sound pressure level. + Then apply A-weighting to the raw amplitudes and run the same calculations again. + + Logged A-weighted equivalents are rough approximations of the human-perceived loudness. + """ + if len(self.measurements) > 0: - # https://www.engineeringtoolbox.com/sound-pressure-d_711.html - sound_pressure = np.sqrt(np.mean(self.measurements ** 2)) # RMS of amplitudes - sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) if sound_pressure > 0 else 0 # dB + sound_pressure, _ = calculate_spl(self.measurements) + measurements_weighted = apply_a_weighting(self.measurements) + sound_pressure_weighted, sound_pressure_level_weighted = calculate_spl(measurements_weighted) if not HARDWARE.is_sound_playing(): - self.spl_filter.update(sound_pressure_level) + self.spl_filter_weighted.update(sound_pressure_level_weighted) else: sound_pressure = 0 - sound_pressure_level = 0 + sound_pressure_weighted = 0 + sound_pressure_level_weighted = 0 self.measurements = np.empty(0) msg = messaging.new_message('microphone') msg.microphone.soundPressure = float(sound_pressure) - msg.microphone.soundPressureDb = float(sound_pressure_level) - msg.microphone.filteredSoundPressureDb = float(self.spl_filter.x) + msg.microphone.soundPressureWeighted = float(sound_pressure_weighted) + + msg.microphone.soundPressureWeightedDb = float(sound_pressure_level_weighted) + msg.microphone.filteredSoundPressureWeightedDb = float(self.spl_filter_weighted.x) self.pm.send('microphone', msg) self.rk.keep_time() @@ -51,13 +87,13 @@ class Mic: if device is None: device = "sysdefault" - with sd.InputStream(device=device, channels=1, samplerate=44100, callback=self.callback) as stream: + with sd.InputStream(device=device, channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") while True: self.update() -def main(pm=None, sm=None): +def main(pm=None): if pm is None: pm = messaging.PubMaster(['microphone']) From de061eacbe772b1a9be73d4a3864fad6e05037ac Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 2 Dec 2022 02:23:51 -0800 Subject: [PATCH 153/185] soundd: lower max volume breakpoint (#26673) * revert tune * remove hanning window * retune from hanning window * add hanning back * update once reached 4096 * Revert "update once reached 4096" This reverts commit 0898cbeec736916ffaee5e963df048b48636ece5. --- selfdrive/ui/soundd/sound.cc | 2 +- system/micd.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 73d65eb1f7..f039511ff8 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -48,7 +48,7 @@ void Sound::update() { // scale volume with speed if (sm.updated("microphone")) { - float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 55.f, 0.f, 1.f); + float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 52.f, 0.f, 1.f); volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); Hardware::set_volume(volume); } diff --git a/system/micd.py b/system/micd.py index 150dcb9cbe..57700e5927 100755 --- a/system/micd.py +++ b/system/micd.py @@ -26,8 +26,7 @@ def calculate_spl(measurements): def apply_a_weighting(measurements: np.ndarray) -> np.ndarray: # Generate a Hanning window of the same length as the audio measurements - hanning_window = np.hanning(len(measurements)) - measurements_windowed = measurements * hanning_window + measurements_windowed = measurements * np.hanning(len(measurements)) # Calculate the frequency axis for the signal freqs = np.fft.fftfreq(measurements_windowed.size, d=1 / SAMPLE_RATE) From 9cc06e9ea658b74bf1504fa35c6c8729fb1f550f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 2 Dec 2022 11:51:25 -0800 Subject: [PATCH 154/185] boardd: misc spi fixes (#26670) --- selfdrive/boardd/panda.cc | 4 +--- selfdrive/boardd/panda.h | 1 - selfdrive/boardd/panda_comms.h | 3 --- selfdrive/boardd/spi.cc | 25 +++++++++++++++++-------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index db9bcbab51..3e447c830e 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -230,8 +230,6 @@ bool Panda::can_receive(std::vector& out_vec) { } bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec) { - recv_buf.clear(); - if (size < sizeof(uint32_t)) { return true; } @@ -239,7 +237,7 @@ bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &o uint32_t magic; memcpy(&magic, &data[0], sizeof(uint32_t)); if (magic != CAN_TRANSACTION_MAGIC) { - LOGE("CAN: MALFORMED CAN RECV PACKET"); + LOGE("CAN recv: buffer didn't start with magic"); handle->comms_healthy = false; return false; } diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index b20d8f0afa..75fec57a3e 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -43,7 +43,6 @@ struct can_frame { class Panda { private: std::unique_ptr handle; - std::vector recv_buf; public: Panda(std::string serial="", uint32_t bus_offset=0); diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h index f42eadc5b2..bd262dfa0e 100644 --- a/selfdrive/boardd/panda_comms.h +++ b/selfdrive/boardd/panda_comms.h @@ -13,8 +13,6 @@ #define TIMEOUT 0 #define SPI_BUF_SIZE 1024 -const bool PANDA_NO_RETRY = getenv("PANDA_NO_RETRY"); - // comms base class class PandaCommsHandle { @@ -52,7 +50,6 @@ public: private: libusb_context *ctx = NULL; libusb_device_handle *dev_handle = NULL; - std::vector recv_buf; void handle_usb_issue(int err, const char func[]); }; diff --git a/selfdrive/boardd/spi.cc b/selfdrive/boardd/spi.cc index 2803f58db0..717b6ce820 100644 --- a/selfdrive/boardd/spi.cc +++ b/selfdrive/boardd/spi.cc @@ -6,6 +6,7 @@ #include #include "common/util.h" +#include "common/timing.h" #include "common/swaglog.h" #include "panda/board/comms_definitions.h" #include "selfdrive/boardd/panda_comms.h" @@ -24,6 +25,9 @@ struct __attribute__((packed)) spi_header { uint16_t max_rx_len; }; +const int SPI_MAX_RETRIES = 5; +const int SPI_ACK_TIMEOUT = 50; // milliseconds + PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { LOGD("opening SPI panda: %s", serial.c_str()); @@ -109,7 +113,7 @@ int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int l int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len) { std::lock_guard lk(hw_lock); - const int xfer_size = 0x40; + const int xfer_size = 0x40 * 15; int ret = 0; uint16_t length = (tx_data != NULL) ? tx_len : rx_len; @@ -119,7 +123,8 @@ int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t t int len = std::min(xfer_size, tx_len - (xfer_size * i)); d = spi_transfer_retry(endpoint, tx_data + (xfer_size * i), len, NULL, 0); } else { - d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), xfer_size); + uint16_t to_read = std::min(xfer_size, rx_len - ret); + d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), to_read); } if (d < 0) { @@ -137,15 +142,11 @@ int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t t return ret; } - - std::vector PandaSpiHandle::list() { // TODO: list all pandas available over SPI return {}; } - - void add_checksum(uint8_t *data, int data_len) { data[data_len] = SPI_CHECKSUM_START; for (int i=0; i < data_len; i++) { @@ -165,17 +166,19 @@ bool check_checksum(uint8_t *data, int data_len) { int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) { int ret; + int count = SPI_MAX_RETRIES; std::lock_guard lk(hw_lock); do { // TODO: handle error ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len); - } while (ret < 0 && connected && !PANDA_NO_RETRY); + count--; + } while (ret < 0 && connected && count > 0); return ret; } int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) { - // TODO: add timeout? + double start_millis = millis_since_boot(); while (true) { int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); if (ret < 0) { @@ -189,6 +192,12 @@ int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) { LOGW("SPI: got NACK"); return -1; } + + // handle timeout + if (millis_since_boot() - start_millis > SPI_ACK_TIMEOUT) { + LOGE("SPI: timed out waiting for ACK"); + return -1; + } } return 0; From bb8a38a0508c764c8340e7b16968b06a8367ab7a Mon Sep 17 00:00:00 2001 From: Mitchell Goff Date: Fri, 2 Dec 2022 13:14:30 -0800 Subject: [PATCH 155/185] navmodeld (#26665) * Added navmodeld * New nav model: 7c306685-5476-4bd4-ab65-105b01b6bca8/300, feats only * little cleanup * Remove NAV flag * Moved to_kj_array_ptr to commonmodel.h * Switch from decimation to last_frame_id check * add to release files Co-authored-by: Adeeb Shihadeh --- .gitignore | 1 + release/files_common | 10 +++- selfdrive/manager/process_config.py | 3 +- selfdrive/modeld/SConscript | 5 ++ selfdrive/modeld/models/commonmodel.h | 6 +++ selfdrive/modeld/models/driving.cc | 5 -- selfdrive/modeld/models/nav.cc | 66 +++++++++++++++++++++++ selfdrive/modeld/models/nav.h | 73 ++++++++++++++++++++++++++ selfdrive/modeld/models/navmodel.onnx | 3 ++ selfdrive/modeld/models/navmodel_q.dlc | 3 ++ selfdrive/modeld/navmodeld | 12 +++++ selfdrive/modeld/navmodeld.cc | 60 +++++++++++++++++++++ 12 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 selfdrive/modeld/models/nav.cc create mode 100644 selfdrive/modeld/models/nav.h create mode 100644 selfdrive/modeld/models/navmodel.onnx create mode 100644 selfdrive/modeld/models/navmodel_q.dlc create mode 100755 selfdrive/modeld/navmodeld create mode 100644 selfdrive/modeld/navmodeld.cc diff --git a/.gitignore b/.gitignore index 31cef94222..2b283d3b11 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ selfdrive/sensord/_sensord system/camerad/camerad system/camerad/test/ae_gray_test selfdrive/modeld/_modeld +selfdrive/modeld/_navmodeld selfdrive/modeld/_dmonitoringmodeld /src/ diff --git a/release/files_common b/release/files_common index 2f7f2d4d61..297a7a808e 100644 --- a/release/files_common +++ b/release/files_common @@ -350,20 +350,28 @@ selfdrive/manager/test/test_manager.py selfdrive/modeld/__init__.py selfdrive/modeld/SConscript selfdrive/modeld/modeld.cc +selfdrive/modeld/navmodeld.cc selfdrive/modeld/dmonitoringmodeld.cc selfdrive/modeld/constants.py selfdrive/modeld/modeld +selfdrive/modeld/navmodeld selfdrive/modeld/dmonitoringmodeld selfdrive/modeld/models/commonmodel.cc selfdrive/modeld/models/commonmodel.h + selfdrive/modeld/models/driving.cc selfdrive/modeld/models/driving.h +selfdrive/modeld/models/supercombo.onnx + selfdrive/modeld/models/dmonitoring.cc selfdrive/modeld/models/dmonitoring.h -selfdrive/modeld/models/supercombo.onnx selfdrive/modeld/models/dmonitoring_model_q.dlc +selfdrive/modeld/models/nav.cc +selfdrive/modeld/models/nav.h +selfdrive/modeld/models/navmodel_q.dlc + selfdrive/modeld/transforms/loadyuv.cc selfdrive/modeld/transforms/loadyuv.h selfdrive/modeld/transforms/loadyuv.cl diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 50c19610ed..c03e995497 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -32,7 +32,8 @@ procs = [ NativeProcess("encoderd", "selfdrive/loggerd", ["./encoderd"]), NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), - # NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"]), + NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"], enabled=False), + NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"], enabled=False), NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC), NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)), NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)), diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index f1a8d71881..82338e456b 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -112,3 +112,8 @@ llenv.Program('_modeld', [ "modeld.cc", "models/driving.cc", ]+common_model, LIBS=libs + transformations) + +lenv.Program('_navmodeld', [ + "navmodeld.cc", + "models/nav.cc", + ]+common_model, LIBS=libs + transformations) \ No newline at end of file diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index 40c82a8c21..1a079da055 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -13,6 +13,7 @@ #endif #include "common/mat.h" +#include "cereal/messaging/messaging.h" #include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/transform.h" @@ -21,6 +22,11 @@ const bool send_raw_pred = getenv("SEND_RAW_PRED") != NULL; void softmax(const float* input, float* output, size_t len); float sigmoid(float input); +template +constexpr const kj::ArrayPtr to_kj_array_ptr(const std::array &arr) { + return kj::ArrayPtr(arr.data(), arr.size()); +} + class ModelFrame { public: ModelFrame(cl_device_id device_id, cl_context context); diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index 4015731c42..9cb216ff19 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -22,11 +22,6 @@ std::array prev_brake_3ms2_probs = {0,0,0}; // #define DUMP_YUV -template -constexpr const kj::ArrayPtr to_kj_array_ptr(const std::array &arr) { - return kj::ArrayPtr(arr.data(), arr.size()); -} - void model_init(ModelState* s, cl_device_id device_id, cl_context context) { s->frame = new ModelFrame(device_id, context); s->wide_frame = new ModelFrame(device_id, context); diff --git a/selfdrive/modeld/models/nav.cc b/selfdrive/modeld/models/nav.cc new file mode 100644 index 0000000000..dae87c7ce5 --- /dev/null +++ b/selfdrive/modeld/models/nav.cc @@ -0,0 +1,66 @@ +#include "selfdrive/modeld/models/nav.h" + +#include +#include + +#include "common/mat.h" +#include "common/modeldata.h" +#include "common/timing.h" + + +void navmodel_init(NavModelState* s) { +#ifdef USE_ONNX_MODEL + s->m = new ONNXModel("models/navmodel.onnx", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); +#else + s->m = new SNPEModel("models/navmodel_q.dlc", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); +#endif +} + +NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf) { + memcpy(s->net_input_buf, buf->addr, NAV_INPUT_SIZE); + + double t1 = millis_since_boot(); + s->m->addImage((float*)s->net_input_buf, NAV_INPUT_SIZE/sizeof(float)); + s->m->execute(); + double t2 = millis_since_boot(); + + NavModelResult *model_res = (NavModelResult*)&s->output; + model_res->dsp_execution_time = (t2 - t1) / 1000.; + return model_res; +} + +void fill_plan(cereal::NavModelData::Builder &framed, const NavModelOutputPlan &plan) { + std::array pos_x, pos_y; + std::array pos_x_std, pos_y_std; + + for (int i=0; im; +} diff --git a/selfdrive/modeld/models/nav.h b/selfdrive/modeld/models/nav.h new file mode 100644 index 0000000000..b469f75987 --- /dev/null +++ b/selfdrive/modeld/models/nav.h @@ -0,0 +1,73 @@ +#pragma once + +#include "cereal/messaging/messaging.h" +#include "cereal/visionipc/visionipc_client.h" +#include "common/util.h" +#include "common/modeldata.h" +#include "selfdrive/modeld/models/commonmodel.h" +#include "selfdrive/modeld/runners/run.h" + +constexpr int NAV_INPUT_SIZE = 256*256; +constexpr int NAV_FEATURE_LEN = 64; +constexpr int NAV_DESIRE_LEN = 32; +constexpr int NAV_PLAN_MHP_N = 5; + +struct NavModelOutputXY { + float x; + float y; +}; +static_assert(sizeof(NavModelOutputXY) == sizeof(float)*2); + +struct NavModelOutputPlan { + std::array mean; + std::array std; + float prob; +}; +static_assert(sizeof(NavModelOutputPlan) == sizeof(NavModelOutputXY)*TRAJECTORY_SIZE*2 + sizeof(float)); + +struct NavModelOutputPlans { + std::array predictions; + + constexpr const NavModelOutputPlan &get_best_prediction() const { + int max_idx = 0; + for (int i = 1; i < predictions.size(); i++) { + if (predictions[i].prob > predictions[max_idx].prob) { + max_idx = i; + } + } + return predictions[max_idx]; + } +}; +static_assert(sizeof(NavModelOutputPlans) == sizeof(NavModelOutputPlan)*NAV_PLAN_MHP_N); + +struct NavModelOutputDesirePrediction { + std::array values; +}; +static_assert(sizeof(NavModelOutputDesirePrediction) == sizeof(float)*NAV_DESIRE_LEN); + +struct NavModelOutputFeatures { + std::array values; +}; +static_assert(sizeof(NavModelOutputFeatures) == sizeof(float)*NAV_FEATURE_LEN); + +struct NavModelResult { + const NavModelOutputPlans plans; + const NavModelOutputDesirePrediction desire_pred; + const NavModelOutputFeatures features; + float dsp_execution_time; +}; +static_assert(sizeof(NavModelResult) == sizeof(NavModelOutputPlans) + sizeof(NavModelOutputDesirePrediction) + sizeof(NavModelOutputFeatures) + sizeof(float)); + +constexpr int NAV_OUTPUT_SIZE = sizeof(NavModelResult) / sizeof(float); +constexpr int NAV_NET_OUTPUT_SIZE = NAV_OUTPUT_SIZE - 1; + +struct NavModelState { + RunModel *m; + uint8_t net_input_buf[NAV_INPUT_SIZE]; + float output[NAV_OUTPUT_SIZE]; +}; + +void navmodel_init(NavModelState* s); +NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf); +void navmodel_publish(PubMaster &pm, uint32_t frame_id, const NavModelResult &model_res, float execution_time); +void navmodel_free(NavModelState* s); diff --git a/selfdrive/modeld/models/navmodel.onnx b/selfdrive/modeld/models/navmodel.onnx new file mode 100644 index 0000000000..60dd8c0e7f --- /dev/null +++ b/selfdrive/modeld/models/navmodel.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eab4b986e14d7d842d6d5487011c329d356fb56995b2ae7dc7188aefe6df9d97 +size 12285002 diff --git a/selfdrive/modeld/models/navmodel_q.dlc b/selfdrive/modeld/models/navmodel_q.dlc new file mode 100644 index 0000000000..7d9b36ed4d --- /dev/null +++ b/selfdrive/modeld/models/navmodel_q.dlc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83d53efc40053b02fe7d3da4ef6213a4a5a1ae4d1bd49c121b9beb6a54ea1148 +size 3154868 diff --git a/selfdrive/modeld/navmodeld b/selfdrive/modeld/navmodeld new file mode 100755 index 0000000000..079afd9677 --- /dev/null +++ b/selfdrive/modeld/navmodeld @@ -0,0 +1,12 @@ +#!/bin/sh + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +if [ -f /TICI ]; then + export LD_LIBRARY_PATH="/usr/lib/aarch64-linux-gnu:/data/pythonpath/third_party/snpe/larch64:$LD_LIBRARY_PATH" + export ADSP_LIBRARY_PATH="/data/pythonpath/third_party/snpe/dsp/" +else + export LD_LIBRARY_PATH="$DIR/../../third_party/snpe/x86_64-linux-clang:$DIR/../../openpilot/third_party/snpe/x86_64:$LD_LIBRARY_PATH" +fi +exec ./_navmodeld diff --git a/selfdrive/modeld/navmodeld.cc b/selfdrive/modeld/navmodeld.cc new file mode 100644 index 0000000000..57ef7cf0e0 --- /dev/null +++ b/selfdrive/modeld/navmodeld.cc @@ -0,0 +1,60 @@ +#include +#include + +#include +#include + +#include "cereal/visionipc/visionipc_client.h" +#include "common/swaglog.h" +#include "common/util.h" +#include "selfdrive/modeld/models/nav.h" + +ExitHandler do_exit; + +void run_model(NavModelState &model, VisionIpcClient &vipc_client) { + PubMaster pm({"navModel"}); + + double last_ts = 0; + uint32_t last_frame_id = 0; + VisionIpcBufExtra extra = {}; + + while (!do_exit) { + VisionBuf *buf = vipc_client.recv(&extra); + if (buf == nullptr) continue; + if (extra.frame_id < last_frame_id + 10) continue; // Run at 2Hz + + double t1 = millis_since_boot(); + NavModelResult *model_res = navmodel_eval_frame(&model, buf); + double t2 = millis_since_boot(); + + // send navmodel packet + navmodel_publish(pm, extra.frame_id, *model_res, (t2 - t1) / 1000.0); + + //printf("navmodel process: %.2fms, from last %.2fms\n", t2 - t1, t1 - last_ts); + last_ts = t1; + last_frame_id = extra.frame_id; + } +} + +int main(int argc, char **argv) { + setpriority(PRIO_PROCESS, 0, -15); + + // init the models + NavModelState model; + navmodel_init(&model); + LOGW("models loaded, navmodeld starting"); + + VisionIpcClient vipc_client = VisionIpcClient("navd", VISION_STREAM_MAP, true); + while (!do_exit && !vipc_client.connect(false)) { + util::sleep_for(100); + } + + // run the models + if (vipc_client.connected) { + LOGW("connected with buffer size: %d", vipc_client.buffers[0].len); + run_model(model, vipc_client); + } + + navmodel_free(&model); + return 0; +} From daea877c07a51d82903880e9464eace38ef9a95d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 3 Dec 2022 05:15:14 +0800 Subject: [PATCH 156/185] Cabana: add shortcuts (#26528) add shortcuts --- tools/cabana/detailwidget.cc | 4 ++-- tools/cabana/mainwin.cc | 11 ++++++++++- tools/cabana/mainwin.h | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index c20e2b672e..5035bf49b4 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -85,9 +85,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart tab_widget = new QTabWidget(this); tab_widget->setTabPosition(QTabWidget::South); - tab_widget->addTab(scroll, "Msg"); + tab_widget->addTab(scroll, "&Msg"); history_log = new HistoryLog(this); - tab_widget->addTab(history_log, "Logs"); + tab_widget->addTab(history_log, "&Logs"); main_layout->addWidget(tab_widget); QObject::connect(binary_view, &BinaryView::signalClicked, this, &DetailWidget::showForm); diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 324323ac44..6a298bc228 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ MainWindow::MainWindow() : QMainWindow() { setCentralWidget(central_widget); createActions(); createStatusBar(); + createShortcuts(); qRegisterMetaType("uint64_t"); qRegisterMetaType("ReplyMsgType"); @@ -156,6 +158,12 @@ void MainWindow::createStatusBar() { statusBar()->addPermanentWidget(progress_bar); } +void MainWindow::createShortcuts() { + auto shortcut = new QShortcut(QKeySequence(Qt::Key_Space), this, nullptr, nullptr, Qt::ApplicationShortcut); + QObject::connect(shortcut, &QShortcut::activated, []() { can->pause(!can->isPaused()); }); + // TODO: add more shortcuts here. +} + void MainWindow::loadDBCFromName(const QString &name) { if (name != dbc()->name()) dbc()->open(name); @@ -225,7 +233,8 @@ void MainWindow::dockCharts(bool dock) { floating_window->deleteLater(); floating_window = nullptr; } else if (!dock && !floating_window) { - floating_window = new QWidget(nullptr); + floating_window = new QWidget(this); + floating_window->setWindowFlags(Qt::Window); floating_window->setWindowTitle("Charts - Cabana"); floating_window->setLayout(new QVBoxLayout()); floating_window->layout()->addWidget(charts_widget); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index bb9280c3ea..5d377c44ca 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -35,6 +35,7 @@ signals: protected: void createActions(); void createStatusBar(); + void createShortcuts(); void closeEvent(QCloseEvent *event) override; void updateDownloadProgress(uint64_t cur, uint64_t total, bool success); void setOption(); From 59ed74ae4a7a51fcfa673393947b755d7b29c323 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 3 Dec 2022 05:15:32 +0800 Subject: [PATCH 157/185] Cabanba: new dialog to manage chart series (#26675) * New dialog to manage chart series * connect to signal currentIndexChanged * cleanup use cbegin/cend --- tools/cabana/chartswidget.cc | 133 ++++++++++++++++++++++++++++++++++- tools/cabana/chartswidget.h | 23 ++++++ tools/cabana/dbcmanager.h | 1 + 3 files changed, 156 insertions(+), 1 deletion(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index a3faab0a20..79b40133a5 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -196,6 +196,14 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { close_btn_proxy->setWidget(remove_btn); close_btn_proxy->setZValue(chart->zValue() + 11); + QToolButton *manage_btn = new QToolButton(); + manage_btn->setText("🔧"); + manage_btn->setAutoRaise(true); + manage_btn->setToolTip(tr("Manage series")); + manage_btn_proxy = new QGraphicsProxyWidget(chart); + manage_btn_proxy->setWidget(manage_btn); + manage_btn_proxy->setZValue(chart->zValue() + 11); + setChart(chart); setRenderHint(QPainter::Antialiasing); setRubberBand(QChartView::HorizontalRubberBand); @@ -207,6 +215,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated); QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings); QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); + QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries); } ChartView::~ChartView() { @@ -278,9 +287,41 @@ void ChartView::msgRemoved(uint32_t address) { } } +void ChartView::manageSeries() { + SeriesSelector dlg(this); + for (auto &s : sigs) { + dlg.addSeries(s.msg_id, msgName(s.msg_id), QString::fromStdString(s.sig->name)); + } + + int ret = dlg.exec(); + if (ret == QDialog::Accepted) { + QList series_list = dlg.series(); + if (series_list.isEmpty()) { + emit remove(); + } else { + for (auto &s : series_list) { + if (auto m = dbc()->msg(s[0])) { + auto it = m->sigs.find(s[2]); + if (it != m->sigs.end() && !hasSeries(s[0], &(it->second))) { + addSeries(s[0], &(it->second)); + } + } + } + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + bool exists = std::any_of(series_list.cbegin(), series_list.cend(), [&](auto &s) { + return s[0] == it->msg_id && s[2] == it->sig->name.c_str(); + }); + it = exists ? ++it : removeSeries(it); + } + } + } +} + void ChartView::resizeEvent(QResizeEvent *event) { QChartView::resizeEvent(event); - close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8); + int x = event->size().width() - close_btn_proxy->size().width() - 11; + close_btn_proxy->setPos(x, 8); + manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - 5, 8); } void ChartView::updateTitle() { @@ -462,3 +503,93 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { painter->setPen(QPen(chart()->titleBrush().color(), 2)); painter->drawLine(QPointF{x, chart()->plotArea().top() - 2}, QPointF{x, chart()->plotArea().bottom() + 2}); } + +// SeriesSelector + +SeriesSelector::SeriesSelector(QWidget *parent) { + setWindowTitle(tr("Manage Chart Series")); + QHBoxLayout *contents_layout = new QHBoxLayout(); + + QVBoxLayout *left_layout = new QVBoxLayout(); + left_layout->addWidget(new QLabel(tr("Select Signals:"))); + msgs_combo = new QComboBox(this); + left_layout->addWidget(msgs_combo); + sig_list = new QListWidget(this); + sig_list->setSortingEnabled(true); + sig_list->setToolTip(tr("Double click on an item to add signal to chart")); + left_layout->addWidget(sig_list); + + QVBoxLayout *right_layout = new QVBoxLayout(); + right_layout->addWidget(new QLabel(tr("Chart Signals:"))); + chart_series = new QListWidget(this); + chart_series->setSortingEnabled(true); + chart_series->setToolTip(tr("Double click on an item to remove signal from chart")); + right_layout->addWidget(chart_series); + contents_layout->addLayout(left_layout); + contents_layout->addLayout(right_layout); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->addLayout(contents_layout); + main_layout->addWidget(buttonBox); + + for (auto it = can->can_msgs.cbegin(); it != can->can_msgs.cend(); ++it) { + if (auto m = dbc()->msg(it.key())) { + msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key()), it.key()); + } + } + msgs_combo->model()->sort(0); + + QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + QObject::connect(msgs_combo, SIGNAL(currentIndexChanged(int)), SLOT(msgSelected(int))); + QObject::connect(sig_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::addSignal); + QObject::connect(chart_series, &QListWidget::itemDoubleClicked, [](QListWidgetItem *item) { delete item; }); + + if (int index = msgs_combo->currentIndex(); index >= 0) { + msgSelected(index); + } +} + +void SeriesSelector::msgSelected(int index) { + QString msg_id = msgs_combo->itemData(index).toString(); + sig_list->clear(); + if (auto m = dbc()->msg(msg_id)) { + for (auto &[name, s] : m->sigs) { + QStringList data({msg_id, m->name, name}); + QListWidgetItem *item = new QListWidgetItem(name, sig_list); + item->setData(Qt::UserRole, data); + sig_list->addItem(item); + } + } +} + +void SeriesSelector::addSignal(QListWidgetItem *item) { + QStringList data = item->data(Qt::UserRole).toStringList(); + addSeries(data[0], data[1], data[2]); +} + +void SeriesSelector::addSeries(const QString &id, const QString& msg_name, const QString &sig_name) { + QStringList data({id, msg_name, sig_name}); + for (int i = 0; i < chart_series->count(); ++i) { + if (chart_series->item(i)->data(Qt::UserRole).toStringList() == data) { + return; + } + } + QListWidgetItem *new_item = new QListWidgetItem(chart_series); + new_item->setData(Qt::UserRole, data); + chart_series->addItem(new_item); + QLabel *label = new QLabel(QString("%0 %1 %2").arg(data[2]).arg(data[1]).arg(data[0]), chart_series); + label->setContentsMargins(5, 0, 5, 0); + new_item->setSizeHint(label->sizeHint()); + chart_series->setItemWidget(new_item, label); +} + +QList SeriesSelector::series() { + QList ret; + for (int i = 0; i < chart_series->count(); ++i) { + ret.push_back(chart_series->item(i)->data(Qt::UserRole).toStringList()); + } + return ret; +} diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index c3fa931e6e..3e3277c5b8 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -1,6 +1,9 @@ #pragma once +#include +#include #include +#include #include #include #include @@ -50,6 +53,7 @@ private slots: void msgUpdated(uint32_t address); void signalUpdated(const Signal *sig); void signalRemoved(const Signal *sig); + void manageSeries(); private: QList::iterator removeSeries(const QList::iterator &it); @@ -70,6 +74,7 @@ private: QGraphicsEllipseItem *track_ellipse; QGraphicsTextItem *value_text; QGraphicsProxyWidget *close_btn_proxy; + QGraphicsProxyWidget *manage_btn_proxy; std::pair events_range = {0, 0}; QList sigs; }; @@ -114,3 +119,21 @@ private: std::pair zoomed_range; bool use_dark_theme = false; }; + +class SeriesSelector : public QDialog { + Q_OBJECT + +public: + SeriesSelector(QWidget *parent); + void addSeries(const QString &id, const QString& msg_name, const QString &sig_name); + QList series(); + +private slots: + void msgSelected(int index); + void addSignal(QListWidgetItem *item); + +private: + QComboBox *msgs_combo; + QListWidget *sig_list; + QListWidget *chart_series; +}; diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 4e0bc91069..c7675121bb 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -28,6 +28,7 @@ public: static std::pair parseId(const QString &id); inline static std::vector allDBCNames() { return get_dbc_names(); } + inline std::map &allMsgs() { return msgs; } inline QString name() const { return dbc ? dbc->name.c_str() : ""; } void updateMsg(const QString &id, const QString &name, uint32_t size); From c0d545523e48464acdd6a356d505e0e81266582d Mon Sep 17 00:00:00 2001 From: Erich Moraga <33645296+ErichMoraga@users.noreply.github.com> Date: Fri, 2 Dec 2022 15:36:48 -0600 Subject: [PATCH 158/185] Add missing COROLLA_TSS2 EPS, ABS, and engine f/w (#26669) * Add missing COROLLA_TSS2 EPS, ABS, and engine f/w. `@Yin#7979` 2020 Lexus UX 200 DongleID/route ced26c79125dd3e3|2022-12-02--06-03-06 * revert Co-authored-by: Cameron Clough --- selfdrive/car/toyota/values.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index e112ebdb80..98e1f0e99d 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -551,12 +551,10 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F152633D00\x00\x00\x00\x00\x00\x00', b'F152633D60\x00\x00\x00\x00\x00\x00', - b'F152633310\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', b'\x018966306Q7000\x00\x00\x00\x00', - b'\x018966306V1000\x00\x00\x00\x00', b'\x01896633T20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 15): [ @@ -567,7 +565,6 @@ FW_VERSIONS = { b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CHR: { @@ -748,6 +745,7 @@ FW_VERSIONS = { b'\x018966312S7000\x00\x00\x00\x00', b'\x018966312W3000\x00\x00\x00\x00', b'\x018966312W9000\x00\x00\x00\x00', + b'\x01896637644000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0230A10000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -775,6 +773,7 @@ FW_VERSIONS = { b'\x018965B1255000\x00\x00\x00\x00', b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B16011\x00\x00\x00\x00\x00\x00', + b'8965B76012\x00\x00\x00\x00\x00\x00', b'\x018965B12510\x00\x00\x00\x00\x00\x00', b'\x018965B1256000\x00\x00\x00\x00', ], @@ -800,6 +799,7 @@ FW_VERSIONS = { b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612B91\x00\x00\x00\x00\x00\x00', b'\x01F15260A070\x00\x00\x00\x00\x00\x00', + b'\x01F152676250\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', From 060b69ee2f5148140ec696cbc950b83f700fd671 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 2 Dec 2022 15:25:34 -0800 Subject: [PATCH 159/185] micd: revert check playing sound (high cpu usage) (#26672) * don't use hardware * check micd proc * use pactl package * cleanup * Revert "cleanup" This reverts commit baf9887e2d3e7dce8c24a93e970bb5a2d3609d50. * Revert "use pactl package" This reverts commit 0c1f3a4b865e44052affa57323ae4a21d274d6e3. * Revert "micd: don't update filtered sound level if playing sound (#26652)" This reverts commit 86cd919a57be22fa0ccf324a8767999309df60e4. * Revert "check micd proc" This reverts commit 9ebbe2aa42bdfd2f7f8bf226978a518d984fb154. Co-authored-by: Cameron Clough --- system/hardware/base.py | 4 ---- system/hardware/pc/hardware.py | 4 ---- system/hardware/tici/hardware.py | 3 --- system/micd.py | 4 +--- 4 files changed, 1 insertion(+), 14 deletions(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index 16ed9621c1..31df1babe0 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -43,10 +43,6 @@ class HardwareBase(ABC): def get_sound_card_online(self): pass - @abstractmethod - def is_sound_playing(self): - pass - @abstractmethod def get_imei(self, slot) -> str: pass diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 2c83eb35f4..564f9e483a 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -1,5 +1,4 @@ import random -import subprocess from cereal import log from system.hardware.base import HardwareBase, ThermalConfig @@ -18,9 +17,6 @@ class Pc(HardwareBase): def get_sound_card_online(self): return True - def is_sound_playing(self): - return "RUNNING" in subprocess.check_output(["pactl", "list", "short", "sinks"]).decode('utf8') - def reboot(self, reason=None): print("REBOOT!") diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 8371d6ef70..b5f5e00410 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -96,9 +96,6 @@ class Tici(HardwareBase): return (os.path.isfile('/proc/asound/card0/state') and open('/proc/asound/card0/state').read().strip() == 'ONLINE') - def is_sound_playing(self): - return "RUNNING" in subprocess.check_output(["pactl", "list", "short", "sinks"]).decode('utf8') - def reboot(self, reason=None): subprocess.check_output(["sudo", "reboot"]) diff --git a/system/micd.py b/system/micd.py index 57700e5927..d0c661ee0d 100755 --- a/system/micd.py +++ b/system/micd.py @@ -5,7 +5,6 @@ import numpy as np from cereal import messaging from common.filter_simple import FirstOrderFilter from common.realtime import Ratekeeper -from system.hardware import HARDWARE from system.swaglog import cloudlog RATE = 10 @@ -60,8 +59,7 @@ class Mic: sound_pressure, _ = calculate_spl(self.measurements) measurements_weighted = apply_a_weighting(self.measurements) sound_pressure_weighted, sound_pressure_level_weighted = calculate_spl(measurements_weighted) - if not HARDWARE.is_sound_playing(): - self.spl_filter_weighted.update(sound_pressure_level_weighted) + self.spl_filter_weighted.update(sound_pressure_level_weighted) else: sound_pressure = 0 sound_pressure_weighted = 0 From 10085d1e3f61b472c4f25cd3e98d5ee83b40d4eb Mon Sep 17 00:00:00 2001 From: martinl Date: Sat, 3 Dec 2022 01:35:44 +0200 Subject: [PATCH 160/185] Subaru: add missing fw values for 2023 Crosstrek Limited (#26678) * Add FPv2: 2023 Crosstrek Limited / @JWynegar * update docs Co-authored-by: Shane Smiskol --- docs/CARS.md | 2 +- selfdrive/car/subaru/values.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index df0dda742c..79e176d4d4 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -138,7 +138,7 @@ A supported vehicle is one that just works when you install a comma three. All s |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Forester 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Impreza 2017-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Impreza 2020-22|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 9975e495dd..6ac2637fa2 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -63,7 +63,7 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { ], CAR.IMPREZA_2020: [ SubaruCarInfo("Subaru Impreza 2020-22"), - SubaruCarInfo("Subaru Crosstrek 2020-21"), + SubaruCarInfo("Subaru Crosstrek 2020-23"), SubaruCarInfo("Subaru XV 2020-21"), ], CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"), @@ -235,12 +235,14 @@ FW_VERSIONS = { b'\x9a\xc0\000\000', b'\n\xc0\004\000', b'\x9a\xc0\x04\x00', + b'\n\xc0\x04\x01', ], (Ecu.fwdCamera, 0x787, None): [ b'\000\000eb\037@ \"', b'\000\000e\x8f\037@ )', b'\x00\x00eq\x1f@ "', b'\x00\x00eq\x00\x00\x00\x00', + b'\x00\x00e\x8f\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xca!ap\a', @@ -250,6 +252,7 @@ FW_VERSIONS = { b'\xcc!fp\a', b'\xca!f@\x07', b'\xca!fp\x07', + b'\xf3"f@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xe6\xf5\004\000\000', @@ -258,6 +261,7 @@ FW_VERSIONS = { b'\xe7\xf5D0\000', b'\xf1\x00\xd7\x10@', b'\xe6\xf5D0\x00', + b'\xe9\xf6F0\x00', ], }, CAR.FORESTER: { From 20dba2f33d870d81347ff7045911797a286c9128 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 2 Dec 2022 18:38:04 -0800 Subject: [PATCH 161/185] Micd: update sound levels in callback (#26674) * update once reached 4096 * update once reached 4096 * reduce * debug & cmt * fix * fifo again * fix * clean that up * update filter on demand Co-authored-by: Cameron Clough --- system/micd.py | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/system/micd.py b/system/micd.py index d0c661ee0d..a56140e3b9 100755 --- a/system/micd.py +++ b/system/micd.py @@ -8,9 +8,10 @@ from common.realtime import Ratekeeper from system.swaglog import cloudlog RATE = 10 -DT_MIC = 1. / RATE +FFT_SAMPLES = 4096 REFERENCE_SPL = 2e-5 # newtons/m^2 SAMPLE_RATE = 44100 +FILTER_DT = 1. / (SAMPLE_RATE / FFT_SAMPLES) def calculate_spl(measurements): @@ -45,41 +46,44 @@ class Mic: self.rk = Ratekeeper(RATE) self.measurements = np.empty(0) - self.spl_filter_weighted = FirstOrderFilter(0, 2.5, DT_MIC, initialized=False) - def update(self): - """ - Using amplitude measurements, calculate an uncalibrated sound pressure and sound pressure level. - Then apply A-weighting to the raw amplitudes and run the same calculations again. - - Logged A-weighted equivalents are rough approximations of the human-perceived loudness. - """ + self.sound_pressure = 0 + self.sound_pressure_weighted = 0 + self.sound_pressure_level_weighted = 0 - if len(self.measurements) > 0: - sound_pressure, _ = calculate_spl(self.measurements) - measurements_weighted = apply_a_weighting(self.measurements) - sound_pressure_weighted, sound_pressure_level_weighted = calculate_spl(measurements_weighted) - self.spl_filter_weighted.update(sound_pressure_level_weighted) - else: - sound_pressure = 0 - sound_pressure_weighted = 0 - sound_pressure_level_weighted = 0 - - self.measurements = np.empty(0) + self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) + def update(self): msg = messaging.new_message('microphone') - msg.microphone.soundPressure = float(sound_pressure) - msg.microphone.soundPressureWeighted = float(sound_pressure_weighted) + msg.microphone.soundPressure = float(self.sound_pressure) + msg.microphone.soundPressureWeighted = float(self.sound_pressure_weighted) - msg.microphone.soundPressureWeightedDb = float(sound_pressure_level_weighted) + msg.microphone.soundPressureWeightedDb = float(self.sound_pressure_level_weighted) msg.microphone.filteredSoundPressureWeightedDb = float(self.spl_filter_weighted.x) self.pm.send('microphone', msg) self.rk.keep_time() def callback(self, indata, frames, time, status): + """ + Using amplitude measurements, calculate an uncalibrated sound pressure and sound pressure level. + Then apply A-weighting to the raw amplitudes and run the same calculations again. + + Logged A-weighted equivalents are rough approximations of the human-perceived loudness. + """ + self.measurements = np.concatenate((self.measurements, indata[:, 0])) + while self.measurements.size >= FFT_SAMPLES: + measurements = self.measurements[:FFT_SAMPLES] + + self.sound_pressure, _ = calculate_spl(measurements) + measurements_weighted = apply_a_weighting(measurements) + self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted) + self.spl_filter_weighted.update(self.sound_pressure_level_weighted) + + self.measurements = self.measurements[FFT_SAMPLES:] + def micd_thread(self, device=None): if device is None: device = "sysdefault" From 92296ce55d7e910c809658ad8f7eeaab6ded875c Mon Sep 17 00:00:00 2001 From: Lee Jong Mun <43285072+crwusiz@users.noreply.github.com> Date: Sat, 3 Dec 2022 11:53:50 +0900 Subject: [PATCH 162/185] remove unused space (#26680) --- selfdrive/ui/qt/onroad.cc | 2 +- selfdrive/ui/ui.cc | 3 +-- selfdrive/ui/ui.h | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 50f891dd56..fed59ef123 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -204,7 +204,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { v_ego_cluster_seen = true; } float cur_speed = cs_alive ? std::max(0.0, v_ego) : 0.0; - cur_speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; + cur_speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; auto speed_limit_sign = sm["navInstruction"].getNavInstruction().getSpeedLimitSign(); float speed_limit = nav_alive ? sm["navInstruction"].getNavInstruction().getSpeedLimit() : 0.0; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 2d4533afe1..c62d737481 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -55,9 +55,8 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con } void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line, - float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - QPolygonF left_points, right_points; left_points.reserve(max_idx + 1); right_points.reserve(max_idx + 1); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index d6f5c3e2e0..9e1c54948b 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -20,7 +20,7 @@ const int bdr_s = 30; const int header_h = 420; const int footer_h = 280; -const int UI_FREQ = 20; // Hz +const int UI_FREQ = 20; // Hz typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; @@ -78,7 +78,7 @@ typedef enum UIStatus { } UIStatus; const QColor bg_colors [] = { - [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), + [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), [STATUS_WARNING] = QColor(0xDA, 0x6F, 0x25, 0xf1), @@ -152,7 +152,6 @@ private: UIState *uiState(); // device management class - class Device : public QObject { Q_OBJECT From ea857b0f71c5bb981b209ac35e63fcbdb1ecf5d4 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 2 Dec 2022 22:26:04 -0500 Subject: [PATCH 163/185] VW MQB: Engage at standstill with OP longitudinal (#26656) * VW MQB: Allow engage at standstill * retry CI * Update selfdrive/car/volkswagen/interface.py Co-authored-by: Shane Smiskol * Revert "Update selfdrive/car/volkswagen/interface.py" This reverts commit 14c7412bf74b23ae88c05cad40aafd4605a63c29. Co-authored-by: Shane Smiskol --- selfdrive/car/volkswagen/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index d90a36ed3e..da0ce25afa 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -230,7 +230,7 @@ class CarInterface(CarInterfaceBase): events.add(EventName.belowSteerSpeed) if self.CS.CP.openpilotLongitudinalControl: - if ret.vEgo < self.CP.minEnableSpeed + 2.: + if ret.vEgo < self.CP.minEnableSpeed + 0.5: events.add(EventName.belowEngageSpeed) if c.enabled and ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) From 3e2a49c6e5a526c7af5667ea7bd62f91b20ab0a9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 2 Dec 2022 22:09:42 -0800 Subject: [PATCH 164/185] GM: add Bolt EV test route (#26681) * bolt ev! * releases * it's a trim --- RELEASES.md | 1 + docs/CARS.md | 3 ++- selfdrive/car/gm/interface.py | 4 ++-- selfdrive/car/gm/values.py | 11 ++++++----- selfdrive/car/tests/routes.py | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 7b51e6dff3..c41b3ed0f7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,6 +3,7 @@ Version 0.9.1 (2022-12-XX) * Adjust alert volume using ambient noise level * Removed driver monitoring timer resetting on interaction if face detected and distracted * Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! +* Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! Version 0.9.0 (2022-11-21) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index 79e176d4d4..30411adc23 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 216 Supported Cars +# 217 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -19,6 +19,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| +|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 7d38c60900..195df36a7f 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -97,7 +97,7 @@ class CarInterface(CarInterfaceBase): # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX, CAR.BOLT_EV} + ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX} # Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below. # Some GMs need some tolerance above 10 kph to avoid a fault @@ -170,7 +170,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.000045 tire_stiffness_factor = 1.0 - elif candidate in (CAR.BOLT_EV, CAR.BOLT_EUV): + elif candidate == CAR.BOLT_EUV: ret.mass = 1669. + STD_CARGO_KG ret.wheelbase = 2.63779 ret.steerRatio = 16.8 diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 0a8cdc6dbb..84fa36a994 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -68,7 +68,6 @@ class CAR: ACADIA = "GMC ACADIA DENALI 2018" BUICK_REGAL = "BUICK REGAL ESSENCE 2018" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" - BOLT_EV = "CHEVROLET BOLT EV 2022" BOLT_EUV = "CHEVROLET BOLT EUV 2022" SILVERADO = "CHEVROLET SILVERADO 1500 2020" EQUINOX = "CHEVROLET EQUINOX 2019" @@ -101,8 +100,10 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), - CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23"), - CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"), + CAR.BOLT_EUV: [ + GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"), + GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"), + ], CAR.SILVERADO: [ GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"), GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE"), @@ -193,9 +194,9 @@ FINGERPRINTS = { DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) -EV_CAR = {CAR.VOLT, CAR.BOLT_EV, CAR.BOLT_EUV} +EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} # We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) -CAMERA_ACC_CAR = {CAR.BOLT_EV, CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX} +CAMERA_ACC_CAR = {CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX} STEER_THRESHOLD = 1.0 diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index da19e1f367..a20616de43 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -22,7 +22,6 @@ non_tested_cars = [ GM.HOLDEN_ASTRA, GM.MALIBU, GM.EQUINOX, - GM.BOLT_EV, HYUNDAI.GENESIS_G90, HYUNDAI.KIA_OPTIMA_H, HONDA.ODYSSEY_CHN, @@ -51,6 +50,7 @@ routes = [ CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV), CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1), + CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.BOLT_EUV, segment=14), # Bolt EV CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.SILVERADO), CarTestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G), From 122c3143589418b74dd6b658607ac5c8d43fb4af Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 2 Dec 2022 23:46:26 -0800 Subject: [PATCH 165/185] Hyundai: setup alt steering limits (#26685) * Hyundai: setup alt steering limits * value * bump panda --- panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 3 --- selfdrive/car/hyundai/interface.py | 4 ++++ selfdrive/car/hyundai/values.py | 8 ++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/panda b/panda index 616450c525..4edd1a6021 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 616450c525a7c1ef508f70f30e29547a606139aa +Subproject commit 4edd1a602131ec2f09a604a4bd28e7d00e334458 diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index e5fdbfd57a..3f128b1598 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -60,9 +60,6 @@ class CarController: # steering torque steer = actuators.steer - if self.CP.carFingerprint in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): - # these cars have significantly more torque than most HKG; limit to 70% of max - steer = clip(steer, -0.7, 0.7) new_steer = int(round(steer * self.params.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index cb4d9a9688..7b157d048b 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -267,6 +267,10 @@ class CarInterface(CarInterfaceBase): elif candidate in EV_CAR: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS + if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): + ret.flags |= HyundaiFlags.ALT_LIMITS.value + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_ALT_LIMITS + ret.centerToFront = ret.wheelbase * 0.4 # TODO: start from empirically derived lateral slip stiffness for the civic and scale by diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 7ecb07e7d7..e5f180a510 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -39,6 +39,12 @@ class CarControllerParams: CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO): self.STEER_MAX = 255 + # these cars have significantly more torque than most HKG; limit to 70% of max + elif CP.flags & HyundaiFlags.ALT_LIMITS: + self.STEER_MAX = 270 + self.STEER_DELTA_UP = 2 + self.STEER_DELTA_DOWN = 3 + # Default for most HKG else: self.STEER_MAX = 384 @@ -50,6 +56,8 @@ class HyundaiFlags(IntFlag): CANFD_ALT_GEARS = 4 CANFD_CAMERA_SCC = 8 + ALT_LIMITS = 16 + class CAR: # Hyundai From d3a66015ad99c54b03bc3693857fbea825122208 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 3 Dec 2022 02:05:24 -0800 Subject: [PATCH 166/185] Toyota: log EPS fault from LKA cmd message drop out (#26687) * log additional faults * bump opendbc --- opendbc | 2 +- selfdrive/car/toyota/carstate.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/opendbc b/opendbc index 3737675445..94fff4782b 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 373767544559718d74bff58dd23890eea1d38a29 +Subproject commit 94fff4782be263efad10032a612b3c96a120c0b7 diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index dbbb8a6f04..250c6b65ae 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -85,10 +85,12 @@ class CarState(CarStateBase): ret.steeringTorqueEps = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_EPS"] * self.eps_torque_scale # we could use the override bit from dbc, but it's triggered at too high torque values ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - # steer rate fault, goes to 21 or 25 for 1 frame, then 9 for ~2 seconds - ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 21, 25) + # steer rate fault: goes to 21 or 25 for 1 frame, then 9 for 2 seconds + # lka msg drop out: goes to 9 then 11 for a combined total of 2 seconds + ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 11, 21, 25) # 17 is a fault from a prolonged high torque delta between cmd and user - ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] == 17 + # 3 is a fault from the lka command message not being received by the EPS + ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in (3, 17) if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 From 5b10deba2ed2a70524d47435640f37655fc593bd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 3 Dec 2022 03:02:57 -0800 Subject: [PATCH 167/185] Toyota: log ACC faults (#26682) * add acc faulted * use faster signal * bump * we know PCM_CRUISE_SM is not what we want --- selfdrive/car/toyota/carstate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 250c6b65ae..fec006fbb4 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -93,10 +93,12 @@ class CarState(CarStateBase): ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in (3, 17) if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: + # TODO: find the bit likely in DSU_CRUISE that describes an ACC fault. one may also exist in CLUTCH ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_ALT"]["UI_SET_SPEED"] else: + ret.accFaulted = cp.vl["PCM_CRUISE_2"]["ACC_FAULTED"] != 0 ret.cruiseState.available = cp.vl["PCM_CRUISE_2"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["PCM_CRUISE_2"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_SM"]["UI_SET_SPEED"] @@ -205,6 +207,7 @@ class CarState(CarStateBase): else: signals.append(("MAIN_ON", "PCM_CRUISE_2")) signals.append(("SET_SPEED", "PCM_CRUISE_2")) + signals.append(("ACC_FAULTED", "PCM_CRUISE_2")) signals.append(("LOW_SPEED_LOCKOUT", "PCM_CRUISE_2")) checks.append(("PCM_CRUISE_2", 33)) From 7b0bfabb95ecb39487d311ba6ccc6b4c4e11ff08 Mon Sep 17 00:00:00 2001 From: cydia2020 <12470297+cydia2020@users.noreply.github.com> Date: Sat, 3 Dec 2022 23:31:31 +1100 Subject: [PATCH 168/185] Toyota: Don't Set Standstill Bit on TSS-P Prius (#26671) * Toyota: set TSS-P Prius as NO_STOP_TIMER_CAR * reflect in docs * also the PHV --- docs/CARS.md | 6 +++--- selfdrive/car/toyota/values.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 30411adc23..580e2ad461 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -179,10 +179,10 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 98e1f0e99d..04ffc79f4f 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -2053,4 +2053,4 @@ EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2 CAR.LEXUS_RXH_TSS2, CAR.LEXUS_NXH_TSS2, CAR.PRIUS_TSS2, CAR.ALPHARDH_TSS2} # no resume button press required -NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} +NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS, CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} From 0ff703d82fd6d93f8d260d7aa40e24c5146a0775 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 5 Dec 2022 03:28:40 +0800 Subject: [PATCH 169/185] Replay: fix SEGFAULT in buildTimeline if segments are not continuous. (#26691) fix segfault in buildtimeline --- tools/replay/replay.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 1464a6cf57..339e688be1 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -123,9 +123,9 @@ void Replay::buildTimeline() { uint64_t alert_begin = 0; TimelineType alert_type = TimelineType::None; - for (int i = 0; i < segments_.size() && !exit_; ++i) { + for (auto it = segments_.cbegin(); it != segments_.cend() && !exit_; ++it) { LogReader log; - if (!log.load(route_->at(i).qlog.toStdString(), &exit_, + if (!log.load(route_->at(it->first).qlog.toStdString(), &exit_, {cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; From f6496ce6702090747f2ee45f26a8ef91730cc9ef Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 5 Dec 2022 03:28:54 +0800 Subject: [PATCH 170/185] Cabana: show video thumbnail over slider (#26689) * show thumbnail * copy segments vector * cleanup cleanup mouseMoveEvent cleanup include * move lambda to memeber function * reduce lock time * private functions * split to small functions --- tools/cabana/canmessages.h | 3 +- tools/cabana/mainwin.cc | 2 +- tools/cabana/videowidget.cc | 62 +++++++++++++++++++++++++++++++++++++ tools/cabana/videowidget.h | 15 +++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index 4cb0f403a0..1713778af7 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -31,7 +31,7 @@ public: QList findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count); bool eventFilter(const Event *event); - inline QString route() const { return replay->route()->name(); } + inline QString routeName() const { return replay->route()->name(); } inline QString carFingerprint() const { return replay->carFingerprint().c_str(); } inline double totalSeconds() const { return replay->totalSeconds(); } inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } @@ -39,6 +39,7 @@ public: const std::deque messages(const QString &id); inline const CanData &lastMessage(const QString &id) { return can_msgs[id]; } + inline const Route* route() const { return replay->route(); } inline const std::vector *events() const { return replay->events(); } inline void setSpeed(float speed) { replay->setSpeed(speed); } inline bool isPaused() const { return replay->isPaused(); } diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 6a298bc228..70297f9978 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -71,7 +71,7 @@ MainWindow::MainWindow() : QMainWindow() { right_hlayout->addWidget(fingerprint_label, 0, Qt::AlignLeft); // TODO: click to select another route. - right_hlayout->addWidget(new QLabel(can->route()), 0, Qt::AlignRight); + right_hlayout->addWidget(new QLabel(can->routeName()), 0, Qt::AlignRight); r_layout->addLayout(right_hlayout); video_widget = new VideoWidget(this); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index d85b23b7e6..3e64d907ec 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,13 +1,17 @@ #include "tools/cabana/videowidget.h" +#include #include #include #include #include #include +#include #include #include +#include #include +#include inline QString formatTime(int seconds) { return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); @@ -92,7 +96,50 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { timeline = can->getTimeline(); update(); }); + setMouseTracking(true); + QObject::connect(can, SIGNAL(streamStarted()), timer, SLOT(start())); + QObject::connect(can, &CANMessages::streamStarted, this, &Slider::streamStarted); +} + +void Slider::streamStarted() { + abort_load_thumbnail = true; + thumnail_future.waitForFinished(); + abort_load_thumbnail = false; + thumbnails.clear(); + thumnail_future = QtConcurrent::run(this, &Slider::loadThumbnails); +} + +void Slider::loadThumbnails() { + const auto segments = can->route()->segments(); + for (auto it = segments.rbegin(); it != segments.rend() && !abort_load_thumbnail; ++it) { + std::string qlog = it->second.qlog.toStdString(); + if (!qlog.empty()) { + LogReader log; + if (log.load(qlog, &abort_load_thumbnail, {cereal::Event::Which::THUMBNAIL}, true, 0, 3)) { + for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_load_thumbnail; ++ev) { + auto thumb = (*ev)->event.getThumbnail(); + QString str = getThumbnailString(thumb.getThumbnail()); + std::lock_guard lk(thumbnail_lock); + thumbnails[thumb.getTimestampEof()] = str; + } + } + } + } +} + +QString Slider::getThumbnailString(const capnp::Data::Reader &data) { + QPixmap thumb; + if (thumb.loadFromData(data.begin(), data.size(), "jpeg")) { + thumb = thumb.scaled({thumb.width()/3, thumb.height()/3}, Qt::KeepAspectRatio); + thumbnail_size = thumb.size(); + QByteArray bytes; + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + thumb.save(&buffer, "png"); + return QString("").arg(QString(bytes.toBase64())); + } + return {}; } void Slider::sliderChange(QAbstractSlider::SliderChange change) { @@ -146,3 +193,18 @@ void Slider::mousePressEvent(QMouseEvent *e) { emit sliderReleased(); } } + +void Slider::mouseMoveEvent(QMouseEvent *e) { + QString thumb; + { + double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0; + std::lock_guard lk(thumbnail_lock); + auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9); + if (it != thumbnails.end()) { + thumb = it.value(); + } + } + QPoint pt = mapToGlobal({e->pos().x() - thumbnail_size.width() / 2, -thumbnail_size.height() - 30}); + QToolTip::showText(pt, thumb, this, rect()); + QSlider::mouseMoveEvent(e); +} diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 16f60b0b03..ea62081a91 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,5 +1,9 @@ #pragma once +#include +#include + +#include #include #include #include @@ -12,12 +16,23 @@ class Slider : public QSlider { public: Slider(QWidget *parent); + +private: void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; void sliderChange(QAbstractSlider::SliderChange change) override; void paintEvent(QPaintEvent *ev) override; + void streamStarted(); + void loadThumbnails(); + QString getThumbnailString(const capnp::Data::Reader &data); int slider_x = -1; std::vector> timeline; + std::mutex thumbnail_lock; + std::atomic abort_load_thumbnail = false; + QMap thumbnails; + QFuture thumnail_future; + QSize thumbnail_size = {}; }; class VideoWidget : public QWidget { From b513b832214f5427dd5d6c217f2e31b4bcecdfac Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 5 Dec 2022 03:30:31 +0800 Subject: [PATCH 171/185] Replay: remove wrong text in TIME (#26692) remove wrong time string --- tools/replay/consoleui.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 5f165ac312..77357a2873 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -156,13 +156,13 @@ void ConsoleUI::timerEvent(QTimerEvent *ev) { } void ConsoleUI::updateStatus() { - auto write_item = [this](int y, int x, const char *key, const std::string &value, const char *unit, + auto write_item = [this](int y, int x, const char *key, const std::string &value, const std::string &unit, bool bold = false, Color color = Color::BrightWhite) { auto win = w[Win::CarState]; wmove(win, y, x); add_str(win, key); add_str(win, value.c_str(), color, bold); - add_str(win, unit); + add_str(win, unit.c_str()); }; static const std::pair status_text[] = { {"loading...", Color::Red}, @@ -177,9 +177,8 @@ void ConsoleUI::updateStatus() { } auto [status_str, status_color] = status_text[status]; write_item(0, 0, "STATUS: ", status_str, " ", false, status_color); - std::string suffix = util::string_format(" / %s [%d/%d] ", format_seconds(replay->totalSeconds()).c_str(), - replay->currentSeconds() / 60, replay->route()->segments().size()); - write_item(0, 25, "TIME: ", format_seconds(replay->currentSeconds()), suffix.c_str(), true); + std::string suffix = " / " + format_seconds(replay->totalSeconds()); + write_item(0, 25, "TIME: ", format_seconds(replay->currentSeconds()), suffix, true); auto p = sm["liveParameters"].getLiveParameters(); write_item(1, 0, "STIFFNESS: ", util::string_format("%.2f %%", p.getStiffnessFactor() * 100), " "); From 2976260b4f57953ac230f0d85349cf3d0ba23168 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Dec 2022 11:33:58 -0800 Subject: [PATCH 172/185] Hyundai: update Kona lat accel factor --- selfdrive/car/torque_data/params.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 7901972fc2..c081359937 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -31,7 +31,7 @@ HYUNDAI IONIQ 5 2022: [3.172929, 3.0, 0.096019] HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482] -HYUNDAI KONA ELECTRIC 2019: [4.398306735170212, 3.2961956260770484, 0.08651833437845884] +HYUNDAI KONA ELECTRIC 2019: [3.078814714619148, 3.2961956260770484, 0.08651833437845884] HYUNDAI PALISADE 2020: [2.544642494803999, 1.8721703683337008, 0.1301424599248651] HYUNDAI SANTA FE 2019: [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] HYUNDAI SANTA FE HYBRID 2022: [3.501877602644835, 2.729064118456137, 0.10384068104538963] From 0398f3ecf4c62e26768f3baa661ac27b9280955c Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Sun, 4 Dec 2022 14:46:52 -0800 Subject: [PATCH 173/185] Undo accidental revert of Toyota: Add FW for 2023 Toyota Camry Hybrid #26654 (#26699) Fix accidental revert of 1f4554b0c030cfc2623d7c7db413e9c61dabaef4 in c0d545523e48464acdd6a356d505e0e81266582d --- selfdrive/car/toyota/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 04ffc79f4f..c9e13e21a1 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -551,10 +551,12 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F152633D00\x00\x00\x00\x00\x00\x00', b'F152633D60\x00\x00\x00\x00\x00\x00', + b'F152633310\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', b'\x018966306Q7000\x00\x00\x00\x00', + b'\x018966306V1000\x00\x00\x00\x00', b'\x01896633T20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 15): [ @@ -565,6 +567,7 @@ FW_VERSIONS = { b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CHR: { From f84b1f2d9f772b1470eee99503b6dc1d0c813957 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 6 Dec 2022 05:05:41 +0800 Subject: [PATCH 174/185] Sensord: remove unused pm_mutex (#26705) remove unused mutex --- selfdrive/sensord/sensors_qcom2.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/sensord/sensors_qcom2.cc b/selfdrive/sensord/sensors_qcom2.cc index 2279cf2532..fc8dc8620b 100644 --- a/selfdrive/sensord/sensors_qcom2.cc +++ b/selfdrive/sensord/sensors_qcom2.cc @@ -27,7 +27,6 @@ #define I2C_BUS_IMU 1 ExitHandler do_exit; -std::mutex pm_mutex; uint64_t init_ts = 0; void interrupt_loop(std::vector& sensors, From e6fcc2d6aa582f90fd3a2b2192616005f875fcb2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 5 Dec 2022 13:58:15 -0800 Subject: [PATCH 175/185] planner: read experimental mode from controlsState (#26553) read from controlsState --- selfdrive/controls/lib/longitudinal_planner.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index def0a1208a..a0f6318323 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -6,7 +6,6 @@ from common.numpy_fast import clip, interp import cereal.messaging as messaging from common.conversions import Conversions as CV from common.filter_simple import FirstOrderFilter -from common.params import Params from common.realtime import DT_MDL from selfdrive.modeld.constants import T_IDXS from selfdrive.controls.lib.longcontrol import LongCtrlState @@ -48,12 +47,7 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): class LongitudinalPlanner: def __init__(self, CP, init_v=0.0, init_a=0.0): self.CP = CP - self.params = Params() - self.param_read_counter = 0 - self.mpc = LongitudinalMpc() - self.read_param() - self.fcw = False self.a_desired = init_a @@ -65,10 +59,6 @@ class LongitudinalPlanner: self.j_desired_trajectory = np.zeros(CONTROL_N) self.solverExecutionTime = 0.0 - def read_param(self): - e2e = self.params.get_bool('ExperimentalMode') and self.CP.openpilotLongitudinalControl - self.mpc.mode = 'blended' if e2e else 'acc' - @staticmethod def parse_model(model_msg, model_error): if (len(model_msg.position.x) == 33 and @@ -85,10 +75,8 @@ class LongitudinalPlanner: j = np.zeros(len(T_IDXS_MPC)) return x, v, a, j - def update(self, sm, read=True): - if self.param_read_counter % 50 == 0 and read: - self.read_param() - self.param_read_counter += 1 + def update(self, sm): + self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' v_ego = sm['carState'].vEgo v_cruise_kph = sm['controlsState'].vCruise From 2d0e29d60e1a74ef79de7e64af1495c94d262fcd Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:59:36 -0500 Subject: [PATCH 176/185] VW MQB: Add FW for 2018 Volkswagen Atlas (#26702) --- selfdrive/car/volkswagen/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index c96f6decbf..18a444bf7c 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -320,11 +320,13 @@ FW_VERSIONS = { b'\xf1\x8703H906026J \xf1\x899971', b'\xf1\x8703H906026S \xf1\x896693', b'\xf1\x8703H906026S \xf1\x899970', + b'\xf1\x873CN906259 \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158A \xf1\x893387', b'\xf1\x8709G927158DR\xf1\x893536', b'\xf1\x8709G927158DR\xf1\x893742', + b'\xf1\x8709G927158F \xf1\x893489', b'\xf1\x8709G927158FT\xf1\x893835', b'\xf1\x8709G927158GL\xf1\x893939', ], @@ -338,6 +340,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\00571B60924A1', b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105', ], (Ecu.fwdRadar, 0x757, None): [ From 4d74e44ae14301aab27b833974bb90955d20d04e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 6 Dec 2022 07:12:36 +0800 Subject: [PATCH 177/185] Cabana: display dash instead of zero if no data (#26686) display dash instead of zero if no data --- tools/cabana/binaryview.cc | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index e655bf0c04..bc64edbfeb 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -181,15 +181,14 @@ void BinaryViewModel::updateState() { auto prev_items = items; const auto &binary = can->lastMessage(msg_id).dat; // data size may changed. - if (!dbc_msg && binary.size() != row_count) { - beginResetModel(); + if (binary.size() > row_count) { + beginInsertRows({}, row_count, binary.size() - 1); row_count = binary.size(); - items.clear(); items.resize(row_count * column_count); - endResetModel(); + endInsertRows(); } char hex[3] = {'\0'}; - for (int i = 0; i < std::min(binary.size(), row_count); ++i) { + for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < column_count - 1; ++j) { items[i * column_count + j].val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0'; } @@ -197,8 +196,13 @@ void BinaryViewModel::updateState() { hex[1] = toHex(binary[i] & 0xf); items[i * column_count + 8].val = hex; } + for (int i = binary.size(); i < row_count; ++i) { + for (int j = 0; j < column_count; ++j) { + items[i * column_count + j].val = "-"; + } + } - for (int i = 0; i < items.size(); ++i) { + for (int i = 0; i < row_count; ++i) { if (i >= prev_items.size() || prev_items[i].val != items[i].val) { auto idx = index(i / column_count, i % column_count); emit dataChanged(idx, idx); From be0c2dbbf9ea3ae2c94a057b22e972f405f7e67f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 5 Dec 2022 15:23:00 -0800 Subject: [PATCH 178/185] Longitudinal planner: fix tests (#26707) fix tests --- selfdrive/test/longitudinal_maneuvers/plant.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index c3af1eee03..0d3336134f 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -4,6 +4,7 @@ import numpy as np from cereal import log import cereal.messaging as messaging +from common.params import Params from common.realtime import Ratekeeper, DT_MDL from selfdrive.controls.lib.longcontrol import LongCtrlState from selfdrive.modeld.constants import T_IDXS @@ -17,6 +18,7 @@ class Plant: def __init__(self, lead_relevancy=False, speed=0.0, distance_lead=2.0, enabled=True, only_lead2=False, only_radar=False): self.rate = 1. / DT_MDL + self.params = Params() if not Plant.messaging_initialized: Plant.radar = messaging.pub_sock('radarState') @@ -109,6 +111,7 @@ class Plant: control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off control.controlsState.vCruise = float(v_cruise * 3.6) + control.controlsState.experimentalMode = self.params.get_bool("ExperimentalMode") car_state.carState.vEgo = float(self.speed) car_state.carState.standstill = self.speed < 0.01 From 973ecdc0c9c719a255b39dad69923be0e2ede1a5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 5 Dec 2022 15:24:54 -0800 Subject: [PATCH 179/185] Hyundai: Tucson 2023 support (#26427) * Hardcode fingerprint * no skipping * rev * the SE does not have SCC https://cdn.dealereprocess.org/cdn/brochures/hyundai/2022-tucson.pdf * revert * adjust weight * add versions for 2023 * update docs * fingerprint properly * add package for 2022 but comment out * updatedocs * update route * bump * bump! * 2022 * Update selfdrive/car/tests/routes.py --- RELEASES.md | 1 + docs/CARS.md | 4 +++- selfdrive/car/hyundai/interface.py | 4 ++-- selfdrive/car/hyundai/values.py | 22 ++++++++++++++++++---- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/substitute.yaml | 1 + 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index c41b3ed0f7..44fc5e715e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,7 @@ Version 0.9.1 (2022-12-XX) * Removed driver monitoring timer resetting on interaction if face detected and distracted * Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! +* Hyundai Tucson 2022-23 support Version 0.9.0 (2022-11-21) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index 580e2ad461..141e910fa5 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 217 Supported Cars +# 219 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -83,6 +83,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Sonata 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Hyundai|Tucson 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| +|Hyundai|Tucson 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Tucson Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 7b157d048b..b9f6b8fc58 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -114,8 +114,8 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.67 ret.steerRatio = 14.00 * 1.15 tire_stiffness_factor = 0.385 - elif candidate == CAR.TUCSON_HYBRID_4TH_GEN: - ret.mass = 1680. + STD_CARGO_KG # average of all 3 trims + elif candidate in (CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN): + ret.mass = 1630. + STD_CARGO_KG # average ret.wheelbase = 2.756 ret.steerRatio = 16. tire_stiffness_factor = 0.385 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index e5f180a510..74c881e2b6 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -86,6 +86,7 @@ class CAR: VELOSTER = "HYUNDAI VELOSTER 2019" SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" IONIQ_5 = "HYUNDAI IONIQ 5 2022" + TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN" TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN" SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN" @@ -157,11 +158,15 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), CAR.IONIQ_5: [ - HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23" , "Highway Driving Assist", harness=Harness.hyundai_k), + HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", harness=Harness.hyundai_k), HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q), ], + CAR.TUCSON_4TH_GEN: [ + HyundaiCarInfo("Hyundai Tucson 2022", harness=Harness.hyundai_n), + HyundaiCarInfo("Hyundai Tucson 2023", "All", harness=Harness.hyundai_n), + ], CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), - CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2021-22", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n), + CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2021-22", harness=Harness.hyundai_n), # Kia CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g), @@ -184,7 +189,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Optima Hybrid 2019"), ], CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a), - CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n), + CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", harness=Harness.hyundai_n), CAR.KIA_SORENTO: [ HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), @@ -1435,6 +1440,14 @@ FW_VERSIONS = { b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', ], }, + CAR.TUCSON_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', + ], + }, CAR.TUCSON_HYBRID_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', @@ -1496,7 +1509,7 @@ FEATURES = { "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # The radar does SCC on these cars when HDA I, rather than the camera CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} @@ -1555,6 +1568,7 @@ DBC = { CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None), CAR.KIA_EV6: dbc_dict('hyundai_canfd', None), CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), + CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index a20616de43..89b895864a 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -96,6 +96,7 @@ routes = [ CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), + CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.TUCSON_4TH_GEN), # 2023 CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_PHEV_4TH_GEN), diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index df696bc0fc..aeb2e6f280 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -35,6 +35,7 @@ HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 +HYUNDAI TUCSON 4TH GEN: HYUNDAI TUCSON HYBRID 4TH GEN HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 KIA STINGER 2022: KIA STINGER GT2 2018 GENESIS G90 2017: GENESIS G70 2018 From a311d936d094b294d22a9ee034380400ec532e91 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 5 Dec 2022 15:27:35 -0800 Subject: [PATCH 180/185] releases: fix car port sorting --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 44fc5e715e..211e63a755 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,9 +2,9 @@ Version 0.9.1 (2022-12-XX) ======================== * Adjust alert volume using ambient noise level * Removed driver monitoring timer resetting on interaction if face detected and distracted -* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! * Hyundai Tucson 2022-23 support +* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! Version 0.9.0 (2022-11-21) ======================== From e598438a97b9ae6c4d5d921cd094efe1f40320b4 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 6 Dec 2022 07:30:43 +0800 Subject: [PATCH 181/185] Cabana: show welcome widget if no msg selected (#26703) * show a blank widget if no msg selected * smaller font * rename to WelcomeWidget --- tools/cabana/detailwidget.cc | 37 ++++++++++++++++++++++++++++++++++-- tools/cabana/detailwidget.h | 7 +++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 5035bf49b4..cd1057f7e1 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -16,9 +16,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { undo_stack = new QUndoStack(this); - setMinimumWidth(500); - QVBoxLayout *main_layout = new QVBoxLayout(this); + QWidget *main_widget = new QWidget(this); + QVBoxLayout *main_layout = new QVBoxLayout(main_widget); main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setSpacing(0); @@ -90,6 +90,10 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart tab_widget->addTab(history_log, "&Logs"); main_layout->addWidget(tab_widget); + stacked_layout = new QStackedLayout(this); + stacked_layout->addWidget(new WelcomeWidget(this)); + stacked_layout->addWidget(main_widget); + QObject::connect(binary_view, &BinaryView::signalClicked, this, &DetailWidget::showForm); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); @@ -136,6 +140,7 @@ void DetailWidget::setMessage(const QString &message_id) { tabbar->setCurrentIndex(index); dbcMsgChanged(); scroll->verticalScrollBar()->setValue(0); + stacked_layout->setCurrentIndex(1); } void DetailWidget::dbcMsgChanged(int show_form_idx) { @@ -302,3 +307,31 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } + +// WelcomeWidget + +WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->addStretch(0); + QLabel *logo = new QLabel("CABANA"); + logo->setAlignment(Qt::AlignCenter); + logo->setStyleSheet("font-size:50px;font-weight:bold;"); + main_layout->addWidget(logo); + + auto newShortcutRow = [](const QString &title, const QString &key) { + QHBoxLayout *hlayout = new QHBoxLayout(); + auto btn = new QToolButton(); + btn->setText(key); + btn->setEnabled(false); + hlayout->addWidget(new QLabel(title), 0, Qt::AlignRight); + hlayout->addWidget(btn, 0, Qt::AlignLeft); + return hlayout; + }; + + main_layout->addLayout(newShortcutRow("Pause", "Space")); + main_layout->addLayout(newShortcutRow("Help", "Alt + H")); + main_layout->addStretch(0); + + setStyleSheet("QLabel{color:darkGray;}"); +} + diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 75409fa706..91127c9b74 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -18,6 +19,11 @@ public: QSpinBox *size_spin; }; +class WelcomeWidget : public QWidget { +public: + WelcomeWidget(QWidget *parent); +}; + class DetailWidget : public QWidget { Q_OBJECT @@ -51,5 +57,6 @@ private: BinaryView *binary_view; QScrollArea *scroll; ChartsWidget *charts; + QStackedLayout *stacked_layout; QList signal_list; }; From 9ea72b655035ee9f96c60c3268049bba6b8b9e1a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 5 Dec 2022 17:34:57 -0800 Subject: [PATCH 182/185] Longitudinal tests: clean up (#26708) * refactor test_cruise_speed.py * clean up long tests * and here --- selfdrive/controls/tests/test_cruise_speed.py | 8 +- .../controls/tests/test_following_distance.py | 9 +- .../test/longitudinal_maneuvers/maneuver.py | 4 +- .../test/longitudinal_maneuvers/plant.py | 7 +- .../test_longitudinal.py | 278 +++++++++--------- 5 files changed, 149 insertions(+), 157 deletions(-) diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py index a635198ceb..cd1d31cf07 100755 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -6,14 +6,13 @@ import unittest from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_ENABLE_MIN, IMPERIAL_INCREMENT from cereal import car from common.conversions import Conversions as CV -from common.params import Params from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type -def run_cruise_simulation(cruise, t_end=20.): +def run_cruise_simulation(cruise, e2e, t_end=20.): man = Maneuver( '', duration=t_end, @@ -23,6 +22,7 @@ def run_cruise_simulation(cruise, t_end=20.): cruise_values=[cruise], prob_lead_values=[0.0], breakpoints=[0.], + e2e=e2e, ) valid, output = man.evaluate() assert valid @@ -31,14 +31,12 @@ def run_cruise_simulation(cruise, t_end=20.): class TestCruiseSpeed(unittest.TestCase): def test_cruise_speed(self): - params = Params() for e2e in [False, True]: - params.put_bool("ExperimentalMode", e2e) for speed in np.arange(5, 40, 5): print(f'Testing {speed} m/s') cruise_speed = float(speed) - simulation_steady_state = run_cruise_simulation(cruise_speed) + simulation_steady_state = run_cruise_simulation(cruise_speed, e2e) self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s') diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 0535caab84..5185867d2d 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 import unittest import numpy as np -from common.params import Params - from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver + def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): man = Maneuver( '', @@ -16,7 +15,7 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): initial_distance_lead=100, speed_lead_values=[v_lead], breakpoints=[0.], - e2e=e2e + e2e=e2e, ) valid, output = man.evaluate() assert valid @@ -25,13 +24,11 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): class TestFollowingDistance(unittest.TestCase): def test_following_distance(self): - params = Params() for e2e in [False, True]: - params.put_bool("ExperimentalMode", e2e) for speed in np.arange(0, 40, 5): print(f'Testing {speed} m/s') v_lead = float(speed) - simulation_steady_state = run_following_distance_simulation(v_lead) + simulation_steady_state = run_following_distance_simulation(v_lead, e2e=e2e) correct_steady_state = desired_follow_distance(v_lead, v_lead) err_ratio = 0.2 if e2e else 0.1 self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5)) diff --git a/selfdrive/test/longitudinal_maneuvers/maneuver.py b/selfdrive/test/longitudinal_maneuvers/maneuver.py index 071eaada12..65935f8979 100644 --- a/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -2,7 +2,7 @@ import numpy as np from selfdrive.test.longitudinal_maneuvers.plant import Plant -class Maneuver(): +class Maneuver: def __init__(self, title, duration, **kwargs): # Was tempted to make a builder class self.distance_lead = kwargs.get("initial_distance_lead", 200.0) @@ -18,6 +18,7 @@ class Maneuver(): self.only_radar = kwargs.get("only_radar", False) self.ensure_start = kwargs.get("ensure_start", False) self.enabled = kwargs.get("enabled", True) + self.e2e = kwargs.get("e2e", False) self.duration = duration self.title = title @@ -30,6 +31,7 @@ class Maneuver(): enabled=self.enabled, only_lead2=self.only_lead2, only_radar=self.only_radar, + e2e=self.e2e, ) valid = True diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index 0d3336134f..8e150d800c 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -4,7 +4,6 @@ import numpy as np from cereal import log import cereal.messaging as messaging -from common.params import Params from common.realtime import Ratekeeper, DT_MDL from selfdrive.controls.lib.longcontrol import LongCtrlState from selfdrive.modeld.constants import T_IDXS @@ -16,9 +15,8 @@ class Plant: messaging_initialized = False def __init__(self, lead_relevancy=False, speed=0.0, distance_lead=2.0, - enabled=True, only_lead2=False, only_radar=False): + enabled=True, only_lead2=False, only_radar=False, e2e=False): self.rate = 1. / DT_MDL - self.params = Params() if not Plant.messaging_initialized: Plant.radar = messaging.pub_sock('radarState') @@ -40,6 +38,7 @@ class Plant: self.enabled = enabled self.only_lead2 = only_lead2 self.only_radar = only_radar + self.e2e = e2e self.rk = Ratekeeper(self.rate, print_delay_threshold=100.0) self.ts = 1. / self.rate @@ -111,7 +110,7 @@ class Plant: control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off control.controlsState.vCruise = float(v_cruise * 3.6) - control.controlsState.experimentalMode = self.params.get_bool("ExperimentalMode") + control.controlsState.experimentalMode = self.e2e car_state.carState.vEgo = float(self.speed) car_state.carState.standstill = self.speed < 0.01 diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index e859952445..686b35e456 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import os +from parameterized import parameterized import unittest from common.params import Params @@ -8,124 +9,137 @@ from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver # TODO: make new FCW tests -maneuvers = [ - Maneuver( - 'approach stopped car at 25m/s, initial distance: 120m', - duration=20., - initial_speed=25., - lead_relevancy=True, - initial_distance_lead=120., - speed_lead_values=[30., 0.], - breakpoints=[0., 1.], - ), - Maneuver( - 'approach stopped car at 20m/s, initial distance 90m', - duration=20., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=90., - speed_lead_values=[20., 0.], - breakpoints=[0., 1.], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 1m/s^2', - duration=50., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - breakpoints=[0., 15., 35.0], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 2m/s^2', - duration=50., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - breakpoints=[0., 15., 25.0], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 3m/s^2', - duration=50., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - breakpoints=[0., 15., 21.66], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 3+m/s^2', - duration=40., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - prob_lead_values=[0., 1., 1.], - cruise_values=[20., 20., 20.], - breakpoints=[2., 2.01, 8.8], - ), - Maneuver( - "approach stopped car at 20m/s, with prob_lead_values", - duration=30., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=120., - speed_lead_values=[0.0, 0., 0.], - prob_lead_values=[0.0, 0., 1.], - cruise_values=[20., 20., 20.], - breakpoints=[0.0, 2., 2.01], - ), - Maneuver( - "approach slower cut-in car at 20m/s", - duration=20., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=50., - speed_lead_values=[15., 15.], - breakpoints=[1., 11.], - only_lead2=True, - ), - Maneuver( - "stay stopped behind radar override lead", - duration=20., - initial_speed=0., - lead_relevancy=True, - initial_distance_lead=10., - speed_lead_values=[0., 0.], - prob_lead_values=[0., 0.], - breakpoints=[1., 11.], - only_radar=True, - ), - Maneuver( - "NaN recovery", - duration=30., - initial_speed=15., - lead_relevancy=True, - initial_distance_lead=60., - speed_lead_values=[0., 0., 0.0], - breakpoints=[1., 1.01, 11.], - cruise_values=[float("nan"), 15., 15.], - ), - # controls relies on planner commanding to move for stock-ACC resume spamming - Maneuver( - "resume from a stop", - duration=20., - initial_speed=0., - lead_relevancy=True, - initial_distance_lead=STOP_DISTANCE, - speed_lead_values=[0., 0., 2.], - breakpoints=[1., 10., 15.], - ensure_start=True, - ), - Maneuver( - 'cruising at 25 m/s while disabled', - duration=20., - initial_speed=25., - lead_relevancy=False, - enabled=False, - ), -] +def create_maneuvers(e2e): + return [ + Maneuver( + 'approach stopped car at 25m/s, initial distance: 120m', + duration=20., + initial_speed=25., + lead_relevancy=True, + initial_distance_lead=120., + speed_lead_values=[30., 0.], + breakpoints=[0., 1.], + e2e=e2e, + ), + Maneuver( + 'approach stopped car at 20m/s, initial distance 90m', + duration=20., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=90., + speed_lead_values=[20., 0.], + breakpoints=[0., 1.], + e2e=e2e, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 1m/s^2', + duration=50., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + breakpoints=[0., 15., 35.0], + e2e=e2e, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 2m/s^2', + duration=50., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + breakpoints=[0., 15., 25.0], + e2e=e2e, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 3m/s^2', + duration=50., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + breakpoints=[0., 15., 21.66], + e2e=e2e, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 3+m/s^2', + duration=40., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + prob_lead_values=[0., 1., 1.], + cruise_values=[20., 20., 20.], + breakpoints=[2., 2.01, 8.8], + e2e=e2e, + ), + Maneuver( + "approach stopped car at 20m/s, with prob_lead_values", + duration=30., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=120., + speed_lead_values=[0.0, 0., 0.], + prob_lead_values=[0.0, 0., 1.], + cruise_values=[20., 20., 20.], + breakpoints=[0.0, 2., 2.01], + e2e=e2e, + ), + Maneuver( + "approach slower cut-in car at 20m/s", + duration=20., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=50., + speed_lead_values=[15., 15.], + breakpoints=[1., 11.], + only_lead2=True, + e2e=e2e, + ), + Maneuver( + "stay stopped behind radar override lead", + duration=20., + initial_speed=0., + lead_relevancy=True, + initial_distance_lead=10., + speed_lead_values=[0., 0.], + prob_lead_values=[0., 0.], + breakpoints=[1., 11.], + only_radar=True, + e2e=e2e, + ), + Maneuver( + "NaN recovery", + duration=30., + initial_speed=15., + lead_relevancy=True, + initial_distance_lead=60., + speed_lead_values=[0., 0., 0.0], + breakpoints=[1., 1.01, 11.], + cruise_values=[float("nan"), 15., 15.], + e2e=e2e, + ), + # controls relies on planner commanding to move for stock-ACC resume spamming + Maneuver( + "resume from a stop", + duration=20., + initial_speed=0., + lead_relevancy=True, + initial_distance_lead=STOP_DISTANCE, + speed_lead_values=[0., 0., 2.], + breakpoints=[1., 10., 15.], + ensure_start=True, + e2e=e2e, + ), + Maneuver( + 'cruising at 25 m/s while disabled', + duration=20., + initial_speed=25., + lead_relevancy=False, + enabled=False, + e2e=e2e, + ), + ] class LongitudinalControl(unittest.TestCase): @@ -140,29 +154,11 @@ class LongitudinalControl(unittest.TestCase): params.put_bool("Passive", bool(os.getenv("PASSIVE"))) params.put_bool("OpenpilotEnabledToggle", True) - # hack - def test_longitudinal_setup(self): - pass - - -def run_maneuver_worker(k): - def run(self): - params = Params() - - man = maneuvers[k] - params.put_bool("ExperimentalMode", True) - print(man.title, ' in e2e mode') - valid, _ = man.evaluate() - self.assertTrue(valid, msg=man.title) - params.put_bool("ExperimentalMode", False) - print(man.title, ' in acc mode') - valid, _ = man.evaluate() - self.assertTrue(valid, msg=man.title) - return run - -for k in range(len(maneuvers)): - setattr(LongitudinalControl, f"test_longitudinal_maneuvers_{k+1}", - run_maneuver_worker(k)) + @parameterized.expand([(man,) for e2e in [True, False] for man in create_maneuvers(e2e)]) + def test_maneuver(self, maneuver): + print(maneuver.title, f'in {"e2e" if maneuver.e2e else "acc"} mode') + valid, _ = maneuver.evaluate() + self.assertTrue(valid, msg=maneuver.title) if __name__ == "__main__": From f36e8bc3c7316bd4aac38f8997b66d1649819fe9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 5 Dec 2022 17:35:19 -0800 Subject: [PATCH 183/185] Revert "Toyota: Don't Set Standstill Bit on TSS-P Prius" (#26709) Revert "Toyota: Don't Set Standstill Bit on TSS-P Prius (#26671)" This reverts commit 7b0bfabb95ecb39487d311ba6ccc6b4c4e11ff08. --- docs/CARS.md | 6 +++--- selfdrive/car/toyota/values.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 141e910fa5..4dccb30285 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -181,10 +181,10 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index c9e13e21a1..56bb2697c7 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -2056,4 +2056,4 @@ EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2 CAR.LEXUS_RXH_TSS2, CAR.LEXUS_NXH_TSS2, CAR.PRIUS_TSS2, CAR.ALPHARDH_TSS2} # no resume button press required -NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS, CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} +NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} From 6e0893299ad435291782e40a92ddeb85b85f4b87 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 5 Dec 2022 17:41:32 -0800 Subject: [PATCH 184/185] navmodeld: only render necessary frames (#26698) --- selfdrive/modeld/navmodeld.cc | 1 - selfdrive/navd/map_renderer.cc | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/modeld/navmodeld.cc b/selfdrive/modeld/navmodeld.cc index 57ef7cf0e0..75f2c62ab3 100644 --- a/selfdrive/modeld/navmodeld.cc +++ b/selfdrive/modeld/navmodeld.cc @@ -21,7 +21,6 @@ void run_model(NavModelState &model, VisionIpcClient &vipc_client) { while (!do_exit) { VisionBuf *buf = vipc_client.recv(&extra); if (buf == nullptr) continue; - if (extra.frame_id < last_frame_id + 10) continue; // Run at 2Hz double t1 = millis_since_boot(); NavModelResult *model_res = navmodel_eval_frame(&model, buf); diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index 247b69c4c5..6a19f45fed 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -79,7 +79,7 @@ void MapRenderer::msgUpdate() { auto orientation = location.getCalibratedOrientationNED(); bool localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && pos.getValid(); - if (localizer_valid) { + if (localizer_valid && (sm->rcv_frame("liveLocationKalman") % 10) == 0) { updatePosition(QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]), RAD2DEG(orientation.getValue()[2])); } } From ddae8ffc09261fe932c24192fccc6d8bceb1d088 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Mon, 5 Dec 2022 19:25:57 -0800 Subject: [PATCH 185/185] add onnx2torch dependency (#26710) * add onnx2torch dependency * sort Co-authored-by: Cameron Clough --- poetry.lock | 31 ++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index fbe0009a26..39fb78cf2a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2439,6 +2439,20 @@ typing-extensions = ">=3.6.2.1" [package.extras] lint = ["clang-format (==13.0.0)", "flake8", "mypy (==0.782)", "types-protobuf (==3.18.4)"] +[[package]] +name = "onnx2torch" +version = "1.5.4" +description = "Nice Onnx to Pytorch converter" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.16.4" +onnx = ">=1.9.0" +torch = ">=1.8.0" +torchvision = ">=0.9.0" + [[package]] name = "onnxoptimizer" version = "0.3.1" @@ -4400,7 +4414,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "a41b1d669ec4f0a94abe12af8a9a7d709b2bb56e9c83aa87801cdf5d9af074f8" +content-hash = "331a7b700c17c618a4f0fa7d0ec3e5b585c1dc467f6f03261c3778f729058f55" [metadata.files] adal = [ @@ -5406,6 +5420,7 @@ gevent = [ {file = "gevent-22.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2ea4ce36c09355379bc038be2bd50118f97d2eb6381b7096de4d05aa4c3e241"}, {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e73c9f71aa2a6795ecbec9b57282b002375e863e283558feb87b62840c8c1ac"}, {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc3758f0dc95007c1780d28a9fd2150416a79c50f308f62a674d78a845ea1b9"}, + {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c10ca0beeab0c6be516030471ea630447ddd1f649d3335e5b162097cd4130a"}, {file = "gevent-22.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fe2c0ff095171c49f78f1d4e6dc89fa58253783c7b6dccab9f1d76e2ee391f10"}, {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d18fcc324f39a3b21795022eb47c7752d6e4f4ed89d8cca41f1cc604553265b3"}, {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ea39c70ce166c4a1d4386c7fae96cb8d84ad799527b3378406051104d15443"}, @@ -6401,6 +6416,9 @@ onnx = [ {file = "onnx-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:af90427ca04c6b7b8107c2021e1273227a3ef1a7a01f3073039cae7855a59833"}, {file = "onnx-1.12.0.tar.gz", hash = "sha256:13b3e77d27523b9dbf4f30dfc9c959455859d5e34e921c44f712d69b8369eff9"}, ] +onnx2torch = [ + {file = "onnx2torch-1.5.4-py3-none-any.whl", hash = "sha256:fd1a0fe05072bfb9f3d86d9330299b130b41f11bd4ae634db17078974e711725"}, +] onnxoptimizer = [ {file = "onnxoptimizer-0.3.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e73a5e2e3ca4db9bff54f7131768749c861677b97ee811a136fcf1a52783cf6e"}, {file = "onnxoptimizer-0.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bf2bfe0dc43f0776867688e1759122dec049ff4f45f7221931b687fe7e139e"}, @@ -6576,6 +6594,11 @@ pillow-avif-plugin = [ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017e5e52cb4320414e8ce3e2089eae2cb87c22c73ff6012b17ae326fc5753b20"}, {file = "pillow_avif_plugin-1.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a57136d4866de5dc80cfb24d66655955fbdd87acf1d11d88c8dc2ab41023e46"}, {file = "pillow_avif_plugin-1.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:f339511d0fccb69e3a5e3af39f8fe6700b0a07279015006ea56f8f49e7fecff4"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05e821ecd90bb0b8d2dc7610625372cc47de9cb893d09662528bad572f669d1c"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33886a5f9796fe9a8a3bc25ccfdeba7db119adb50b7004f1928a14b07d0213a"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75b7ed186c2f740dd26e556f6a966c59a170b70263e429a2c81920fe444da8a7"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11aef6b79078b8dad25c928e5871c146ab94424472851d5bf539ba62abde9ac"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10696c536d68a14cefea3b98edb8d5a7ae29e8e07458f1d59c5d1cd780a8bf2a"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1a7291d6a5fb7336e72685a31d193e0b3a6bee9986c9ac4d8bd4b68dbe6d4f7f"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:14b9c5dbf237e7dc12f69819ea181a457b3bd4f59f8cd71d028d3635fd3bcab4"}, {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:799cbfbeee831332d280c80df9ce16b5c3b1224c318264e97e89df8da32e870e"}, @@ -6772,6 +6795,7 @@ pycryptodome = [ {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b"}, {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a"}, {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:2ae53125de5b0d2c95194d957db9bb2681da8c24d0fb0fe3b056de2bcaf5d837"}, {file = "pycryptodome-3.15.0-cp27-cp27m-win32.whl", hash = "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c"}, {file = "pycryptodome-3.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83"}, {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8"}, @@ -6779,12 +6803,14 @@ pycryptodome = [ {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763"}, {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f"}, {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:eb6fce570869e70cc8ebe68eaa1c26bed56d40ad0f93431ee61d400525433c54"}, {file = "pycryptodome-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e"}, + {file = "pycryptodome-3.15.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:50ca7e587b8e541eb6c192acf92449d95377d1f88908c0a32ac5ac2703ebe28b"}, {file = "pycryptodome-3.15.0-cp35-abi3-win32.whl", hash = "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1"}, {file = "pycryptodome-3.15.0-cp35-abi3-win_amd64.whl", hash = "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9"}, {file = "pycryptodome-3.15.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f"}, @@ -6964,11 +6990,13 @@ pyprof2calltree = [ ] pyproj = [ {file = "pyproj-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f343725566267a296b09ee7e591894f1fdc90f84f8ad5ec476aeb53bd4479c07"}, + {file = "pyproj-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5816807ca0bdc7256558770c6206a6783a3f02bcf844f94ee245f197bb5f7285"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e609903572a56cca758bbaee5c1663c3e829ddce5eec4f368e68277e37022b"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fd425ee8b6781c249c7adb7daa2e6c41ce573afabe4f380f5eecd913b56a3be"}, {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954b068136518b3174d0a99448056e97af62b63392a95c420894f7de2229dae6"}, {file = "pyproj-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:4a23d84c5ffc383c7d9f0bde3a06fc1f6697b1b96725597f8f01e7b4bef0a2b5"}, {file = "pyproj-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f9c100fd0fd80edbc7e4daa303600a8cbef6f0de43d005617acb38276b88dc0"}, + {file = "pyproj-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa5171f700f174777a9e9ed8f4655583243967c0f9cf2c90e3f54e54ff740134"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a496d9057b2128db9d733e66b206f2d5954bbae6b800d412f562d780561478c"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e54796e2d9554a5eb8f11df4748af1fbbc47f76aa234d6faf09216a84554c5"}, {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a454a7c4423faa2a14e939d08ef293ee347fa529c9df79022b0585a6e1d8310c"}, @@ -6979,6 +7007,7 @@ pyproj = [ {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f80adda8c54b84271a93829477a01aa57bc178c834362e9f74e1de1b5033c74c"}, {file = "pyproj-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:221d8939685e0c43ee594c9f04b6a73a10e8e1cc0e85f28be0b4eb2f1bc8777d"}, {file = "pyproj-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d94afed99f31673d3d19fe750283621e193e2a53ca9e0443bf9d092c3905833b"}, + {file = "pyproj-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fff9c3a991508f16027be27d153f6c5583d03799443639d13c681e60f49e2d7"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b85acf09e5a9e35cd9ee72989793adb7089b4e611be02a43d3d0bda50ad116b"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45554f47d1a12a84b0620e4abc08a2a1b5d9f273a4759eaef75e74788ec7162a"}, {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12f62c20656ac9b6076ebb213e9a635d52f4f01fef95310121d337e62e910cb6"}, diff --git a/pyproject.toml b/pyproject.toml index c890b78875..d26fc7ff1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,6 +147,7 @@ mpld3 = "^0.5.8" msgpack-python = "^0.5.6" networkx = "~2.3" nvidia-ml-py3 = "^7.352.0" +onnx2torch = "^1.5.4" onnxoptimizer = "^0.3.1" opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" } osmium = "^3.3.0"