September 27th, 2025 Update

This commit is contained in:
James
2025-09-27 12:00:00 -07:00
parent 73821d9482
commit a759abb082
1166 changed files with 228204 additions and 267090 deletions
+9 -5
View File
@@ -2,7 +2,9 @@ import os
import time
from collections.abc import Callable
from cereal import car
import openpilot.system.sentry as sentry
from cereal import car, custom
from openpilot.common.params import Params
from openpilot.selfdrive.car.interfaces import get_interface_attr
from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
@@ -17,10 +19,11 @@ from openpilot.system.version import get_build_metadata
FRAME_FINGERPRINT = 100 # 1s
EventName = car.CarEvent.EventName
FrogPilotEventName = custom.FrogPilotCarEvent.EventName
def get_startup_event(car_recognized, controller_available, fw_seen):
event = EventName.customStartupAlert
event = FrogPilotEventName.customStartupAlert
if not car_recognized:
if fw_seen:
@@ -200,11 +203,12 @@ def get_car(logcan, sendcan, experimental_long_allowed, params, num_pandas=1, fr
params.put_nonblocking("CarModel", candidate)
if frogpilot_toggles.block_user:
candidate = "MOCK"
candidate = MOCK.MOCK
sentry.capture_block()
CarInterface, _, _ = interfaces[candidate]
CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long_allowed, frogpilot_toggles, params, docs=False)
FPCP = CarInterface.get_frogpilot_params(candidate, fingerprints, car_fw, frogpilot_toggles)
CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long_allowed, frogpilot_toggles, docs=False)
FPCP = CarInterface.get_frogpilot_params(candidate, fingerprints, car_fw, CP, frogpilot_toggles)
CP.carVin = vin
CP.carFw = car_fw
CP.fingerprintSource = source
+3 -6
View File
@@ -30,7 +30,7 @@ class Car:
def __init__(self, CI=None):
self.can_sock = messaging.sub_sock('can', timeout=20)
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'liveCalibration', 'onroadEvents', 'frogpilotPlan'])
self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'liveCalibration', 'onroadEvents', 'frogpilotOnroadEvents', 'frogpilotPlan'])
self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'frogpilotCarState'])
self.can_rcv_cum_timeout_counter = 0
@@ -62,9 +62,9 @@ class Car:
openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly
controller_available = self.CI.CC is not None and openpilot_enabled_toggle
self.CP.passive = not controller_available or self.CP.dashcamOnly
self.CP.passive = not controller_available
if self.CP.passive:
safety_config = car.CarParams.SafetyConfig.new_message()
safety_config.safetyModel = car.CarParams.SafetyModel.noOutput
@@ -106,9 +106,6 @@ class Car:
self.frogpilot_toggles = get_frogpilot_toggles()
if self.frogpilot_toggles.acceleration_profile == 3:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX
if self.frogpilot_toggles.always_on_lateral:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
+1
View File
@@ -13,6 +13,7 @@ class CarInterface(CarInterfaceBase):
@staticmethod
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs, frogpilot_toggles):
ret.carName = "chrysler"
ret.dashcamOnly = candidate in RAM_HD
# radar parsing needs some work, see https://github.com/commaai/openpilot/issues/26842
ret.radarUnavailable = True # DBC[candidate]['radar'] is None
+1 -7
View File
@@ -7,9 +7,6 @@ from openpilot.selfdrive.car.ford.values import CarControllerParams, FordFlags
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX
from openpilot.selfdrive.car.interfaces import get_max_allowed_accel
GearShifter = car.CarState.GearShifter
LongCtrlState = car.CarControl.Actuators.LongControlState
VisualAlert = car.CarControl.HUDControl.VisualAlert
@@ -97,10 +94,7 @@ class CarController(CarControllerBase):
# send acc msg at 50Hz
if self.CP.openpilotLongitudinalControl and (self.frame % CarControllerParams.ACC_CONTROL_STEP) == 0:
# Both gas and accel are in m/s^2, accel is used solely for braking
if frogpilot_toggles.sport_plus and (CS.out.gearShifter == GearShifter.sport or not frogpilot_toggles.map_acceleration):
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo))
else:
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
gas = accel
if not CC.longActive or gas < CarControllerParams.MIN_GAS:
gas = CarControllerParams.INACTIVE_GAS
+1
View File
@@ -16,6 +16,7 @@ class CarInterface(CarInterfaceBase):
@staticmethod
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs, frogpilot_toggles):
ret.carName = "ford"
ret.dashcamOnly = bool(ret.flags & FordFlags.CANFD)
ret.radarUnavailable = True
ret.steerControlType = car.CarParams.SteerControlType.angle
+6 -11
View File
@@ -7,7 +7,7 @@ from openpilot.common.params_pyx import Params
from opendbc.can.packer import CANPacker
from openpilot.selfdrive.car import apply_driver_steer_torque_limits, create_gas_interceptor_command
from openpilot.selfdrive.car.gm import gmcan
from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, GMFlags, CC_ONLY_CAR, SDGM_CAR, EV_CAR, AccState
from openpilot.selfdrive.car.gm.values import DBC, AccState, CanBus, CarControllerParams, CruiseButtons, GMFlags, CC_ONLY_CAR, SDGM_CAR, EV_CAR
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.controls.lib.drive_helpers import apply_deadzone
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
@@ -24,9 +24,9 @@ CAMERA_CANCEL_DELAY_FRAMES = 10
MIN_STEER_MSG_INTERVAL_MS = 15
# Constants for pitch compensation
PITCH_DEADZONE = 0.01 # [radians] 0.01 ≈ 1% grade
BRAKE_PITCH_FACTOR_BP = [5., 10.] # [m/s] smoothly revert to planned accel at low speeds
BRAKE_PITCH_FACTOR_V = [0., 1.] # [unitless in [0,1]]; don't touch
PITCH_DEADZONE = 0.01 # [radians] 0.01 ≈ 1% grade
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
@@ -53,9 +53,10 @@ class CarController(CarControllerBase):
self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis'])
# FrogPilot variables
self.pitch = FirstOrderFilter(0., 0.09 * 4, DT_CTRL * 4) # runs at 25 Hz
self.accel_g = 0.0
self.pitch = FirstOrderFilter(0., 0.09 * 4, DT_CTRL * 4) # runs at 25 Hz
@staticmethod
def calc_pedal_command(accel: float, long_active: bool) -> float:
if not long_active: return 0.
@@ -143,16 +144,10 @@ class CarController(CarControllerBase):
# Normal operation
if self.CP.carFingerprint in EV_CAR:
self.params.update_ev_gas_brake_threshold(CS.out.vEgo)
if frogpilot_toggles.sport_plus and (CS.out.gearShifter == GearShifter.sport or not frogpilot_toggles.map_acceleration):
self.apply_gas = int(round(interp(accel, self.params.EV_GAS_LOOKUP_BP_PLUS, self.params.GAS_LOOKUP_V_PLUS)))
else:
self.apply_gas = int(round(interp(accel, self.params.EV_GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
self.apply_gas = int(round(interp(accel, self.params.EV_GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
self.apply_brake = int(round(interp(brake_accel, self.params.EV_BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
else:
if frogpilot_toggles.sport_plus and (CS.out.gearShifter == GearShifter.sport or not frogpilot_toggles.map_acceleration):
self.apply_gas = int(round(interp(accel, self.params.GAS_LOOKUP_BP_PLUS, self.params.GAS_LOOKUP_V_PLUS)))
else:
self.apply_gas = int(round(interp(accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
self.apply_gas = int(round(interp(accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
self.apply_brake = int(round(interp(brake_accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
# Don't allow any gas above inactive regen while stopping
# FIXME: brakes aren't applied immediately when enabling at a stop
+36 -39
View File
@@ -1,20 +1,20 @@
#!/usr/bin/env python3
import os
from cereal import car, custom
from math import fabs, exp
import numpy as np
from panda import Panda
from openpilot.common.basedir import BASEDIR
from openpilot.common.conversions import Conversions as CV
from openpilot.selfdrive.car import create_button_events, get_safety_config
from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG
from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus, GMFlags, CC_ONLY_CAR, SDGM_CAR
from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel
from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LateralAccelFromTorqueCallbackType
from openpilot.selfdrive.controls.lib.drive_helpers import get_friction
ButtonType = car.CarState.ButtonEvent.Type
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
FrogPilotEventName = custom.FrogPilotCarEvent.EventName
GearShifter = car.CarState.GearShifter
TransmissionType = car.CarParams.TransmissionType
NetworkLocation = car.CarParams.NetworkLocation
@@ -33,8 +33,6 @@ NON_LINEAR_TORQUE_PARAMS = {
CAR.CHEVROLET_SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122]
}
NEURAL_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/neural_ff_weights.json')
class CarInterface(CarInterfaceBase):
@staticmethod
@@ -54,45 +52,45 @@ class CarInterface(CarInterfaceBase):
else:
return CarInterfaceBase.get_steer_feedforward_default
def torque_from_lateral_accel_siglin(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, lateral_accel_error: float,
lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float:
friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation)
def get_lataccel_torque_siglin(self) -> float:
def sig(val):
# https://timvieira.github.io/blog/post/2014/02/11/exp-normalize-trick
if val >= 0:
return 1 / (1 + exp(-val)) - 0.5
else:
z = exp(val)
return z / (1 + z) - 0.5
def torque_from_lateral_accel_siglin_func(lateral_acceleration: float) -> float:
# The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
# This has big effect on the stability about 0 (noise when going straight)
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
assert non_linear_torque_params, "The params are not defined"
a, b, c, _ = non_linear_torque_params
sig_input = a * lateral_acceleration
sig = np.sign(sig_input) * (1 / (1 + exp(-fabs(sig_input))) - 0.5)
steer_torque = (sig * b) + (lateral_acceleration * c)
return float(steer_torque)
# The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
# This has big effect on the stability about 0 (noise when going straight)
# ToDo: To generalize to other GMs, explore tanh function as the nonlinear
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
assert non_linear_torque_params, "The params are not defined"
a, b, c, _ = non_linear_torque_params
steer_torque = (sig(latcontrol_inputs.lateral_acceleration * a) * b) + (latcontrol_inputs.lateral_acceleration * c)
return float(steer_torque) + friction
def torque_from_lateral_accel_neural(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, lateral_accel_error: float,
lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float:
friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation)
inputs = list(latcontrol_inputs)
if gravity_adjusted:
inputs[0] += inputs[1]
return float(self.neural_ff_model.predict(inputs)) + friction
lataccel_values = np.arange(-5.0, 5.0, 0.01)
torque_values = [torque_from_lateral_accel_siglin_func(x) for x in lataccel_values]
assert min(torque_values) < -1 and max(torque_values) > 1, "The torque values should cover the range [-1, 1]"
return torque_values, lataccel_values
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
if self.CP.carFingerprint in (CAR.CHEVROLET_BOLT_EUV, CAR.CHEVROLET_BOLT_CC):
self.neural_ff_model = NanoFFModel(NEURAL_PARAMS_PATH, self.CP.carFingerprint)
return self.torque_from_lateral_accel_neural
elif self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
return self.torque_from_lateral_accel_siglin
if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
torque_values, lataccel_values = self.get_lataccel_torque_siglin()
def torque_from_lateral_accel_siglin(lateral_acceleration: float, torque_params: car.CarParams.LateralTorqueTuning):
return np.interp(lateral_acceleration, lataccel_values, torque_values)
return torque_from_lateral_accel_siglin
else:
return self.torque_from_lateral_accel_linear
def lateral_accel_from_torque(self) -> LateralAccelFromTorqueCallbackType:
if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
torque_values, lataccel_values = self.get_lataccel_torque_siglin()
def lateral_accel_from_torque_siglin(torque: float, torque_params: car.CarParams.LateralTorqueTuning):
return np.interp(torque, torque_values, lataccel_values)
return lateral_accel_from_torque_siglin
else:
return self.lateral_accel_from_torque_linear
@staticmethod
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs, frogpilot_toggles):
ret.carName = "gm"
@@ -347,7 +345,6 @@ class CarInterface(CarInterfaceBase):
events.add(EventName.belowEngageSpeed)
if ret.cruiseState.standstill and not self.CP.autoResumeSng:
events.add(EventName.resumeRequired)
if ret.vEgo < self.CP.minSteerSpeed:
events.add(EventName.belowSteerSpeed)
@@ -358,7 +355,7 @@ class CarInterface(CarInterfaceBase):
self.CP.transmissionType == TransmissionType.direct and \
not self.CS.single_pedal_mode and \
c.longActive:
events.add(EventName.pedalInterceptorNoBrake)
events.add(FrogPilotEventName.pedalInterceptorNoBrake)
ret.events = events.to_msg()
+16 -23
View File
@@ -33,45 +33,39 @@ class CarControllerParams:
# Our controller should still keep the 2 second average above
# -3.5 m/s^2 as per planner limits
ACCEL_MAX = 2. # m/s^2
ACCEL_MAX_PLUS = 4. # m/s^2
ACCEL_MIN = -4. # m/s^2
def __init__(self, CP):
# Gas/brake lookups
self.ZERO_GAS = 6144 # Coasting
self.ZERO_GAS = 2048 # Coasting
self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen
if CP.carFingerprint in CAMERA_ACC_CAR and CP.carFingerprint not in CC_ONLY_CAR:
self.MAX_GAS = 7496
self.MAX_GAS_PLUS = 8848
self.MAX_ACC_REGEN = 5610
self.INACTIVE_REGEN = 5650
self.MAX_GAS = 3400
self.MAX_ACC_REGEN = 1514
self.INACTIVE_REGEN = 1554
# Camera ACC vehicles have no regen while enabled.
# Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly
self.max_regen_acceleration = 0.
max_regen_acceleration = 0.
elif CP.carFingerprint in SDGM_CAR:
self.MAX_GAS = 7496
self.MAX_GAS_PLUS = 7496
self.MAX_ACC_REGEN = 5610
self.INACTIVE_REGEN = 5650
self.max_regen_acceleration = 0.
self.MAX_GAS = 3400
self.MAX_ACC_REGEN = 1514
self.INACTIVE_REGEN = 1554
max_regen_acceleration = 0.
else:
self.MAX_GAS = 7168 # Safety limit, not ACC max. Stock ACC >8192 from standstill.
self.MAX_GAS_PLUS = 8191 # 8292 uses new bit, possible but not tested. Matches Twilsonco tw-main max
self.MAX_ACC_REGEN = 5500 # Max ACC regen is slightly less than max paddle regen
self.INACTIVE_REGEN = 5500
self.MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill.
self.MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen
self.INACTIVE_REGEN = 1404
# ICE has much less engine braking force compared to regen in EVs,
# lower threshold removes some braking deadzone
self.max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1
max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1
self.GAS_LOOKUP_BP = [self.max_regen_acceleration, 0., self.ACCEL_MAX]
self.GAS_LOOKUP_BP_PLUS = [self.max_regen_acceleration, 0., self.ACCEL_MAX_PLUS]
self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX]
self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS]
self.GAS_LOOKUP_V_PLUS = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS_PLUS]
self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, self.max_regen_acceleration]
self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration]
self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.]
# determined by letting Volt regen to a stop in L gear from 89mph,
@@ -82,11 +76,10 @@ class CarControllerParams:
def update_ev_gas_brake_threshold(self, v_ego):
gas_brake_threshold = interp(v_ego, self.EV_GAS_BRAKE_THRESHOLD_BP, self.EV_GAS_BRAKE_THRESHOLD_V)
self.GAS_LOOKUP_BP_PLUS = [self.max_regen_acceleration, 0., self.ACCEL_MAX_PLUS]
self.EV_GAS_LOOKUP_BP = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX]
self.EV_GAS_LOOKUP_BP_PLUS = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX_PLUS]
self.EV_BRAKE_LOOKUP_BP = [self.ACCEL_MIN, gas_brake_threshold]
@dataclass
class GMCarDocs(CarDocs):
package: str = "Adaptive Cruise Control (ACC)"
+1 -7
View File
@@ -10,9 +10,6 @@ from openpilot.selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HOND
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.controls.lib.drive_helpers import rate_limit
from openpilot.selfdrive.car.interfaces import get_max_allowed_accel
GearShifter = car.CarState.GearShifter
VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState
@@ -219,10 +216,7 @@ class CarController(CarControllerBase):
ts = self.frame * DT_CTRL
if self.CP.carFingerprint in HONDA_BOSCH:
if frogpilot_toggles.sport_plus and (CS.out.gearShifter == GearShifter.sport or not frogpilot_toggles.map_acceleration):
self.accel = clip(accel, self.params.BOSCH_ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo))
else:
self.accel = clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX)
self.accel = clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX)
self.gas = interp(accel, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V)
stopping = actuators.longControlState == LongCtrlState.stopping
-4
View File
@@ -199,7 +199,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] # TODO: can probably use some tuning
elif candidate == CAR.HONDA_CLARITY:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_CLARITY
if eps_modified:
for fw in car_fw:
if fw.ecu == "eps" and b"-" not in fw.fwVersion and b"," in fw.fwVersion:
@@ -231,9 +230,6 @@ class CarInterface(CarInterfaceBase):
if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_BOSCH_LONG
if ret.enableGasInterceptor and candidate not in HONDA_BOSCH:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_GAS_INTERCEPTOR
if candidate in HONDA_BOSCH_RADARLESS:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_RADARLESS
+1 -7
View File
@@ -9,9 +9,6 @@ from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.car.interfaces import get_max_allowed_accel
GearShifter = car.CarState.GearShifter
VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState
@@ -84,10 +81,7 @@ class CarController(CarControllerBase):
self.apply_steer_last = apply_steer
# accel + longitudinal
if frogpilot_toggles.sport_plus and (CS.out.gearShifter == GearShifter.sport or not frogpilot_toggles.map_acceleration):
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo))
else:
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
stopping = actuators.longControlState == LongCtrlState.stopping
set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_KPH if CS.is_metric else CV.MS_TO_MPH)
-2
View File
@@ -23,14 +23,12 @@ def calculate_speed_limit(CP, FPCP, cp, cp_cam):
speed_limit_bus = cp
else:
speed_limit_bus = cp_cam
speed_limit = speed_limit_bus.vl["CLUSTER_SPEED_LIMIT"]["SPEED_LIMIT_1"]
else:
if FPCP.fpFlags & HyundaiFrogPilotFlags.LKAS12:
speed_limit = cp_cam.vl["LKAS12"]["CF_Lkas_TsrSpeed_Display_Clu"]
else:
speed_limit = 0
if speed_limit in (0, 255) and FPCP.fpFlags & HyundaiFrogPilotFlags.NAV_MSG:
speed_limit = cp.vl["Navi_HU"]["SpeedLim_Nav_Clu"]
+2 -10
View File
@@ -22,8 +22,6 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu
class CarInterface(CarInterfaceBase):
@staticmethod
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs, frogpilot_toggles):
use_old_long = frogpilot_toggles.old_long_api
ret.carName = "hyundai"
ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None
@@ -84,14 +82,14 @@ class CarInterface(CarInterfaceBase):
# *** longitudinal control ***
if candidate in CANFD_CAR:
if use_old_long:
if frogpilot_toggles.old_long_api:
ret.longitudinalTuning.deadzoneBP = [0.]
ret.longitudinalTuning.deadzoneV = [0.]
ret.longitudinalTuning.kpV = [0.1]
ret.longitudinalTuning.kiV = [0.0]
ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR)
else:
if use_old_long:
if frogpilot_toggles.old_long_api:
ret.longitudinalTuning.deadzoneBP = [0.]
ret.longitudinalTuning.deadzoneV = [0.]
ret.longitudinalTuning.kpV = [0.5]
@@ -137,9 +135,6 @@ class CarInterface(CarInterfaceBase):
if candidate in CAMERA_SCC_CAR:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
if 0x391 in fingerprint[0]:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_LFA_BTN
if ret.openpilotLongitudinalControl:
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG
if ret.flags & HyundaiFlags.HYBRID:
@@ -157,9 +152,6 @@ class CarInterface(CarInterfaceBase):
if 0x2AA in fingerprint[0]:
ret.minSteerSpeed = 0.
if frogpilot_toggles.taco_tune_hacks:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_TACO_TUNE_HACK
return ret
@staticmethod
+50 -59
View File
@@ -1,10 +1,9 @@
import json
import os
import numpy as np
import tomllib
from abc import abstractmethod, ABC
from enum import StrEnum
from typing import Any, NamedTuple
from typing import Any
from collections.abc import Callable
from functools import cache
from types import SimpleNamespace
@@ -12,23 +11,22 @@ from types import SimpleNamespace
from cereal import car, custom
from openpilot.common.basedir import BASEDIR
from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params
from openpilot.common.simple_kalman import KF1D, get_kalman_gain
from openpilot.common.numpy_fast import clip
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG
from openpilot.selfdrive.car.chrysler.values import CAR as ChryslerCAR, ChryslerFrogPilotFlags
from openpilot.selfdrive.car.gm.values import CAR as GMCAR
from openpilot.selfdrive.car.honda.values import CAR as HondaCAR, HONDA_BOSCH
from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus
from openpilot.selfdrive.car.hyundai.values import CAR as HyundaiCAR, CANFD_CAR, HyundaiFrogPilotFlags
from openpilot.selfdrive.car.mock.values import CAR as MockCAR
from openpilot.selfdrive.car.toyota.values import CAR as ToyotaCAR, ToyotaFrogPilotFlags
from openpilot.selfdrive.car.toyota.values import CAR as ToyotaCAR, TSS2_CAR, UNSUPPORTED_DSU_CAR, ToyotaFrogPilotFlags
from openpilot.selfdrive.car.values import PLATFORMS
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, get_friction
from openpilot.selfdrive.controls.lib.events import Events
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel
def get_max_allowed_accel(v_ego):
return float(np.interp(v_ego, [0., 5., 20.], [4.0, 4.0, 2.0])) # ISO 15622:2018
from panda import Panda
ButtonType = car.CarState.ButtonEvent.Type
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
@@ -57,15 +55,8 @@ GEAR_SHIFTER_MAP: dict[str, car.CarState.GearShifter] = {
'B': GearShifter.brake, 'BRAKE': GearShifter.brake,
}
class LatControlInputs(NamedTuple):
lateral_acceleration: float
roll_compensation: float
vego: float
aego: float
TorqueFromLateralAccelCallbackType = Callable[[LatControlInputs, car.CarParams.LateralTorqueTuning, float, float, bool, bool], float]
TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, bool], float]
LateralAccelFromTorqueCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, bool], float]
@cache
@@ -140,7 +131,7 @@ class CarInterfaceBase(ABC):
return cls.get_params(candidate, gen_empty_fingerprint(), list(), False, False, False)
@classmethod
def get_params(cls, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], experimental_long: bool, frogpilot_toggles: SimpleNamespace, params: Params, docs: bool):
def get_params(cls, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], experimental_long: bool, frogpilot_toggles: SimpleNamespace, docs: bool):
ret = CarInterfaceBase.get_std_params(candidate)
platform = PLATFORMS[candidate]
@@ -163,25 +154,35 @@ class CarInterfaceBase(ABC):
ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, ret.tireStiffnessFactor)
# Enable torque controller for all cars that do not use angle based steering
if ret.steerControlType != car.CarParams.SteerControlType.angle and params.get_bool("LateralTune") and (params.get_bool("NNFF") or params.get_bool("NNFFLite")):
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
return ret
@classmethod
def get_frogpilot_params(cls, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], frogpilot_toggles: SimpleNamespace):
def get_frogpilot_params(cls, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], CP, frogpilot_toggles: SimpleNamespace):
fp_ret = custom.FrogPilotCarParams.new_message()
platform = PLATFORMS[candidate]
fp_ret.fpFlags |= int(platform.config.flags)
fp_ret.safetyConfigs = [custom.FrogPilotCarParams.SafetyConfig.new_message()]
if platform not in MockCAR:
if platform in ChryslerCAR:
if candidate == ChryslerCAR.RAM_HD_5TH_GEN:
if 570 not in fingerprint[0]:
fp_ret.fpFlags |= ChryslerFrogPilotFlags.RAM_HD_ALT_BUTTONS.value
elif platform in GMCAR:
fp_ret.canUsePedal = True
elif platform in HondaCAR:
if candidate == HondaCAR.HONDA_CLARITY:
fp_ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_CLARITY
if CP.enableGasInterceptor and candidate not in HONDA_BOSCH:
fp_ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_GAS_INTERCEPTOR
fp_ret.canUsePedal = candidate not in HONDA_BOSCH
elif platform in HyundaiCAR:
if candidate in CANFD_CAR:
hda2 = Ecu.adas in [fw.ecu for fw in car_fw]
@@ -190,9 +191,13 @@ class CarInterfaceBase(ABC):
fp_ret.fpFlags |= HyundaiFrogPilotFlags.NAV_MSG.value
fp_ret.isHDA2 = hda2
if frogpilot_toggles.taco_tune_hacks:
fp_ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_TACO_TUNE_HACK
else:
if 0x391 in fingerprint[0]:
fp_ret.fpFlags |= HyundaiFrogPilotFlags.CAN_LFA_BTN.value
fp_ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_LFA_BTN
if 0x53E in fingerprint[2]:
fp_ret.fpFlags |= HyundaiFrogPilotFlags.LKAS12.value
@@ -205,6 +210,20 @@ class CarInterfaceBase(ABC):
if 0x23 in fingerprint[0]:
fp_ret.fpFlags |= ToyotaFrogPilotFlags.ZSS.value
if CP.enableGasInterceptor:
fp_ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_GAS_INTERCEPTOR
fp_ret.canUsePedal = not CP.autoResumeSng
fp_ret.canUseSDSU = not CP.enableDsu and candidate not in UNSUPPORTED_DSU_CAR and candidate not in TSS2_CAR
if CP.steerControlType != car.CarParams.SteerControlType.angle:
if CP.lateralTuning.which() == "pid" and (frogpilot_toggles.force_torque_controller or frogpilot_toggles.nnff or frogpilot_toggles.nnff_lite):
CarInterfaceBase.configure_torque_tune(candidate, fp_ret.lateralTuning)
elif CP.lateralTuning.which() == "torque":
CarInterfaceBase.configure_torque_tune(candidate, fp_ret.lateralTuning)
else:
fp_ret.lateralTuning.init("pid")
fp_ret.openpilotLongitudinalControlDisabled = frogpilot_toggles.disable_openpilot_long
return fp_ret
@@ -228,15 +247,19 @@ class CarInterfaceBase(ABC):
def get_steer_feedforward_function(self):
return self.get_steer_feedforward_default
def torque_from_lateral_accel_linear(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning,
lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float:
def torque_from_lateral_accel_linear(self, lateral_acceleration: float, torque_params: car.CarParams.LateralTorqueTuning) -> float:
# The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction)
friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation)
return (latcontrol_inputs.lateral_acceleration / float(torque_params.latAccelFactor)) + friction
return lateral_acceleration / float(torque_params.latAccelFactor)
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
return self.torque_from_lateral_accel_linear
def lateral_accel_from_torque_linear(self, torque: float, torque_params: car.CarParams.LateralTorqueTuning) -> float:
return torque * float(torque_params.latAccelFactor)
def lateral_accel_from_torque(self) -> LateralAccelFromTorqueCallbackType:
return self.lateral_accel_from_torque_linear
# returns a set of default params to avoid repetition in car specific params
@staticmethod
def get_std_params(candidate):
@@ -278,8 +301,8 @@ class CarInterfaceBase(ABC):
tune.init('torque')
tune.torque.useSteeringAngle = use_steering_angle
tune.torque.kp = 1.0
tune.torque.kf = 1.0
tune.torque.kp = 1.0
tune.torque.ki = 0.3
tune.torque.friction = params['FRICTION']
tune.torque.latAccelFactor = params['LAT_ACCEL_FACTOR']
@@ -582,35 +605,3 @@ def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: boo
pass
return result
class NanoFFModel:
def __init__(self, weights_loc: str, platform: str):
self.weights_loc = weights_loc
self.platform = platform
self.load_weights(platform)
def load_weights(self, platform: str):
with open(self.weights_loc) as fob:
self.weights = {k: np.array(v) for k, v in json.load(fob)[platform].items()}
def relu(self, x: np.ndarray):
return np.maximum(0.0, x)
def forward(self, x: np.ndarray):
assert x.ndim == 1
x = (x - self.weights['input_norm_mat'][:, 0]) / (self.weights['input_norm_mat'][:, 1] - self.weights['input_norm_mat'][:, 0])
x = self.relu(np.dot(x, self.weights['w_1']) + self.weights['b_1'])
x = self.relu(np.dot(x, self.weights['w_2']) + self.weights['b_2'])
x = self.relu(np.dot(x, self.weights['w_3']) + self.weights['b_3'])
x = np.dot(x, self.weights['w_4']) + self.weights['b_4']
return x
def predict(self, x: list[float], do_sample: bool = False):
x = self.forward(np.array(x))
if do_sample:
pred = np.random.laplace(x[0], np.exp(x[1]) / self.weights['temperature'])
else:
pred = x[0]
pred = pred * (self.weights['output_norm_mat'][1] - self.weights['output_norm_mat'][0]) + self.weights['output_norm_mat'][0]
return pred
+2
View File
@@ -17,6 +17,8 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)]
ret.radarUnavailable = True
ret.dashcamOnly = candidate not in (CAR.MAZDA_CX5_2022, CAR.MAZDA_CX9_2021)
ret.steerActuatorDelay = 0.1
ret.steerLimitTimer = 0.8
+1 -1
View File
@@ -1,5 +1,5 @@
from openpilot.selfdrive.car.interfaces import CarControllerBase
class CarController(CarControllerBase):
def update(self, CC, CS, now_nanos):
def update(self, CC, CS, now_nanos, frogpilot_toggles):
return CC.actuators.as_builder(), []
+2
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
from cereal import car, custom
import cereal.messaging as messaging
from openpilot.selfdrive.car import get_safety_config
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
# mocked car interface for dashcam mode
@@ -19,6 +20,7 @@ class CarInterface(CarInterfaceBase):
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 13.
ret.dashcamOnly = True
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput)]
return ret
def _update(self, c, frogpilot_toggles):
+1 -1
View File
@@ -4,7 +4,7 @@ from opendbc.can.can_define import CANDefine
from openpilot.common.conversions import Conversions as CV
from openpilot.selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
from openpilot.selfdrive.car.subaru.values import DBC, CanBus, PREGLOBAL_CARS, SubaruFlags
from openpilot.selfdrive.car.subaru.values import DBC, PREGLOBAL_CARS, CanBus, SubaruFlags
from openpilot.selfdrive.car import CanSignalRateCalculator
+1 -1
View File
@@ -17,7 +17,7 @@ class CarInterface(CarInterfaceBase):
# - replacement for ES_Distance so we can cancel the cruise control
# - to find the Cruise_Activated bit from the car
# - proper panda safety setup (use the correct cruise_activated bit, throttle from Throttle_Hybrid, etc)
ret.dashcamOnly = bool(ret.flags & (SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
ret.dashcamOnly = bool(ret.flags & (SubaruFlags.PREGLOBAL | SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
ret.autoResumeSng = False
# Detect infotainment message sent from the camera
+1 -1
View File
@@ -20,7 +20,7 @@ class CarControllerParams:
self.STEER_DRIVER_FACTOR = 1 # from dbc
if CP.flags & SubaruFlags.GLOBAL_GEN2:
self.STEER_MAX = 1000
self.STEER_MAX = 1600
self.STEER_DELTA_UP = 40
self.STEER_DELTA_DOWN = 40
elif CP.carFingerprint == CAR.SUBARU_IMPREZA_2020:
File diff suppressed because one or more lines are too long
+15 -13
View File
@@ -15,8 +15,6 @@ from openpilot.selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_
from openpilot.selfdrive.controls.lib.pid import PIDController
from opendbc.can.packer import CANPacker
from openpilot.selfdrive.car.interfaces import get_max_allowed_accel
GearShifter = car.CarState.GearShifter
LongCtrlState = car.CarControl.Actuators.LongControlState
SteerControlType = car.CarParams.SteerControlType
@@ -30,6 +28,8 @@ ACCEL_WINDUP_LIMIT = 4.0 * DT_CTRL * 3 # m/s^2 / frame
ACCEL_WINDDOWN_LIMIT = -4.0 * DT_CTRL * 3 # m/s^2 / frame
ACCEL_PID_UNWIND = 0.03 * DT_CTRL * 3 # m/s^2 / frame
MAX_PITCH_COMPENSATION = 1.5 # m/s^2
# LKA limits
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
MAX_STEER_RATE = 100 # deg/s
@@ -76,7 +76,8 @@ class CarController(CarControllerBase):
self.long_pid = get_long_tune(self.CP, self.params)
self.aego = FirstOrderFilter(0.0, 0.25, DT_CTRL * 3)
self.pitch = FirstOrderFilter(0, 0.5, DT_CTRL)
self.pitch = FirstOrderFilter(0, 0.25, DT_CTRL)
self.pitch_slow = FirstOrderFilter(0, 1.5, DT_CTRL)
self.accel = 0
self.prev_accel = 0
@@ -93,16 +94,7 @@ class CarController(CarControllerBase):
# FrogPilot variables
self.doors_locked = False
self.stock_max_accel = self.params.ACCEL_MAX
def update(self, CC, CS, now_nanos, frogpilot_toggles):
if frogpilot_toggles.sport_plus and (CS.out.gearShifter == GearShifter.sport or not frogpilot_toggles.map_acceleration):
self.params.ACCEL_MAX = get_max_allowed_accel(CS.out.vEgo)
self.long_pid.pos_limit = self.params.ACCEL_MAX
else:
self.params.ACCEL_MAX = self.stock_max_accel
self.long_pid.pos_limit = self.params.ACCEL_MAX
actuators = CC.actuators
stopping = actuators.longControlState == LongCtrlState.stopping
hud_control = CC.hudControl
@@ -111,6 +103,7 @@ class CarController(CarControllerBase):
if len(CC.orientationNED) == 3:
self.pitch.update(CC.orientationNED[1])
self.pitch_slow.update(CC.orientationNED[1])
# *** control msgs ***
can_sends = []
@@ -276,6 +269,15 @@ class CarController(CarControllerBase):
self.long_pid.i -= ACCEL_PID_UNWIND * float(np.sign(self.long_pid.i))
error_future = pcm_accel_cmd - a_ego_future
if not stopping:
# Toyota's PCM slowly responds to changes in pitch. On change, we amplify our
# acceleration request to compensate for the undershoot and following overshoot
high_pass_pitch = self.pitch.x - self.pitch_slow.x
pitch_compensation = float(np.clip(math.sin(high_pass_pitch) * ACCELERATION_DUE_TO_GRAVITY,
-MAX_PITCH_COMPENSATION, MAX_PITCH_COMPENSATION))
pcm_accel_cmd += pitch_compensation
pcm_accel_cmd = self.long_pid.update(error_future,
speed=CS.out.vEgo,
feedforward=pcm_accel_cmd,
@@ -355,7 +357,7 @@ class CarController(CarControllerBase):
new_actuators.steer = apply_steer / self.params.STEER_MAX
new_actuators.steerOutputCan = apply_steer
new_actuators.steeringAngleDeg = self.last_angle
new_actuators.accel = self.accel
new_actuators.accel = float(self.accel)
# FrogPilot Toyota carcontroller functions
if not self.doors_locked and CS.out.gearShifter != PARK:
-3
View File
@@ -131,9 +131,6 @@ class CarInterface(CarInterfaceBase):
if not ret.openpilotLongitudinalControl:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL
if ret.enableGasInterceptor:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_GAS_INTERCEPTOR
# min speed to enable ACC. if car can do stop and go, then set enabling speed
# to a negative value, so it won't matter.
ret.minEnableSpeed = -1. if (candidate in STOP_AND_GO_CAR or ret.enableGasInterceptor) else MIN_ACC_SPEED
+1 -1
View File
@@ -59,7 +59,7 @@ class ToyotaFlags(IntFlag):
# these cars use the Lane Tracing Assist (LTA) message for lateral control
ANGLE_CONTROL = 128
NO_STOP_TIMER = 256
# these cars are speculated to allow stop and go when the DSU is unplugged
# these cars are speculated to allow stop and go when the DSU is unplugged or disabled with sDSU
SNG_WITHOUT_DSU = 512
# these cars can utilize 2.0 m/s^2
RAISED_ACCEL_LIMIT = 2048
+1 -7
View File
@@ -8,9 +8,6 @@ from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.car.volkswagen import mqbcan, pqcan
from openpilot.selfdrive.car.volkswagen.values import CANBUS, CarControllerParams, VolkswagenFlags
from openpilot.selfdrive.car.interfaces import get_max_allowed_accel
GearShifter = car.CarState.GearShifter
VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState
@@ -83,10 +80,7 @@ class CarController(CarControllerBase):
if self.frame % self.CCP.ACC_CONTROL_STEP == 0 and self.CP.openpilotLongitudinalControl:
acc_control = self.CCS.acc_control_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
if frogpilot_toggles.sport_plus and (CS.out.gearShifter == GearShifter.sport or not frogpilot_toggles.map_acceleration):
accel = clip(actuators.accel, self.CCP.ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo)) if CC.longActive else 0
else:
accel = clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0
accel = clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0
stopping = actuators.longControlState == LongCtrlState.stopping
starting = actuators.longControlState == LongCtrlState.pid and (CS.esp_hold_confirmation or CS.out.vEgo < frogpilot_toggles.vEgoStopping)
can_sends.extend(self.CCS.create_acc_accel_control(self.packer_pt, CANBUS.pt, CS.acc_type, CC.longActive, accel,
+119 -79
View File
@@ -33,7 +33,6 @@ from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel
from openpilot.system.hardware import HARDWARE
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles, params_memory
from openpilot.frogpilot.controls.lib.frogpilot_acceleration import get_max_allowed_accel
SOFT_DISABLE_TIME = 3 # seconds
LDW_MIN_SPEED = 31 * CV.MPH_TO_MS
@@ -52,6 +51,7 @@ Desire = log.Desire
LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection
EventName = car.CarEvent.EventName
FrogPilotEventName = custom.FrogPilotCarEvent.EventName
ButtonType = car.CarState.ButtonEvent.Type
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
@@ -74,11 +74,11 @@ class Controls:
self.CP = msg.as_builder()
cloudlog.info("controlsd got CarParams")
with custom.FrogPilotCarParams.from_bytes(self.params.get("FrogPilotCarParamsPersistent", block=True)) as fpmsg:
FPCP = fpmsg.as_builder()
with custom.FrogPilotCarParams.from_bytes(self.params.get("FrogPilotCarParams", block=True)) as fpmsg:
self.FPCP = fpmsg.as_builder()
# Uses car interface helper functions, altering state won't be considered by card for actuation
self.CI = get_car_interface(self.CP, FPCP)
self.CI = get_car_interface(self.CP, self.FPCP)
else:
self.CI, self.CP = CI, CI.CP
@@ -86,7 +86,7 @@ class Controls:
self.branch = get_short_branch()
# Setup sockets
self.pm = messaging.PubMaster(['controlsState', 'carControl', 'onroadEvents'])
self.pm = messaging.PubMaster(['controlsState', 'carControl', 'onroadEvents', 'frogpilotControlsState', 'frogpilotOnroadEvents'])
self.sensor_packets = ["accelerometer", "gyroscope"]
self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"]
@@ -136,10 +136,10 @@ class Controls:
self.LaC: LatControl
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.LaC = LatControlAngle(self.CP, self.CI)
elif self.CP.lateralTuning.which() == 'pid':
elif self.FPCP.lateralTuning.which() == 'pid':
self.LaC = LatControlPID(self.CP, self.CI)
elif self.CP.lateralTuning.which() == 'torque':
self.LaC = LatControlTorque(self.CP, self.CI)
elif self.FPCP.lateralTuning.which() == 'torque':
self.LaC = LatControlTorque(self.CP, self.FPCP, self.CI)
self.initialized = False
self.state = State.disabled
@@ -156,7 +156,8 @@ class Controls:
self.current_alert_types = [ET.PERMANENT]
self.logged_comm_issue = None
self.not_running_prev = None
self.steer_limited = False
self.steer_limited_by_safety = False
self.curvature = 0.0
self.desired_curvature = 0.0
self.experimental_mode = False
self.personality = self.read_personality_param()
@@ -185,8 +186,6 @@ class Controls:
self.rk = Ratekeeper(100, print_delay_threshold=None)
# FrogPilot variables
self.frogpilot_toggles = get_frogpilot_toggles()
self.belowSteerSpeed_shown = False
self.distance_pressed_previously = False
self.resumeRequired_shown = False
@@ -194,12 +193,17 @@ class Controls:
self.display_timer = 0
self.frogpilot_events_prev = []
self.event_names_to_clear = set()
self.use_old_long = self.frogpilot_toggles.old_long_api
self.has_menu = self.CP.carName == "gm" and not (self.CP.flags & GMFlags.NO_CAMERA.value or self.CP.carFingerprint in CC_ONLY_CAR)
self.frogpilot_AM = AlertManager()
self.frogpilot_events = Events(frogpilot=True)
self.frogpilot_toggles = get_frogpilot_toggles()
def set_initial_state(self):
if REPLAY:
controls_state = self.params.get("ReplayControlsState")
@@ -210,10 +214,14 @@ class Controls:
if any(ps.controlsAllowed for ps in self.sm['pandaStates']):
self.state = State.enabled
def contains_event_type(self, *event_types):
return any(self.events.contains(event_type) or self.frogpilot_events.contains(event_type) for event_type in event_types)
def update_events(self, CS):
"""Compute onroadEvents from carState"""
self.events.clear()
self.frogpilot_events.clear()
# Add joystick event, static on cars, dynamic on nonCars
if self.joystick_mode:
@@ -222,7 +230,10 @@ class Controls:
# Add startup event
if self.startup_event is not None:
self.events.add(self.startup_event)
if self.startup_event == FrogPilotEventName.customStartupAlert:
self.frogpilot_events.add(self.startup_event)
else:
self.events.add(self.startup_event)
self.startup_event = None
# Don't add any more events if not initialized
@@ -288,7 +299,7 @@ class Controls:
if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \
(CS.rightBlindspot and direction == LaneChangeDirection.right):
if self.frogpilot_toggles.loud_blindspot_alert:
self.events.add(EventName.laneChangeBlockedLoud)
self.frogpilot_events.add(FrogPilotEventName.laneChangeBlockedLoud)
else:
self.events.add(EventName.laneChangeBlocked)
else:
@@ -296,12 +307,12 @@ class Controls:
if self.sm['frogpilotPlan'].laneWidthLeft >= self.frogpilot_toggles.lane_detection_width:
self.events.add(EventName.preLaneChangeLeft)
else:
self.events.add(EventName.noLaneAvailable)
self.frogpilot_events.add(FrogPilotEventName.noLaneAvailable)
else:
if self.sm['frogpilotPlan'].laneWidthRight >= self.frogpilot_toggles.lane_detection_width:
self.events.add(EventName.preLaneChangeRight)
else:
self.events.add(EventName.noLaneAvailable)
self.frogpilot_events.add(FrogPilotEventName.noLaneAvailable)
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
@@ -309,8 +320,12 @@ class Controls:
for i, pandaState in enumerate(self.sm['pandaStates']):
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
if i < len(self.CP.safetyConfigs):
expected_param = self.CP.safetyConfigs[i].safetyParam
if i < len(self.FPCP.safetyConfigs):
expected_param |= self.FPCP.safetyConfigs[i].safetyParam
safety_mismatch = pandaState.safetyModel != self.CP.safetyConfigs[i].safetyModel or \
pandaState.safetyParam != self.CP.safetyConfigs[i].safetyParam or \
pandaState.safetyParam != expected_param or \
pandaState.alternativeExperience != self.CP.alternativeExperience
else:
safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES
@@ -351,7 +366,7 @@ class Controls:
self.events.add(EventName.canError)
# generic catch-all. ideally, a more specific event should be added above instead
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
has_disable_events = self.contains_event_type(ET.NO_ENTRY) and self.contains_event_type(ET.SOFT_DISABLE, ET.IMMEDIATE_DISABLE)
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
if not self.sm.all_checks() and no_system_errors:
if not self.sm.all_alive():
@@ -424,10 +439,10 @@ class Controls:
self.events.add(EventName.modeldLagging)
# Add FrogPilot events
self.events.add_from_msg(self.sm['frogpilotPlan'].frogpilotEvents)
self.frogpilot_events.add_from_msg(self.sm['frogpilotPlan'].frogpilotEvents)
if self.frogpilot_toggles.block_user:
self.events.add(EventName.blockUser, static=True)
self.frogpilot_events.add(FrogPilotEventName.blockUser)
# Remove already played events
event_names = self.events.names
@@ -513,29 +528,29 @@ class Controls:
# ENABLED, SOFT DISABLING, PRE ENABLING, OVERRIDING
if self.state != State.disabled:
# user and immediate disable always have priority in a non-disabled state
if self.events.contains(ET.USER_DISABLE):
if self.contains_event_type(ET.USER_DISABLE):
self.state = State.disabled
self.current_alert_types.append(ET.USER_DISABLE)
elif self.events.contains(ET.IMMEDIATE_DISABLE):
elif self.contains_event_type(ET.IMMEDIATE_DISABLE):
self.state = State.disabled
self.current_alert_types.append(ET.IMMEDIATE_DISABLE)
else:
# ENABLED
if self.state == State.enabled:
if self.events.contains(ET.SOFT_DISABLE):
if self.contains_event_type(ET.SOFT_DISABLE):
self.state = State.softDisabling
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
elif self.events.contains(ET.OVERRIDE_LATERAL) or self.events.contains(ET.OVERRIDE_LONGITUDINAL):
elif self.contains_event_type(ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL):
self.state = State.overriding
self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
# SOFT DISABLING
elif self.state == State.softDisabling:
if not self.events.contains(ET.SOFT_DISABLE):
if not self.contains_event_type(ET.SOFT_DISABLE):
# no more soft disabling condition, so go back to ENABLED
self.state = State.enabled
@@ -547,32 +562,32 @@ class Controls:
# PRE ENABLING
elif self.state == State.preEnabled:
if not self.events.contains(ET.PRE_ENABLE):
if not self.contains_event_type(ET.PRE_ENABLE):
self.state = State.enabled
else:
self.current_alert_types.append(ET.PRE_ENABLE)
# OVERRIDING
elif self.state == State.overriding:
if self.events.contains(ET.SOFT_DISABLE):
if self.contains_event_type(ET.SOFT_DISABLE):
self.state = State.softDisabling
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
elif not (self.events.contains(ET.OVERRIDE_LATERAL) or self.events.contains(ET.OVERRIDE_LONGITUDINAL)):
elif not self.contains_event_type(ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL):
self.state = State.enabled
else:
self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
# DISABLED
elif self.state == State.disabled:
if self.events.contains(ET.ENABLE):
if self.events.contains(ET.NO_ENTRY):
if self.contains_event_type(ET.ENABLE):
if self.contains_event_type(ET.NO_ENTRY):
self.current_alert_types.append(ET.NO_ENTRY)
else:
if self.events.contains(ET.PRE_ENABLE):
if self.contains_event_type(ET.PRE_ENABLE):
self.state = State.preEnabled
elif self.events.contains(ET.OVERRIDE_LATERAL) or self.events.contains(ET.OVERRIDE_LONGITUDINAL):
elif self.contains_event_type(ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL):
self.state = State.overriding
else:
self.state = State.enabled
@@ -594,8 +609,11 @@ class Controls:
sr = max(lp.steerRatio, 0.1)
self.VM.update_params(x, sr)
steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg)
self.curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll)
# Update Torque Params
if self.CP.lateralTuning.which() == 'torque':
if self.FPCP.lateralTuning.which() == 'torque':
torque_params = self.sm['liveTorqueParameters']
if self.sm.all_checks(['liveTorqueParameters']) and (torque_params.useParams or self.frogpilot_toggles.force_auto_tune):
self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered,
@@ -614,7 +632,7 @@ class Controls:
standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
CC.latActive = (self.active or self.sm['frogpilotCarState'].alwaysOnLateralEnabled) and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
(not standstill or self.joystick_mode) and self.sm['frogpilotPlan'].lateralCheck and not self.sm['frogpilotCarState'].pauseLateral
CC.longActive = self.enabled and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) and not self.sm['frogpilotCarState'].pauseLongitudinal and self.CP.openpilotLongitudinalControl
CC.longActive = self.enabled and not self.contains_event_type(ET.OVERRIDE_LONGITUDINAL) and not self.sm['frogpilotCarState'].pauseLongitudinal and self.CP.openpilotLongitudinalControl
actuators = CC.actuators
actuators.longControlState = self.LoC.long_control_state
@@ -632,7 +650,7 @@ class Controls:
if not CC.latActive:
self.LaC.reset()
if not CC.longActive:
if self.use_old_long:
if self.frogpilot_toggles.old_long_api:
self.LoC.reset_old_long(v_pid=CS.vEgo)
else:
self.LoC.reset()
@@ -640,25 +658,30 @@ 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_helper.v_cruise_kph * CV.KPH_TO_MS)
if self.frogpilot_toggles.sport_plus and (self.sm["frogpilotCarState"].sportGear or not self.frogpilot_toggles.map_acceleration):
pid_accel_limits = (pid_accel_limits[0], get_max_allowed_accel(CS.vEgo))
if self.use_old_long:
if self.frogpilot_toggles.old_long_api:
t_since_plan = (self.sm.frame - self.sm.recv_frame['longitudinalPlan']) * DT_CTRL
actuators.accel = min(self.LoC.update_old_long(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan, self.frogpilot_toggles), self.frogpilot_toggles.max_desired_acceleration)
actuators.accel = float(min(self.LoC.update_old_long(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan, self.frogpilot_toggles), self.frogpilot_toggles.max_desired_acceleration))
else:
actuators.accel = min(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits, self.frogpilot_toggles), self.frogpilot_toggles.max_desired_acceleration)
actuators.accel = float(min(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits, self.frogpilot_toggles), self.frogpilot_toggles.max_desired_acceleration))
if len(long_plan.speeds):
actuators.speed = long_plan.speeds[-1]
# Steering PID loop and lateral MPC
self.desired_curvature = clip_curvature(CS.vEgo, self.desired_curvature, model_v2.action.desiredCurvature)
# Reset desired curvature to current to avoid violating the limits on engage
new_desired_curvature = model_v2.action.desiredCurvature if CC.latActive else self.curvature
self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll)
actuators.curvature = self.desired_curvature
actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited, self.desired_curvature,
self.sm['liveLocationKalman'],
model_data=self.sm['modelV2'], frogpilot_toggles=self.frogpilot_toggles)
steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited_by_safety, self.desired_curvature,
curvature_limited,
self.sm['liveLocationKalman'],
self.sm['modelV2'],
self.frogpilot_toggles)
actuators.steer = float(steer)
actuators.steeringAngleDeg = float(steeringAngleDeg)
else:
lac_log = log.ControlsState.LateralDebugState.new_message()
if self.sm.recv_frame['testJoystick'] > 0:
@@ -682,35 +705,22 @@ class Controls:
lac_log.output = actuators.steer
lac_log.saturated = abs(actuators.steer) >= 0.9
# Send a "steering required alert" if saturation count has reached the limit
if CS.steeringPressed:
self.last_steering_pressed_frame = self.sm.frame
recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0
# Send a "steering required alert" if saturation count has reached the limit
if lac_log.active and not recent_steer_pressed and not self.CP.notCar:
if self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode:
undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2
turning = abs(lac_log.desiredLateralAccel) > 1.0
good_speed = CS.vEgo > 5
max_torque = abs(self.sm['carOutput'].actuatorsOutput.steer) > 0.99
if undershooting and turning and good_speed and max_torque:
lac_log.active and self.events.add(EventName.goatSteerSaturated if self.frogpilot_toggles.goat_scream_alert else EventName.steerSaturated)
elif lac_log.saturated:
# TODO probably should not use dpath_points but curvature
dpath_points = model_v2.position.y
if len(dpath_points):
# Check if we deviated from the path
# TODO use desired vs actual curvature
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
steering_value = actuators.steeringAngleDeg
else:
steering_value = actuators.steer
left_deviation = steering_value > 0 and dpath_points[0] < -0.20
right_deviation = steering_value < 0 and dpath_points[0] > 0.20
if left_deviation or right_deviation:
self.events.add(EventName.steerSaturated)
clipped_speed = max(CS.vEgo, 0.3)
actual_lateral_accel = self.curvature * (clipped_speed**2)
desired_lateral_accel = model_v2.action.desiredCurvature * (clipped_speed**2)
undershooting = abs(desired_lateral_accel) / abs(1e-3 + actual_lateral_accel) > 1.2
turning = abs(desired_lateral_accel) > 1.0
# TODO: lac.saturated includes speed and other checks, should be pulled out
if undershooting and turning and lac_log.saturated:
if self.frogpilot_toggles.goat_scream_alert:
self.frogpilot_events.add(FrogPilotEventName.goatSteerSaturated)
else:
self.events.add(EventName.steerSaturated)
# Ensure no NaNs/Infs
for p in ACTUATOR_FIELDS:
@@ -754,6 +764,9 @@ class Controls:
if self.frogpilot_toggles.conditional_experimental_mode or self.frogpilot_toggles.slc_fallback_experimental_mode:
self.experimental_mode = self.sm['frogpilotPlan'].experimentalMode
if hasattr(self.LaC, "pid"):
self.LaC.pid._k_p = self.frogpilot_toggles.steerKp
# Update FrogPilot variables
if self.sm['frogpilotPlan'].togglesUpdated:
self.frogpilot_toggles = get_frogpilot_toggles()
@@ -826,10 +839,10 @@ class Controls:
if not self.CP.passive and self.initialized:
CO = self.sm['carOutput']
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.steer_limited = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
STEER_ANGLE_SATURATION_THRESHOLD
self.steer_limited_by_safety = abs(CC.actuators.steeringAngleDeg - CO.actuatorsOutput.steeringAngleDeg) > \
STEER_ANGLE_SATURATION_THRESHOLD
else:
self.steer_limited = abs(CC.actuators.steer - CO.actuatorsOutput.steer) > 1e-2
self.steer_limited_by_safety = abs(CC.actuators.steer - CO.actuatorsOutput.steer) > 1e-2
force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \
(self.state == State.softDisabling) or \
@@ -861,7 +874,7 @@ class Controls:
controlsState.curvature = curvature
controlsState.desiredCurvature = self.desired_curvature
controlsState.state = self.state
controlsState.engageable = not self.events.contains(ET.NO_ENTRY)
controlsState.engageable = not self.contains_event_type(ET.NO_ENTRY)
controlsState.longControlState = self.LoC.long_control_state
controlsState.vPid = float(self.LoC.v_pid)
controlsState.vCruise = float(self.v_cruise_helper.v_cruise_kph)
@@ -875,7 +888,7 @@ class Controls:
controlsState.experimentalMode = self.experimental_mode
controlsState.personality = self.personality
lat_tuning = self.CP.lateralTuning.which()
lat_tuning = self.FPCP.lateralTuning.which()
if self.joystick_mode:
controlsState.lateralControlState.debugState = lac_log
elif self.CP.steerControlType == car.CarParams.SteerControlType.angle:
@@ -888,12 +901,18 @@ class Controls:
self.pm.send('controlsState', dat)
# onroadEvents - logged every second or on change
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev):
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev) or (self.frogpilot_events.names != self.frogpilot_events_prev):
ce_send = messaging.new_message('onroadEvents', len(self.events))
ce_send.valid = True
ce_send.onroadEvents = self.events.to_msg()
self.pm.send('onroadEvents', ce_send)
fpce_send = messaging.new_message('frogpilotOnroadEvents', len(self.frogpilot_events))
fpce_send.valid = True
fpce_send.frogpilotOnroadEvents = self.frogpilot_events.to_msg()
self.pm.send('frogpilotOnroadEvents', fpce_send)
self.events_prev = self.events.names.copy()
self.frogpilot_events_prev = self.frogpilot_events.names.copy()
# carControl
cc_send = messaging.new_message('carControl')
@@ -901,6 +920,26 @@ class Controls:
cc_send.carControl = CC
self.pm.send('carControl', cc_send)
# frogpilotControlsState
frogpilot_dat = messaging.new_message('frogpilotControlsState')
frogpilot_dat.valid = CS.canValid
frogpilotControlsState = frogpilot_dat.frogpilotControlsState
frogpilot_alerts = self.frogpilot_events.create_alerts(self.current_alert_types, [self.CP, CS, self.sm, self.is_metric, self.soft_disable_timer, self.frogpilot_toggles])
self.frogpilot_AM.add_many(self.sm.frame, frogpilot_alerts)
current_frogpilot_alert = self.frogpilot_AM.process_alerts(self.sm.frame, clear_event_types)
if current_frogpilot_alert:
frogpilotControlsState.alertText1 = current_frogpilot_alert.alert_text_1
frogpilotControlsState.alertText2 = current_frogpilot_alert.alert_text_2
frogpilotControlsState.alertSize = current_frogpilot_alert.alert_size
frogpilotControlsState.alertStatus = current_frogpilot_alert.alert_status
frogpilotControlsState.alertBlinkingRate = current_frogpilot_alert.alert_rate
frogpilotControlsState.alertType = current_frogpilot_alert.alert_type
frogpilotControlsState.alertSound = current_frogpilot_alert.audible_alert
self.pm.send('frogpilotControlsState', frogpilot_dat)
def step(self):
start_time = time.monotonic()
@@ -932,7 +971,8 @@ class Controls:
def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
if not (self.frogpilot_toggles.conditional_experimental_mode or self.frogpilot_toggles.slc_fallback_experimental_mode):
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.personality = self.read_personality_param()
if self.CP.notCar:
self.joystick_mode = self.params.get_bool("JoystickDebugMode")
+24 -11
View File
@@ -5,6 +5,7 @@ from cereal import car, log
from openpilot.common.conversions import Conversions as CV
from openpilot.common.numpy_fast import clip, interp
from openpilot.common.realtime import DT_CTRL, DT_MDL
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
# WARNING: this value was determined based on the model's training distribution,
# model predictions above this speed can be unpredictable
@@ -24,6 +25,7 @@ MAX_CURVATURE = 0.2
# EU guidelines
MAX_LATERAL_JERK = 5.0
MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2
MAX_VEL_ERR = 5.0
ButtonEvent = car.CarState.ButtonEvent
@@ -179,29 +181,40 @@ def apply_center_deadzone(error, deadzone):
def rate_limit(new_value, last_value, dw_step, up_step):
return clip(new_value, last_value + dw_step, last_value + up_step)
def clamp(val, min_val, max_val):
clamped_val = float(np.clip(val, min_val, max_val))
return clamped_val, clamped_val != val
def smooth_value(val, prev_val, tau, dt=DT_MDL):
alpha = 1 - np.exp(-dt/tau) if tau > 0 else 1
return alpha * val + (1 - alpha) * prev_val
def clip_curvature(v_ego, prev_curvature, new_curvature):
v_ego = max(MIN_SPEED, v_ego)
max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755
safe_desired_curvature = clip(new_curvature,
prev_curvature - max_curvature_rate * DT_CTRL,
prev_curvature + max_curvature_rate * DT_CTRL)
def clip_curvature(v_ego, prev_curvature, new_curvature, roll):
# This function respects ISO lateral jerk and acceleration limits + a max curvature
v_ego = max(v_ego, MIN_SPEED)
max_curvature_rate = MAX_LATERAL_JERK / (v_ego ** 2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755
new_curvature = np.clip(new_curvature,
prev_curvature - max_curvature_rate * DT_CTRL,
prev_curvature + max_curvature_rate * DT_CTRL)
return safe_desired_curvature
roll_compensation = roll * ACCELERATION_DUE_TO_GRAVITY
max_lat_accel = MAX_LATERAL_ACCEL_NO_ROLL + roll_compensation
min_lat_accel = -MAX_LATERAL_ACCEL_NO_ROLL + roll_compensation
new_curvature, limited_accel = clamp(new_curvature, min_lat_accel / v_ego ** 2, max_lat_accel / v_ego ** 2)
new_curvature, limited_max_curv = clamp(new_curvature, -MAX_CURVATURE, MAX_CURVATURE)
return float(new_curvature), limited_accel or limited_max_curv
def get_friction(lateral_accel_error: float, lateral_accel_deadzone: float, friction_threshold: float,
torque_params: car.CarParams.LateralTorqueTuning, friction_compensation: bool) -> float:
torque_params: car.CarParams.LateralTorqueTuning) -> float:
# TODO torque params' friction should be in lataxel space, not torque space
friction_interp = interp(
apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone),
[-friction_threshold, friction_threshold],
[-torque_params.friction, torque_params.friction]
[-torque_params.friction * torque_params.latAccelFactor, torque_params.friction * torque_params.latAccelFactor]
)
friction = float(friction_interp) if friction_compensation else 0.0
return friction
return float(friction_interp)
def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float:
+78 -70
View File
@@ -6,7 +6,7 @@ from enum import IntEnum
from collections.abc import Callable
from types import SimpleNamespace
from cereal import log, car
from cereal import log, car, custom
import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV
from openpilot.common.git import get_short_branch
@@ -16,9 +16,12 @@ from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
AlertSize = log.ControlsState.AlertSize
AlertStatus = log.ControlsState.AlertStatus
FrogPilotAlertStatus = custom.FrogPilotControlsState.AlertStatus
VisualAlert = car.CarControl.HUDControl.VisualAlert
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
FrogPilotAudibleAlert = custom.FrogPilotCarControl.HUDControl.AudibleAlert
EventName = car.CarEvent.EventName
FrogPilotEventName = custom.FrogPilotCarEvent.EventName
# Alert priorities
@@ -47,13 +50,17 @@ class ET:
# get event name from enum
EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()}
FROGPILOT_EVENT_NAME = {v: k for k, v in FrogPilotEventName.schema.enumerants.items()}
class Events:
def __init__(self):
def __init__(self, frogpilot=False):
self.events: list[int] = []
self.static_events: list[int] = []
self.event_counters = dict.fromkeys(EVENTS.keys(), 0)
self.event_counters = dict.fromkeys((FROGPILOT_EVENTS if frogpilot else EVENTS).keys(), 0)
# FrogPilot variables
self.frogpilot = frogpilot
@property
def names(self) -> list[int]:
@@ -72,7 +79,7 @@ class Events:
self.events = self.static_events.copy()
def contains(self, event_type: str) -> bool:
return any(event_type in EVENTS.get(e, {}) for e in self.events)
return any(event_type in (FROGPILOT_EVENTS if self.frogpilot else EVENTS).get(e, {}) for e in self.events)
def create_alerts(self, event_types: list[str], callback_args=None):
if callback_args is None:
@@ -80,15 +87,15 @@ class Events:
ret = []
for e in self.events:
types = EVENTS[e].keys()
types = (FROGPILOT_EVENTS if self.frogpilot else EVENTS)[e].keys()
for et in event_types:
if et in types:
alert = EVENTS[e][et]
alert = (FROGPILOT_EVENTS if self.frogpilot else EVENTS)[e][et]
if not isinstance(alert, Alert):
alert = alert(*callback_args)
if DT_CTRL * (self.event_counters[e] + 1) >= alert.creation_delay:
alert.alert_type = f"{EVENT_NAME[e]}/{et}"
alert.alert_type = f"{(FROGPILOT_EVENT_NAME if self.frogpilot else EVENT_NAME)[e]}/{et}"
alert.event_type = et
ret.append(alert)
return ret
@@ -100,9 +107,9 @@ class Events:
def to_msg(self):
ret = []
for event_name in self.events:
event = car.CarEvent.new_message()
event = (custom.FrogPilotCarEvent if self.frogpilot else car.CarEvent).new_message()
event.name = event_name
for event_type in EVENTS.get(event_name, {}):
for event_type in (FROGPILOT_EVENTS if self.frogpilot else EVENTS).get(event_name, {}):
setattr(event, event_type, True)
ret.append(event)
return ret
@@ -333,7 +340,7 @@ def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster,
# FrogPilot alerts
def custom_startup_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, frogpilot_toggles: SimpleNamespace) -> Alert:
return StartupAlert(frogpilot_toggles.startup_alert_top, frogpilot_toggles.startup_alert_bottom, alert_status=AlertStatus.frogpilot)
return StartupAlert(frogpilot_toggles.startup_alert_top, frogpilot_toggles.startup_alert_bottom, alert_status=FrogPilotAlertStatus.frogpilot)
def forcing_stop_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, frogpilot_toggles: SimpleNamespace) -> Alert:
@@ -343,7 +350,7 @@ def forcing_stop_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMas
return Alert(
f"Forcing the car to stop in {model_length_msg}",
"Press the gas pedal or 'Resume' button to override",
AlertStatus.frogpilot, AlertSize.mid,
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.MID, VisualAlert.none, AudibleAlert.prompt, 1.)
@@ -368,7 +375,7 @@ def holiday_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster,
holiday_messages.get(frogpilot_toggles.current_holiday_theme),
"",
AlertStatus.normal, AlertSize.small,
Priority.LOWEST, VisualAlert.none, AudibleAlert.startup, 5.)
Priority.LOWEST, VisualAlert.none, FrogPilotAudibleAlert.startup, 5.)
def no_lane_available_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, frogpilot_toggles: SimpleNamespace) -> Alert:
@@ -394,7 +401,7 @@ def torque_nn_load_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM
return Alert(
"NNFF Torque Controller loaded",
model_name,
AlertStatus.frogpilot, AlertSize.mid,
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, AudibleAlert.engage, 5.0)
@@ -1028,9 +1035,10 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.PERMANENT: NormalPermanentAlert("Vehicle Sensors Calibrating", "Drive to Calibrate"),
ET.NO_ENTRY: NoEntryAlert("Vehicle Sensors Calibrating"),
},
}
# FrogPilot Events
EventName.blockUser: {
FROGPILOT_EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
FrogPilotEventName.blockUser: {
ET.PERMANENT: Alert(
"Don't use the 'Development' branch!",
"Forcing you into 'Dashcam Mode' for your safety",
@@ -1038,35 +1046,35 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
Priority.HIGHEST, VisualAlert.none, AudibleAlert.none, 1.),
},
EventName.customStartupAlert: {
FrogPilotEventName.customStartupAlert: {
ET.PERMANENT: custom_startup_alert,
},
EventName.forcingStop: {
FrogPilotEventName.forcingStop: {
ET.WARNING: forcing_stop_alert,
},
EventName.goatSteerSaturated: {
FrogPilotEventName.goatSteerSaturated: {
ET.WARNING: Alert(
"JESUS TAKE THE WHEEL!!",
"Turn Exceeds Steering Limit",
AlertStatus.userPrompt, AlertSize.mid,
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.goat, 2.),
Priority.LOW, VisualAlert.steerRequired, FrogPilotAudibleAlert.goat, 2.),
},
EventName.greenLight: {
FrogPilotEventName.greenLight: {
ET.PERMANENT: Alert(
"Light turned green",
"",
AlertStatus.frogpilot, AlertSize.small,
FrogPilotAlertStatus.frogpilot, AlertSize.small,
Priority.MID, VisualAlert.none, AudibleAlert.prompt, 3.),
},
EventName.holidayActive: {
FrogPilotEventName.holidayActive: {
ET.PERMANENT: holiday_alert,
},
EventName.laneChangeBlockedLoud: {
FrogPilotEventName.laneChangeBlockedLoud: {
ET.WARNING: Alert(
"Car Detected in Blindspot",
"",
@@ -1074,19 +1082,19 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
Priority.LOW, VisualAlert.none, AudibleAlert.warningSoft, .1),
},
EventName.leadDeparting: {
FrogPilotEventName.leadDeparting: {
ET.PERMANENT: Alert(
"Lead departed",
"",
AlertStatus.frogpilot, AlertSize.small,
FrogPilotAlertStatus.frogpilot, AlertSize.small,
Priority.MID, VisualAlert.none, AudibleAlert.prompt, 3.),
},
EventName.noLaneAvailable: {
FrogPilotEventName.noLaneAvailable: {
ET.WARNING: no_lane_available_alert,
},
EventName.openpilotCrashed: {
FrogPilotEventName.openpilotCrashed: {
ET.IMMEDIATE_DISABLE: Alert(
"openpilot crashed",
"Please post the 'Error Log' in the FrogPilot Discord!",
@@ -1100,7 +1108,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
Priority.HIGHEST, VisualAlert.none, AudibleAlert.prompt, .1),
},
EventName.pedalInterceptorNoBrake: {
FrogPilotEventName.pedalInterceptorNoBrake: {
ET.WARNING: Alert(
"Braking Unavailable",
"Shift to L",
@@ -1108,43 +1116,43 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
Priority.HIGH, VisualAlert.wrongGear, AudibleAlert.promptRepeat, 4.),
},
EventName.speedLimitChanged: {
FrogPilotEventName.speedLimitChanged: {
ET.PERMANENT: Alert(
"Speed limit changed",
"",
AlertStatus.frogpilot, AlertSize.small,
FrogPilotAlertStatus.frogpilot, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 3.),
},
EventName.thisIsFineSteerSaturated: {
FrogPilotEventName.thisIsFineSteerSaturated: {
ET.WARNING: Alert(
"This is fine ☕",
"Turn Exceeds Steering Limit",
AlertStatus.userPrompt, AlertSize.mid,
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.thisIsFine, 2.),
Priority.LOW, VisualAlert.steerRequired, FrogPilotAudibleAlert.thisIsFine, 2.),
},
EventName.torqueNNLoad: {
FrogPilotEventName.torqueNNLoad: {
ET.PERMANENT: torque_nn_load_alert,
},
EventName.trafficModeActive: {
FrogPilotEventName.trafficModeActive: {
ET.WARNING: Alert(
"Traffic Mode enabled",
"",
AlertStatus.frogpilot, AlertSize.small,
FrogPilotAlertStatus.frogpilot, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 3.),
},
EventName.trafficModeInactive: {
FrogPilotEventName.trafficModeInactive: {
ET.WARNING: Alert(
"Traffic Mode Disabled",
"",
AlertStatus.frogpilot, AlertSize.small,
FrogPilotAlertStatus.frogpilot, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 3.),
},
EventName.turningLeft: {
FrogPilotEventName.turningLeft: {
ET.WARNING: Alert(
"Turning left",
"",
@@ -1152,7 +1160,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, alert_rate=0.75),
},
EventName.turningRight: {
FrogPilotEventName.turningRight: {
ET.WARNING: Alert(
"Turning right",
"",
@@ -1161,98 +1169,98 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
},
# Random Events
EventName.accel30: {
FrogPilotEventName.accel30: {
ET.WARNING: Alert(
"UwU u went a bit fast there!",
"( ⁄•⁄ω⁄•⁄ )",
AlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, AudibleAlert.uwu, 4.),
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, FrogPilotAudibleAlert.uwu, 4.),
},
EventName.accel35: {
FrogPilotEventName.accel35: {
ET.WARNING: Alert(
"I ain't giving you no tree-fiddy",
"You damn Loch Ness Monsta!",
AlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, AudibleAlert.nessie, 4.),
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, FrogPilotAudibleAlert.nessie, 4.),
},
EventName.accel40: {
FrogPilotEventName.accel40: {
ET.WARNING: Alert(
"Great Scott!",
"🚗💨",
AlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, AudibleAlert.doc, 4.),
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, FrogPilotAudibleAlert.doc, 4.),
},
EventName.dejaVuCurve: {
FrogPilotEventName.dejaVuCurve: {
ET.PERMANENT: Alert(
"♬♪ Deja vu! ᕕ(⌐■_■)ᕗ ♪♬",
"🏎️",
AlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, AudibleAlert.dejaVu, 4.),
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.LOW, VisualAlert.none, FrogPilotAudibleAlert.dejaVu, 4.),
},
EventName.firefoxSteerSaturated: {
FrogPilotEventName.firefoxSteerSaturated: {
ET.WARNING: Alert(
"IE Has Stopped Responding...",
"Turn Exceeds Steering Limit",
AlertStatus.userPrompt, AlertSize.mid,
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.firefox, 4.),
Priority.LOW, VisualAlert.steerRequired, FrogPilotAudibleAlert.firefox, 4.),
},
EventName.hal9000: {
FrogPilotEventName.hal9000: {
ET.WARNING: Alert(
"I'm sorry Dave",
"I'm afraid I can't do that...",
AlertStatus.normal, AlertSize.mid,
Priority.HIGH, VisualAlert.none, AudibleAlert.hal9000, 4.),
Priority.HIGH, VisualAlert.none, FrogPilotAudibleAlert.hal9000, 4.),
},
EventName.openpilotCrashedRandomEvent: {
FrogPilotEventName.openpilotCrashedRandomEvent: {
ET.IMMEDIATE_DISABLE: Alert(
"openpilot crashed 💩",
"Please post the 'Error Log' in the FrogPilot Discord!",
AlertStatus.normal, AlertSize.mid,
Priority.HIGHEST, VisualAlert.none, AudibleAlert.fart, 10.),
Priority.HIGHEST, VisualAlert.none, FrogPilotAudibleAlert.fart, 10.),
ET.NO_ENTRY: Alert(
"openpilot crashed 💩",
"Please post the 'Error Log' in the FrogPilot Discord!",
AlertStatus.normal, AlertSize.mid,
Priority.HIGHEST, VisualAlert.none, AudibleAlert.fart, 10.),
Priority.HIGHEST, VisualAlert.none, FrogPilotAudibleAlert.fart, 10.),
},
EventName.toBeContinued: {
FrogPilotEventName.toBeContinued: {
ET.PERMANENT: Alert(
"To be continued...",
"⬅️",
AlertStatus.frogpilot, AlertSize.mid,
Priority.MID, VisualAlert.none, AudibleAlert.continued, 7.),
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.MID, VisualAlert.none, FrogPilotAudibleAlert.continued, 7.),
},
EventName.vCruise69: {
FrogPilotEventName.vCruise69: {
ET.WARNING: Alert(
"Lol 69",
"",
AlertStatus.frogpilot, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.noice, 2.),
FrogPilotAlertStatus.frogpilot, AlertSize.small,
Priority.LOW, VisualAlert.none, FrogPilotAudibleAlert.noice, 2.),
},
EventName.yourFrogTriedToKillMe: {
FrogPilotEventName.yourFrogTriedToKillMe: {
ET.PERMANENT: Alert(
"Your Frog tried to kill me...",
"👺",
AlertStatus.frogpilot, AlertSize.mid,
Priority.MID, VisualAlert.none, AudibleAlert.angry, 5.),
FrogPilotAlertStatus.frogpilot, AlertSize.mid,
Priority.MID, VisualAlert.none, FrogPilotAudibleAlert.angry, 5.),
},
EventName.youveGotMail: {
FrogPilotEventName.youveGotMail: {
ET.WARNING: Alert(
"You've got mail! 📧",
"",
AlertStatus.frogpilot, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.mail, 3.),
FrogPilotAlertStatus.frogpilot, AlertSize.small,
Priority.LOW, VisualAlert.none, FrogPilotAudibleAlert.mail, 3.),
},
}
+6 -5
View File
@@ -1,6 +1,6 @@
import numpy as np
from abc import abstractmethod, ABC
from openpilot.common.numpy_fast import clip
from openpilot.common.realtime import DT_CTRL
MIN_LATERAL_CONTROL_SPEED = 0.3 # m/s
@@ -17,16 +17,17 @@ class LatControl(ABC):
self.steer_max = 1.0
@abstractmethod
def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk, model_data=None, frogpilot_toggles=None):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, llk, model_data, frogpilot_toggles):
pass
def reset(self):
self.sat_count = 0.
def _check_saturation(self, saturated, CS, steer_limited):
if saturated and CS.vEgo > self.sat_check_min_speed and not steer_limited and not CS.steeringPressed:
def _check_saturation(self, saturated, CS, steer_limited_by_safety, curvature_limited):
# Saturated only if control output is not being limited by car torque/angle rate limits
if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_safety and not CS.steeringPressed:
self.sat_count += self.sat_count_rate
else:
self.sat_count -= self.sat_count_rate
self.sat_count = clip(self.sat_count, 0.0, self.sat_limit)
self.sat_count = np.clip(self.sat_count, 0.0, self.sat_limit)
return self.sat_count > (self.sat_limit - 1e-3)
+11 -3
View File
@@ -3,6 +3,7 @@ import math
from cereal import log
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
# TODO This is speed dependent
STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees
@@ -10,8 +11,9 @@ class LatControlAngle(LatControl):
def __init__(self, CP, CI):
super().__init__(CP, CI)
self.sat_check_min_speed = 5.
self.use_steer_limited_by_safety = CP.carName == "tesla"
def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk, model_data=None, frogpilot_toggles=None):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, llk, model_data, frogpilot_toggles):
angle_log = log.ControlsState.LateralAngleState.new_message()
if not active:
@@ -22,8 +24,14 @@ class LatControlAngle(LatControl):
angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
angle_steers_des += params.angleOffsetDeg
angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD
angle_log.saturated = self._check_saturation(angle_control_saturated, CS, False)
if self.use_steer_limited_by_safety:
# these cars' carcontrollers calculate max lateral accel and jerk, so we can rely on carOutput for saturation
angle_control_saturated = steer_limited_by_safety
else:
# for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota)
# or relying on EPS (Ford Q3), carOutput does not capture maxing out torque # TODO: this can be improved
angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD
angle_log.saturated = bool(self._check_saturation(angle_control_saturated, CS, False, curvature_limited))
angle_log.steeringAngleDeg = float(CS.steeringAngleDeg)
angle_log.steeringAngleDesiredDeg = angle_steers_des
return 0, float(angle_steers_des), angle_log
+16 -16
View File
@@ -13,11 +13,7 @@ class LatControlPID(LatControl):
k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.get_steer_feedforward = CI.get_steer_feedforward_function()
def reset(self):
super().reset()
self.pid.reset()
def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk, model_data=None, frogpilot_toggles=None):
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, llk, model_data, frogpilot_toggles):
pid_log = log.ControlsState.LateralPIDState.new_message()
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
pid_log.steeringRateDeg = float(CS.steeringRateDeg)
@@ -29,20 +25,24 @@ class LatControlPID(LatControl):
pid_log.steeringAngleDesiredDeg = angle_steers_des
pid_log.angleError = error
if not active:
output_steer = 0.0
output_torque = 0.0
pid_log.active = False
self.pid.reset()
else:
# offset does not contribute to resistive torque
steer_feedforward = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
ff = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_torque = self.pid.update(error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
output_steer = self.pid.update(error, override=CS.steeringPressed,
feedforward=steer_feedforward, speed=CS.vEgo)
pid_log.active = True
pid_log.p = self.pid.p
pid_log.i = self.pid.i
pid_log.f = self.pid.f
pid_log.output = output_steer
pid_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited)
pid_log.p = float(self.pid.p)
pid_log.i = float(self.pid.i)
pid_log.f = float(self.pid.f)
pid_log.output = float(output_torque)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
return output_steer, angle_steers_des, pid_log
return output_torque, angle_steers_des, pid_log
+49 -45
View File
@@ -1,8 +1,9 @@
import math
import numpy as np
from cereal import log
from openpilot.common.numpy_fast import interp
from openpilot.selfdrive.car.interfaces import LatControlInputs
from openpilot.selfdrive.controls.lib.drive_helpers import get_friction
from openpilot.selfdrive.car.interfaces import FRICTION_THRESHOLD
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.selfdrive.controls.lib.pid import PIDController
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
@@ -25,17 +26,18 @@ LOW_SPEED_Y = [15, 13, 10, 5]
class LatControlTorque(LatControl):
def __init__(self, CP, CI):
def __init__(self, CP, FPCP, CI):
super().__init__(CP, CI)
self.torque_params = CP.lateralTuning.torque
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
k_f=self.torque_params.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.torque_params = FPCP.lateralTuning.torque
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
self.use_steering_angle = self.torque_params.useSteeringAngle
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
k_f=self.torque_params.kf)
self.update_limits()
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
# FrogPilot variables
self.nnff = NeuralNetworkFeedforward(CP, CI, self)
self.nnff = NeuralNetworkFeedforward(CP, self)
self.nnff_loaded = self.nnff.lat_torque_nn_model != None
@@ -43,64 +45,66 @@ class LatControlTorque(LatControl):
self.torque_params.latAccelFactor = latAccelFactor
self.torque_params.latAccelOffset = latAccelOffset
self.torque_params.friction = friction
self.update_limits()
def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk, model_data=None, frogpilot_toggles=None):
def update_limits(self):
self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, llk, model_data, frogpilot_toggles):
pid_log = log.ControlsState.LateralTorqueState.new_message()
if not active:
output_torque = 0.0
pid_log.active = False
else:
actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
if self.use_steering_angle:
actual_curvature = actual_curvature_vm
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
else:
actual_curvature_llk = llk.angularVelocityCalibrated.value[2] / CS.vEgo
actual_curvature = interp(CS.vEgo, [2.0, 5.0], [actual_curvature_vm, actual_curvature_llk])
curvature_deadzone = 0.0
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
# desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
# desired_lateral_jerk = desired_curvature_rate * CS.vEgo ** 2
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y_NN if frogpilot_toggles.nnff else LOW_SPEED_Y)**2
low_speed_factor = np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y_NN if frogpilot_toggles.nnff else LOW_SPEED_Y)**2
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation
if self.nnff_loaded and frogpilot_toggles.nnff or frogpilot_toggles.nnff_lite:
torque_from_setpoint, torque_from_measurement, pid_log, ff = self.nnff.compute_nnff(
pid_log, ff = self.nnff.compute_nnff(
CS, VM, actual_lateral_accel, desired_lateral_accel, gravity_adjusted_lateral_accel, lateral_accel_deadzone,
llk, measurement, model_data, params, pid_log, roll_compensation, setpoint, frogpilot_toggles
)
else:
torque_from_setpoint = self.torque_from_lateral_accel(LatControlInputs(setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
setpoint, lateral_accel_deadzone, friction_compensation=False, gravity_adjusted=False)
torque_from_measurement = self.torque_from_lateral_accel(LatControlInputs(measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
measurement, lateral_accel_deadzone, friction_compensation=False, gravity_adjusted=False)
pid_log.error = torque_from_setpoint - torque_from_measurement
ff = self.torque_from_lateral_accel(LatControlInputs(gravity_adjusted_lateral_accel, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params,
desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, friction_compensation=True,
gravity_adjusted=True)
freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5
self.pid._k_p = frogpilot_toggles.steerKp
output_torque = self.pid.update(pid_log.error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_torque = self.pid.update(pid_log.error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
else:
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
pid_log.error = float(setpoint - measurement)
ff = gravity_adjusted_lateral_accel
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
ff -= self.torque_params.latAccelOffset
ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_lataccel = self.pid.update(pid_log.error,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
pid_log.active = True
pid_log.p = self.pid.p
pid_log.i = self.pid.i
pid_log.d = self.pid.d
pid_log.f = self.pid.f
pid_log.output = -output_torque
pid_log.actualLateralAccel = actual_lateral_accel
pid_log.desiredLateralAccel = desired_lateral_accel
pid_log.saturated = self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited)
pid_log.p = float(self.pid.p)
pid_log.i = float(self.pid.i)
pid_log.d = float(self.pid.d)
pid_log.f = float(self.pid.f)
pid_log.output = float(-output_torque) # TODO: log lat accel?
pid_log.actualLateralAccel = float(actual_lateral_accel)
pid_log.desiredLateralAccel = float(desired_lateral_accel)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
# TODO left is positive in this convention
return -output_torque, 0.0, pid_log
@@ -3,12 +3,11 @@ import os
import time
import numpy as np
from cereal import log
from openpilot.common.numpy_fast import clip
from openpilot.selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX
from openpilot.common.realtime import DT_MDL
from openpilot.common.swaglog import cloudlog
# WARNING: imports outside of constants will not trigger a rebuild
from openpilot.selfdrive.modeld.constants import index_function
from openpilot.selfdrive.car.interfaces import ACCEL_MIN
if __name__ == '__main__': # generating code
from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
@@ -57,6 +56,8 @@ FCW_IDXS = T_IDXS < 5.0
T_DIFFS = np.diff(T_IDXS, prepend=[0.])
COMFORT_BRAKE = 2.5
STOP_DISTANCE = 6.0
CRUISE_MIN_ACCEL = -1.2
CRUISE_MAX_ACCEL = 1.6
def get_jerk_factor(aggressive_jerk_acceleration=0.5, aggressive_jerk_danger=0.5, aggressive_jerk_speed=0.5,
standard_jerk_acceleration=1.0, standard_jerk_danger=1.0, standard_jerk_speed=1.0,
@@ -326,9 +327,9 @@ class LongitudinalMpc:
lead_xv = np.column_stack((x_lead_traj, v_lead_traj))
return lead_xv
def process_lead(self, lead, tracking_lead=True):
def process_lead(self, lead):
v_ego = self.x0[1]
if lead is not None and lead.status and tracking_lead:
if lead is not None and lead.status:
x_lead = lead.dRel
v_lead = lead.vLead
a_lead = lead.aLeadK
@@ -343,24 +344,18 @@ class LongitudinalMpc:
# MPC will not converge if immediate crash is expected
# Clip lead distance to what is still possible to brake for
min_x_lead = ((v_ego + v_lead)/2) * (v_ego - v_lead) / (-ACCEL_MIN * 2)
x_lead = clip(x_lead, min_x_lead, 1e8)
v_lead = clip(v_lead, 0.0, 1e8)
a_lead = clip(a_lead, -10., 5.)
x_lead = np.clip(x_lead, min_x_lead, 1e8)
v_lead = np.clip(v_lead, 0.0, 1e8)
a_lead = np.clip(a_lead, -10., 5.)
lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau)
return lead_xv
def set_accel_limits(self, min_a, max_a):
# TODO this sets a max accel limit, but the minimum limit is only for cruise decel
# needs refactor
self.cruise_min_a = min_a
self.max_a = max_a
def update(self, lead_one, lead_two, v_cruise, x, v, a, j, t_follow, tracking_lead, personality=log.LongitudinalPersonality.standard):
def update(self, radarstate, v_cruise, x, v, a, j, t_follow, frogpilot_toggles, personality=log.LongitudinalPersonality.standard):
v_ego = self.x0[1]
self.status = lead_one.status and tracking_lead or lead_two.status
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
lead_xv_0 = self.process_lead(lead_one, tracking_lead)
lead_xv_1 = self.process_lead(lead_two)
lead_xv_0 = self.process_lead(radarstate.leadOne)
lead_xv_1 = self.process_lead(radarstate.leadTwo)
# To estimate a safe distance from a moving lead, we calculate how much stopping
# distance that lead needs as a minimum. We can add that to the current distance
@@ -369,8 +364,7 @@ class LongitudinalMpc:
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1])
self.params[:,0] = ACCEL_MIN
# negative accel constraint causes problems because negative speed is not allowed
self.params[:,1] = max(0.0, self.max_a)
self.params[:,1] = ACCEL_MAX
# Update in ACC mode or ACC/e2e blend
if self.mode == 'acc':
@@ -378,9 +372,9 @@ class LongitudinalMpc:
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05)
# TODO does this make sense when max_a is negative?
v_upper = v_ego + (T_IDXS * self.max_a * 1.05)
v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05)
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
v_lower,
v_upper)
@@ -421,8 +415,8 @@ class LongitudinalMpc:
self.params[:,4] = t_follow
self.run()
lead_probability = lead_one.modelProb
if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and lead_probability > 0.9):
if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and
radarstate.leadOne.modelProb > 0.9):
self.crash_cnt += 1
else:
self.crash_cnt = 0
+58 -126
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
import math
import numpy as np
from openpilot.common.numpy_fast import clip, interp
import cereal.messaging as messaging
from openpilot.common.conversions import Conversions as CV
@@ -12,15 +11,16 @@ from openpilot.selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, CONTROL_N, get_speed_error
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, V_CRUISE_UNSET, CONTROL_N, get_accel_from_plan
from openpilot.common.swaglog import cloudlog
from openpilot.frogpilot.common.frogpilot_variables import MINIMUM_LATERAL_ACCELERATION
LON_MPC_STEP = 0.2 # first step is 0.2s
A_CRUISE_MIN = -1.2
A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6]
A_CRUISE_MAX_BP = [0., 10.0, 25., 40.]
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
ALLOW_THROTTLE_THRESHOLD = 0.5
ALLOW_THROTTLE_THRESHOLD = 0.4
MIN_ALLOW_THROTTLE_SPEED = 2.5
# Lookup table for turns
@@ -29,7 +29,7 @@ _A_TOTAL_MAX_BP = [20., 40.]
def get_max_accel(v_ego):
return interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS)
return float(np.interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS))
def get_coast_accel(pitch):
return np.sin(pitch) * -5.65 - 0.3 # fitted from data using xx/projects/allow_throttle/compute_coast_accel.py
@@ -42,67 +42,17 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
"""
# FIXME: This function to calculate lateral accel is incorrect and should use the VehicleModel
# The lookup table for turns should also be updated if we do this
a_total_max = interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V)
a_total_max = np.interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V)
a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase)
a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.))
if abs(a_y) > MINIMUM_LATERAL_ACCELERATION:
a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.))
else:
a_x_allowed = a_target[1]
return [a_target[0], min(a_target[1], a_x_allowed)]
def get_accel_from_plan(speeds, accels, action_t=DT_MDL, vEgoStopping=0.05):
if len(speeds) == CONTROL_N:
v_now = speeds[0]
a_now = accels[0]
v_target = interp(action_t, CONTROL_N_T_IDX, speeds)
a_target = 2 * (v_target - v_now) / (action_t) - a_now
v_target_1sec = interp(action_t + 1.0, CONTROL_N_T_IDX, speeds)
else:
v_target = 0.0
v_target_1sec = 0.0
a_target = 0.0
should_stop = (v_target < vEgoStopping and
v_target_1sec < vEgoStopping)
return a_target, should_stop
def get_accel_from_plan_classic(CP, speeds, accels, longitudinalActuatorDelay, vEgoStopping):
if len(speeds) == CONTROL_N:
v_target_now = interp(DT_MDL, CONTROL_N_T_IDX, speeds)
a_target_now = interp(DT_MDL, CONTROL_N_T_IDX, accels)
v_target = interp(longitudinalActuatorDelay + DT_MDL, CONTROL_N_T_IDX, speeds)
if v_target != v_target_now:
a_target = 2 * (v_target - v_target_now) / longitudinalActuatorDelay - a_target_now
else:
a_target = a_target_now
v_target_1sec = interp(longitudinalActuatorDelay + DT_MDL + 1.0, CONTROL_N_T_IDX, speeds)
else:
v_target = 0.0
v_target_1sec = 0.0
a_target = 0.0
should_stop = (v_target < vEgoStopping and
v_target_1sec < vEgoStopping)
return a_target, should_stop
def get_accel_from_plan_tomb_raider(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.05):
if len(speeds) == len(t_idxs):
v_now = speeds[0]
a_now = accels[0]
v_target = np.interp(action_t, t_idxs, speeds)
a_target = 2 * (v_target - v_now) / (action_t) - a_now
v_target_1sec = np.interp(action_t + 1.0, t_idxs, speeds)
else:
v_target = 0.0
v_target_1sec = 0.0
a_target = 0.0
should_stop = (v_target < vEgoStopping and
v_target_1sec < vEgoStopping)
return a_target, should_stop
class LongitudinalPlanner:
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP
@@ -115,7 +65,9 @@ class LongitudinalPlanner:
self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
self.v_model_error = 0.0
self.prev_accel_clip = [ACCEL_MIN, ACCEL_MAX]
self.output_a_target = 0.0
self.output_should_stop = False
self.v_desired_trajectory = np.zeros(CONTROL_N)
self.a_desired_trajectory = np.zeros(CONTROL_N)
@@ -123,12 +75,12 @@ class LongitudinalPlanner:
self.solverExecutionTime = 0.0
@staticmethod
def parse_model(model_msg, model_error, v_ego, taco_tune):
def parse_model(model_msg, v_ego, taco_tune):
if (len(model_msg.position.x) == ModelConstants.IDX_N and
len(model_msg.velocity.x) == ModelConstants.IDX_N and
len(model_msg.acceleration.x) == ModelConstants.IDX_N):
x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error
x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x)
v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x)
a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x)
j = np.zeros(len(T_IDXS_MPC))
else:
@@ -138,7 +90,7 @@ class LongitudinalPlanner:
j = np.zeros(len(T_IDXS_MPC))
if taco_tune:
max_lat_accel = interp(v_ego, [5, 10, 20], [1.5, 2.0, 3.0])
max_lat_accel = np.interp(v_ego, [5, 10, 20], [1.5, 2.0, 3.0])
curvatures = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.orientationRate.z) / np.clip(v, 0.3, 100.0)
max_v = np.sqrt(max_lat_accel / (np.abs(curvatures) + 1e-3)) - 2.0
v = np.minimum(max_v, v)
@@ -149,11 +101,10 @@ class LongitudinalPlanner:
throttle_prob = 1.0
return x, v, a, j, throttle_prob
def update(self, tomb_raider, sm, frogpilot_toggles):
if tomb_raider:
self.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
else:
self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
def update(self, sm, classic_longitudinal, frogpilot_toggles):
mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
if classic_longitudinal:
self.mpc.mode = mode
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
@@ -175,55 +126,36 @@ class LongitudinalPlanner:
# No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if tomb_raider:
if self.mode == 'acc':
accel_limits = [sm['frogpilotPlan'].minAcceleration, sm['frogpilotPlan'].maxAcceleration]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_limits_turns = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_limits, self.CP)
else:
accel_limits = [ACCEL_MIN, ACCEL_MAX]
accel_limits_turns = [ACCEL_MIN, ACCEL_MAX]
if mode == 'acc':
accel_clip = [sm['frogpilotPlan'].minAcceleration, sm['frogpilotPlan'].maxAcceleration]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
if not sm['frogpilotPlan'].cscControllingSpeed:
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
else:
if self.mpc.mode == 'acc':
accel_limits = [sm['frogpilotPlan'].minAcceleration, sm['frogpilotPlan'].maxAcceleration]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_limits_turns = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_limits, self.CP)
else:
accel_limits = [ACCEL_MIN, ACCEL_MAX]
accel_limits_turns = [ACCEL_MIN, ACCEL_MAX]
accel_clip = [ACCEL_MIN, ACCEL_MAX]
if reset_state:
self.v_desired_filter.x = v_ego
# Clip aEgo to cruise limits to prevent large accelerations when becoming active
self.a_desired = clip(sm['carState'].aEgo, accel_limits[0], accel_limits[1])
self.a_desired = np.clip(sm['carState'].aEgo, accel_clip[0], accel_clip[1])
# Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
# Compute model v_ego error
self.v_model_error = 0.0 if tomb_raider else get_speed_error(sm['modelV2'], v_ego)
x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], self.v_model_error, v_ego, frogpilot_toggles.taco_tune)
x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], v_ego, frogpilot_toggles.taco_tune)
# Don't clip at low speeds since throttle_prob doesn't account for creep
self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED
if not self.allow_throttle:
clipped_accel_coast = max(accel_coast, accel_limits_turns[0])
clipped_accel_coast_interp = interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_limits_turns[1], clipped_accel_coast])
accel_limits_turns[1] = min(accel_limits_turns[1], clipped_accel_coast_interp)
clipped_accel_coast = max(accel_coast, accel_clip[0])
clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast])
accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp)
if force_slow_decel:
v_cruise = 0.0
# clip limits, cannot init MPC outside of bounds
accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05)
accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05)
self.lead_one = sm['radarState'].leadOne
self.lead_two = sm['radarState'].leadTwo
self.mpc.set_weights(sm['frogpilotPlan'].accelerationJerk, sm['frogpilotPlan'].dangerJerk, sm['frogpilotPlan'].speedJerk, prev_accel_constraint, personality=sm['controlsState'].personality)
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
self.mpc.update(self.lead_one, self.lead_two, v_cruise, x, v, a, j, sm['frogpilotPlan'].tFollow,
sm['frogpilotPlan'].trackingLead, personality=sm['controlsState'].personality)
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, sm['frogpilotPlan'].tFollow, frogpilot_toggles, personality=sm['controlsState'].personality)
self.a_desired_trajectory_full = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
@@ -237,10 +169,28 @@ class LongitudinalPlanner:
# Interpolate 0.05 seconds and save as starting point for next iteration
a_prev = self.a_desired
self.a_desired = float(interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory))
self.a_desired = float(np.interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory))
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0
def publish(self, classic_model, tomb_raider, sm, pm, frogpilot_toggles):
action_t = frogpilot_toggles.longitudinalActuatorDelay + DT_MDL
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
action_t=action_t, vEgoStopping=frogpilot_toggles.vEgoStopping)
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
if mode == 'acc':
output_a_target = output_a_target_mpc
self.output_should_stop = output_should_stop_mpc
else:
output_a_target = min(output_a_target_mpc, output_a_target_e2e)
self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc
for idx in range(2):
accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05)
self.output_a_target = np.clip(output_a_target, accel_clip[0], accel_clip[1])
self.prev_accel_clip = accel_clip
def publish(self, sm, pm):
plan_send = messaging.new_message('longitudinalPlan')
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState'])
@@ -254,31 +204,13 @@ class LongitudinalPlanner:
longitudinalPlan.accels = self.a_desired_trajectory.tolist()
longitudinalPlan.jerks = self.j_desired_trajectory.tolist()
longitudinalPlan.hasLead = self.lead_one.status
longitudinalPlan.hasLead = sm['radarState'].leadOne.status
longitudinalPlan.longitudinalPlanSource = self.mpc.source
longitudinalPlan.fcw = self.fcw
action_t = frogpilot_toggles.longitudinalActuatorDelay + DT_MDL
if tomb_raider:
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan_tomb_raider(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
action_t=action_t, vEgoStopping=self.CP.vEgoStopping)
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
if self.mode == 'acc':
a_target = output_a_target_mpc
should_stop = output_should_stop_mpc
else:
a_target = min(output_a_target_mpc, output_a_target_e2e)
should_stop = output_should_stop_e2e or output_should_stop_mpc
elif classic_model:
a_target, should_stop = get_accel_from_plan_classic(self.CP, longitudinalPlan.speeds, longitudinalPlan.accels, frogpilot_toggles.longitudinalActuatorDelay, frogpilot_toggles.vEgoStopping)
else:
a_target, should_stop = get_accel_from_plan(longitudinalPlan.speeds, longitudinalPlan.accels,
action_t=action_t, vEgoStopping=frogpilot_toggles.vEgoStopping)
longitudinalPlan.aTarget = float(a_target)
longitudinalPlan.shouldStop = bool(should_stop) or sm['frogpilotPlan'].forcingStopLength < 1
longitudinalPlan.aTarget = float(self.output_a_target)
longitudinalPlan.shouldStop = bool(self.output_should_stop)
longitudinalPlan.allowBrake = True
longitudinalPlan.allowThrottle = self.allow_throttle
longitudinalPlan.allowThrottle = bool(self.allow_throttle)
pm.send('longitudinalPlan', plan_send)
+17 -26
View File
@@ -1,9 +1,6 @@
import numpy as np
from numbers import Number
from openpilot.common.numpy_fast import clip, interp
class PIDController:
def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
self._k_p = k_p
@@ -17,10 +14,8 @@ class PIDController:
if isinstance(self._k_d, Number):
self._k_d = [[0], [self._k_d]]
self.pos_limit = pos_limit
self.neg_limit = neg_limit
self.set_limits(pos_limit, neg_limit)
self.i_unwind_rate = 0.3 / rate
self.i_rate = 1.0 / rate
self.speed = 0.0
@@ -28,19 +23,15 @@ class PIDController:
@property
def k_p(self):
return interp(self.speed, self._k_p[0], self._k_p[1])
return np.interp(self.speed, self._k_p[0], self._k_p[1])
@property
def k_i(self):
return interp(self.speed, self._k_i[0], self._k_i[1])
return np.interp(self.speed, self._k_i[0], self._k_i[1])
@property
def k_d(self):
return interp(self.speed, self._k_d[0], self._k_d[1])
@property
def error_integral(self):
return self.i/self.k_i
return np.interp(self.speed, self._k_d[0], self._k_d[1])
def reset(self):
self.p = 0.0
@@ -49,25 +40,25 @@ class PIDController:
self.f = 0.0
self.control = 0
def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False):
self.speed = speed
def set_limits(self, pos_limit, neg_limit):
self.pos_limit = pos_limit
self.neg_limit = neg_limit
def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False):
self.speed = speed
self.p = float(error) * self.k_p
self.f = feedforward * self.k_f
self.d = error_rate * self.k_d
if override:
self.i -= self.i_unwind_rate * float(np.sign(self.i))
else:
if not freeze_integrator:
self.i = self.i + error * self.k_i * self.i_rate
if not freeze_integrator:
i = self.i + error * self.k_i * self.i_rate
# Clip i to prevent exceeding control limits
control_no_i = self.p + self.d + self.f
control_no_i = clip(control_no_i, self.neg_limit, self.pos_limit)
self.i = clip(self.i, self.neg_limit - control_no_i, self.pos_limit - control_no_i)
# Don't allow windup if already clipping
test_control = self.p + i + self.d + self.f
i_upperbound = self.i if test_control > self.pos_limit else self.pos_limit
i_lowerbound = self.i if test_control < self.neg_limit else self.neg_limit
self.i = np.clip(i, i_lowerbound, i_upperbound)
control = self.p + self.i + self.d + self.f
self.control = clip(control, self.neg_limit, self.pos_limit)
self.control = np.clip(control, self.neg_limit, self.pos_limit)
return self.control
+3 -4
View File
@@ -37,14 +37,13 @@ def plannerd_thread():
# FrogPilot variables
frogpilot_toggles = get_frogpilot_toggles()
classic_model = frogpilot_toggles.classic_model
tomb_raider = frogpilot_toggles.tomb_raider
classic_longitudinal = frogpilot_toggles.classic_longitudinal
while True:
sm.update()
if sm.updated['modelV2']:
longitudinal_planner.update(tomb_raider, sm, frogpilot_toggles)
longitudinal_planner.publish(classic_model, tomb_raider, sm, pm, frogpilot_toggles)
longitudinal_planner.update(sm, classic_longitudinal, frogpilot_toggles)
longitudinal_planner.publish(sm, pm)
publish_ui_plan(sm, pm, longitudinal_planner)
# Update FrogPilot variables
+33 -25
View File
@@ -6,7 +6,7 @@ from types import SimpleNamespace
from typing import Any
import capnp
from cereal import messaging, log, car
from cereal import messaging, log, car, custom
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.numpy_fast import interp
from openpilot.common.params import Params
@@ -15,7 +15,7 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.simple_kalman import KF1D
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles
from openpilot.frogpilot.common.frogpilot_variables import THRESHOLD, get_frogpilot_toggles
# Default lead acceleration decay set to 50% at 1s
_LEAD_ACCEL_TAU = 1.5
@@ -63,7 +63,9 @@ class Track:
self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K)
# FrogPilot variables
self.lead_track_id = 0
self.leadTrackID = 0
self.radarfulFilter = FirstOrderFilter(0, 0.5, self.K_A[0][1])
def update(self, d_rel: float, y_rel: float, v_rel: float, v_lead: float, measured: float):
# relative values, copy
@@ -102,11 +104,10 @@ class Track:
"modelProb": model_prob,
"radar": True,
"radarTrackId": self.identifier,
"farLead": False,
}
def potential_adjacent_lead(self, left: bool, standstill: bool, model_data: capnp._DynamicStructReader):
if standstill or self.vLeadK < 1 or self.lead_track_id == self.identifier:
if standstill or self.vLead < 1 or self.leadTrackID == self.identifier:
return False
if left:
@@ -117,13 +118,18 @@ class Track:
return -self.yRel > right_lane
def potential_far_lead(self, standstill: bool, model_data: capnp._DynamicStructReader):
if standstill or self.vLeadK < 1 or abs(self.yRel) > 1:
if standstill or self.vLead < 1 or abs(self.yRel) > 1:
return False
left_lane = interp(self.dRel, model_data.laneLines[1].x, model_data.laneLines[1].y)
right_lane = interp(self.dRel, model_data.laneLines[2].x, model_data.laneLines[2].y)
return left_lane < -self.yRel < right_lane
if left_lane < -self.yRel < right_lane:
self.radarfulFilter.update(1)
return True
else:
self.radarfulFilter.update(0)
return False
def potential_low_speed_lead(self, v_ego: float):
# stop for stuff in front of you and low speed, even without model confirmation
@@ -181,13 +187,12 @@ def get_RadarState_from_vision(lead_msg: capnp._DynamicStructReader, v_ego: floa
"status": True,
"radar": False,
"radarTrackId": -1,
"farLead": False,
}
def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capnp._DynamicStructReader,
model_v_ego: float, model_data: capnp._DynamicStructReader, standstill: bool,
frogpilot_toggles: SimpleNamespace, frogpilotCarState: capnp._DynamicStructReader,
frogpilot_toggles: SimpleNamespace, frogpilotPlan: capnp._DynamicStructReader,
low_speed_override: bool = True) -> dict[str, Any]:
# Determine leads, this is where the essential logic happens
if len(tracks) > 0 and ready and lead_msg.prob > frogpilot_toggles.lead_detection_probability:
@@ -211,18 +216,16 @@ def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capn
lead_dict = closest_track.get_RadarState()
if not lead_dict['status'] and len(tracks) > 0:
far_lead_tracks = [c for c in tracks.values() if c.potential_far_lead(standstill, model_data)]
far_lead_tracks = [c for c in tracks.values() if c.potential_far_lead(standstill, model_data) and c.radarfulFilter.x >= THRESHOLD]
if len(far_lead_tracks) > 0:
closest_track = min(far_lead_tracks, key=lambda c: c.dRel)
lead_dict = closest_track.get_RadarState()
lead_dict['farLead'] = True
lead_dict['vLead'] = lead_dict['vLeadK']
for track in tracks.values():
track.lead_track_id = lead_dict.get('radarTrackId', -1)
track.leadTrackID = lead_dict.get('radarTrackId', -1)
if 'dRel' in lead_dict:
lead_dict['dRel'] -= frogpilot_toggles.increased_stopped_distance if not frogpilotCarState.trafficModeEnabled else 0
lead_dict['dRel'] -= frogpilotPlan.increasedStoppedDistance
return lead_dict
@@ -255,9 +258,9 @@ class RadarD:
self.ready = False
# FrogPilot variables
self.frogpilot_toggles = get_frogpilot_toggles()
self.frogpilot_radar_state: capnp._DynamicStructBuilder | None = None
self.classic_model = self.frogpilot_toggles.classic_model
self.frogpilot_toggles = get_frogpilot_toggles()
def update(self, sm: messaging.SubMaster, rr):
self.ready = sm.seen['modelV2']
@@ -302,20 +305,20 @@ class RadarD:
self.radar_state.radarErrors = list(radar_errors)
self.radar_state.carStateMonoTime = sm.logMonoTime['carState']
if self.classic_model and len(sm['modelV2'].temporalPose.trans):
model_v_ego = sm['modelV2'].temporalPose.trans[0]
elif len(sm['modelV2'].velocity.x):
self.frogpilot_radar_state = custom.FrogPilotRadarState.new_message()
if len(sm['modelV2'].velocity.x):
model_v_ego = sm['modelV2'].velocity.x[0]
else:
model_v_ego = self.v_ego
leads_v3 = sm['modelV2'].leadsV3
if len(leads_v3) > 1:
self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, sm['modelV2'], sm['carState'].standstill, self.frogpilot_toggles, sm['frogpilotCarState'], low_speed_override=True)
self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, sm['modelV2'], sm['carState'].standstill, self.frogpilot_toggles, sm['frogpilotCarState'], low_speed_override=False)
self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, sm['modelV2'], sm['carState'].standstill, self.frogpilot_toggles, sm['frogpilotPlan'], low_speed_override=True)
self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, sm['modelV2'], sm['carState'].standstill, self.frogpilot_toggles, sm['frogpilotPlan'], low_speed_override=False)
if self.frogpilot_toggles.adjacent_lead_tracking and self.ready:
self.radar_state.leadLeft = get_adjacent_lead(self.tracks, sm['carState'].standstill, sm['modelV2'], left=True)
self.radar_state.leadRight = get_adjacent_lead(self.tracks, sm['carState'].standstill, sm['modelV2'], left=False)
self.frogpilot_radar_state.leadLeft = get_adjacent_lead(self.tracks, sm['carState'].standstill, sm['modelV2'], left=True)
self.frogpilot_radar_state.leadRight = get_adjacent_lead(self.tracks, sm['carState'].standstill, sm['modelV2'], left=False)
# Update FrogPilot variables
if sm['frogpilotPlan'].togglesUpdated:
@@ -330,6 +333,11 @@ class RadarD:
radar_msg.radarState.cumLagMs = lag_ms
pm.send("radarState", radar_msg)
frogpilot_radar_msg = messaging.new_message("frogpilotRadarState")
frogpilot_radar_msg.valid = self.radar_state_valid
frogpilot_radar_msg.frogpilotRadarState = self.frogpilot_radar_state
pm.send("frogpilotRadarState", frogpilot_radar_msg)
# publish tracks for UI debugging (keep last)
tracks_msg = messaging.new_message('liveTracks', len(self.tracks))
tracks_msg.valid = self.radar_state_valid
@@ -359,8 +367,8 @@ def main():
# *** setup messaging
can_sock = messaging.sub_sock('can')
sm = messaging.SubMaster(['modelV2', 'carState', 'frogpilotCarState', 'frogpilotPlan'], frequency=int(1./DT_CTRL))
pm = messaging.PubMaster(['radarState', 'liveTracks'])
sm = messaging.SubMaster(['modelV2', 'carState', 'frogpilotPlan'], frequency=int(1./DT_CTRL), ignore_alive=['frogpilotPlan'], ignore_valid=['frogpilotPlan'])
pm = messaging.PubMaster(['radarState', 'liveTracks', 'frogpilotRadarState'])
RI = RadarInterface(CP)
+5 -2
View File
@@ -5,7 +5,7 @@ import json
import numpy as np
import cereal.messaging as messaging
from cereal import car
from cereal import car, custom
from cereal import log
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, DT_MDL
@@ -187,6 +187,9 @@ def main():
# FrogPilot variables
frogpilot_toggles = get_frogpilot_toggles()
with custom.FrogPilotCarParams.from_bytes(params_reader.get("FrogPilotCarParams", block=True)) as msg:
FPCP = msg
while True:
sm.update()
if sm.all_checks():
@@ -237,7 +240,7 @@ def main():
0.2 <= liveParameters.stiffnessFactor <= 5.0,
min_sr <= liveParameters.steerRatio <= max_sr,
))
if CP.carFingerprint == "RAM_HD" or CP.carName == "subaru" and CP.lateralTuning.which() == "torque":
if CP.carFingerprint == "RAM_HD" or CP.carName == "subaru" and FPCP.lateralTuning.which() == "torque":
liveParameters.valid = True
liveParameters.steerRatioStd = float(P[States.STEER_RATIO].item())
liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS].item())
+15 -15
View File
@@ -3,7 +3,7 @@ import numpy as np
from collections import deque, defaultdict
import cereal.messaging as messaging
from cereal import car, log
from cereal import car, custom, log
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.filter_simple import FirstOrderFilter
@@ -52,7 +52,7 @@ class TorqueBuckets(PointBuckets):
class TorqueEstimator(ParameterEstimator):
def __init__(self, CP, decimated=False):
def __init__(self, CP, FPCP, decimated=False):
self.hist_len = int(HISTORY / DT_MDL)
self.lag = 0.0
if decimated:
@@ -72,11 +72,11 @@ class TorqueEstimator(ParameterEstimator):
self.offline_friction = 0.0
self.offline_latAccelFactor = 0.0
self.resets = 0.0
self.use_params = CP.carName in ALLOWED_CARS and CP.lateralTuning.which() == 'torque'
self.use_params = CP.carName in ALLOWED_CARS and FPCP.lateralTuning.which() == 'torque'
if CP.lateralTuning.which() == 'torque':
self.offline_friction = CP.lateralTuning.torque.friction
self.offline_latAccelFactor = CP.lateralTuning.torque.latAccelFactor
if FPCP.lateralTuning.which() == 'torque':
self.offline_friction = FPCP.lateralTuning.torque.friction
self.offline_latAccelFactor = FPCP.lateralTuning.torque.latAccelFactor
self.reset()
@@ -102,7 +102,7 @@ class TorqueEstimator(ParameterEstimator):
cache_ltp = log_evt.liveTorqueParameters
with car.CarParams.from_bytes(params_cache) as msg:
cache_CP = msg
if self.get_restore_key(cache_CP, cache_ltp.version) == self.get_restore_key(CP, VERSION):
if self.get_restore_key(cache_CP, FPCP, cache_ltp.version) == self.get_restore_key(CP, FPCP, VERSION):
if cache_ltp.liveValid:
initial_params = {
'latAccelFactor': cache_ltp.latAccelFactorFiltered,
@@ -121,12 +121,12 @@ class TorqueEstimator(ParameterEstimator):
for param in initial_params:
self.filtered_params[param] = FirstOrderFilter(initial_params[param], self.decay, DT_MDL)
def get_restore_key(self, CP, version):
def get_restore_key(self, CP, FPCP, version):
a, b = None, None
if CP.lateralTuning.which() == 'torque':
a = CP.lateralTuning.torque.friction
b = CP.lateralTuning.torque.latAccelFactor
return (CP.carFingerprint, CP.lateralTuning.which(), a, b, version)
if FPCP.lateralTuning.which() == 'torque':
a = FPCP.lateralTuning.torque.friction
b = FPCP.lateralTuning.torque.latAccelFactor
return (CP.carFingerprint, FPCP.lateralTuning.which(), a, b, version)
def reset(self):
self.resets += 1.0
@@ -227,14 +227,14 @@ def main(demo=False):
sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveLocationKalman', 'liveDelay', 'frogpilotPlan'], poll='liveLocationKalman')
params = Params()
with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP:
estimator = TorqueEstimator(CP)
with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP, custom.FrogPilotCarParams.from_bytes(params.get("FrogPilotCarParams", block=True)) as FPCP:
estimator = TorqueEstimator(CP, FPCP)
# FrogPilot variables
frogpilot_toggles = get_frogpilot_toggles()
if not frogpilot_toggles.liveValid:
estimator = TorqueEstimator(CP, decimated=True)
estimator = TorqueEstimator(CP, FPCP, decimated=True)
while True:
sm.update()
+4 -3
View File
@@ -178,7 +178,7 @@ def main(demo=False):
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
# messaging
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "frogpilotModelV2"])
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay", "frogpilotPlan"])
publish_state = PublishState()
@@ -288,6 +288,7 @@ def main(demo=False):
if model_output is not None:
modelv2_send = messaging.new_message('modelV2')
frogpilot_modelv2_send = messaging.new_message('frogpilotModelV2')
drivingdata_send = messaging.new_message('drivingModelData')
posenet_send = messaging.new_message('cameraOdometry')
fill_model_msg(drivingdata_send, modelv2_send, model_output, v_ego, steer_delay,
@@ -301,13 +302,13 @@ def main(demo=False):
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, sm['frogpilotPlan'], frogpilot_toggles)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
modelv2_send.modelV2.meta.turnDirection = DH.turn_direction
frogpilot_modelv2_send.frogpilotModelV2.turnDirection = DH.turn_direction
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
drivingdata_send.drivingModelData.meta.turnDirection = DH.turn_direction
fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)
pm.send('modelV2', modelv2_send)
pm.send('frogpilotModelV2', frogpilot_modelv2_send)
pm.send('drivingModelData', drivingdata_send)
pm.send('cameraOdometry', posenet_send)
+2 -2
View File
@@ -63,8 +63,6 @@ class RouteEngine:
self.mapbox_host = "https://api.mapbox.com"
# FrogPilot variables
self.frogpilot_toggles = get_frogpilot_toggles()
self.approaching_intersection = False
self.approaching_turn = False
@@ -73,6 +71,8 @@ class RouteEngine:
self.stop_coord = []
self.stop_signal = []
self.frogpilot_toggles = get_frogpilot_toggles()
def update(self):
self.sm.update(0)
+15
View File
@@ -119,6 +119,13 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
cereal::CarParams::SafetyModel safety_model;
uint16_t safety_param;
std::string fp_params = p.get("FrogPilotCarParams");
AlignedBuffer fp_aligned_buf;
std::unique_ptr<capnp::FlatArrayMessageReader> fp_msg;
if (fp_params.size() > 0) {
fp_msg.reset(new capnp::FlatArrayMessageReader(fp_aligned_buf.align(fp_params.data(), fp_params.size())));
}
auto safety_configs = car_params.getSafetyConfigs();
uint16_t alternative_experience = car_params.getAlternativeExperience();
for (uint32_t i = 0; i < pandas.size(); i++) {
@@ -133,6 +140,14 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
safety_param = 0U;
}
if (fp_msg) {
auto fp_root = fp_msg->getRoot<cereal::FrogPilotCarParams>();
auto fp_safety_configs = fp_root.getSafetyConfigs();
if (fp_safety_configs.size() > i) {
safety_param |= fp_safety_configs[i].getSafetyParam();
}
}
LOGW("panda %d: setting safety model: %d, param: %d, alternative experience: %d", i, (int)safety_model, safety_param, alternative_experience);
panda->set_alternative_experience(alternative_experience);
panda->set_safety_model(safety_model, safety_param);
+2 -2
View File
@@ -191,13 +191,13 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
#endif
left_widget->addWidget(new DriveStats);
ModelReview *modelReview = new ModelReview(this);
FrogPilotModelReview *modelReview = new FrogPilotModelReview(this);
left_widget->addWidget(modelReview);
left_widget->setStyleSheet("border-radius: 10px;");
left_widget->setCurrentIndex(1);
connect(modelReview, &ModelReview::driveRated, [=]() {
connect(modelReview, &FrogPilotModelReview::driveRated, [=]() {
left_widget->setCurrentIndex(1);
});
connect(frogpilotUIState(), &FrogPilotUIState::reviewModel, [=]() {
+1 -1
View File
@@ -14,7 +14,7 @@
#include "common/transformations/orientation.hpp"
#include "cereal/messaging/messaging.h"
const QString MAPBOX_TOKEN = QString::fromStdString(Params("/cache/params").get("MapboxSecretKey"));
const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("MapboxSecretKey"));
const QString MAPS_HOST = QStringLiteral("https://api.mapbox.com");
const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db";
+25 -7
View File
@@ -389,18 +389,36 @@ void NavManager::parseLocationsResponse(const QString &response, bool success) {
if (!success || response == prev_response) return;
prev_response = response;
QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8());
if (doc.isNull()) {
QString trimmed = response.trimmed();
if (trimmed.isEmpty() || trimmed == "null") {
locations = QJsonArray();
emit updated();
return;
}
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(trimmed.toUtf8(), &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "JSON Parse failed on navigation locations" << response;
return;
}
// set last activity time.
auto remote_locations = doc.array();
QJsonArray remote_locations;
if (doc.isArray()) {
remote_locations = doc.array();
} else if (doc.isObject() && doc.object().value("locations").isArray()) {
remote_locations = doc.object().value("locations").toArray();
} else {
locations = QJsonArray();
emit updated();
return;
}
for (QJsonValueRef loc : remote_locations) {
auto obj = loc.toObject();
auto serverTime = convertTimestampToEpoch(obj["modified"].toString());
obj.insert("time", qMax(serverTime, getLastActivity(obj)));
QJsonObject obj = loc.toObject();
qint64 server_time = convertTimestampToEpoch(obj.value("modified").toString());
obj.insert("time", qMax(server_time, getLastActivity(obj)));
loc = obj;
}
+1 -1
View File
@@ -485,7 +485,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
bool tuningLevelConfirmed = params.getBool("TuningLevelConfirmed");
if (!tuningLevelConfirmed) {
int frogpilotHours = paramsTracking.getInt("FrogPilotMinutes") / 60;
int frogpilotHours = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object().value("FrogPilotSeconds").toInt() / (60 * 60);
int openpilotHours = params.getInt("KonikMinutes") / 60 + params.getInt("openpilotMinutes") / 60;
if (frogpilotHours < 1 && openpilotHours < 100) {
+2 -3
View File
@@ -48,12 +48,11 @@ private:
QStackedWidget *panel_widget;
// FrogPilot variables
Params params;
Params paramsTracking{"/cache/tracking"};
bool panelOpen;
bool subPanelOpen;
bool subSubPanelOpen;
Params params;
};
class DevicePanel : public ListWidget {
+2 -4
View File
@@ -33,7 +33,6 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
ParamControl *automaticUpdatesToggle = new ParamControl("AutomaticUpdates", tr("Automatically Update FrogPilot"),
tr("FrogPilot will automatically update itself and it's assets when you're offroad and have an active internet connection."), "");
automaticUpdatesToggle->setVisible(params.getBool("IsReleaseBranch"));
connect(automaticUpdatesToggle, &ToggleControl::toggleFlipped, this, &updateFrogPilotToggles);
addItem(automaticUpdatesToggle);
// download update btn
@@ -65,7 +64,6 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
if (!frogpilotUIState()->frogpilot_toggles.value("frogs_go_moo").toBool()) {
branches.removeAll("FrogPilot-Development");
branches.removeAll("FrogPilot-Vetting");
branches.removeAll("FrogPilot-Test");
branches.removeAll("MAKE-PRS-HERE");
}
for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) {
@@ -98,8 +96,8 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL"));
connect(uninstallBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), tr("Uninstall"), this)) {
if (FrogPilotConfirmationDialog::yesorno(tr("Do you want to delete deep storage FrogPilot assets? This includes your toggle settings for quick reinstalls."), this)) {
if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure? This is 100% unrecoverable and if you reinstall FrogPilot you'll lose all your previous settings!"), this)) {
if (FrogPilotConfirmationDialog::yesorno(tr("Do you want to perform a full factory reset? All saved assets and settings will be permanently deleted!"), this)) {
if (FrogPilotConfirmationDialog::yesorno(tr("This is a complete factory reset and cannot be undone. Are you absolutely sure you want to continue?"), this)) {
std::system("rm -rf /cache/params/d");
}
}
+29 -11
View File
@@ -6,7 +6,7 @@
#include "selfdrive/ui/qt/util.h"
void OnroadAlerts::updateState(const UIState &s, const FrogPilotUIState &fs) {
Alert a = getAlert(*(s.sm), s.scene.started_frame, fs.frogpilot_toggles);
Alert a = getAlert(*(s.sm), *(fs.sm), s.scene.started_frame, fs.frogpilot_toggles);
if (!alert.equal(a)) {
if (alert.status == cereal::ControlsState::AlertStatus::NORMAL && fs.frogpilot_toggles.value("hide_alerts").toBool()) {
clear();
@@ -28,19 +28,24 @@ void OnroadAlerts::clear() {
update();
}
OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started_frame, QJsonObject &frogpilot_toggles) {
OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, const SubMaster &fpsm, uint64_t started_frame, QJsonObject &frogpilot_toggles) {
const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState();
const cereal::FrogPilotControlsState::Reader &fpcs = fpsm["frogpilotControlsState"].getFrogpilotControlsState();
const uint64_t controls_frame = sm.rcv_frame("controlsState");
Alert a = {};
static QString crash_log_path = "/data/error_logs/error.txt";
if (QFile::exists(crash_log_path)) {
if (frogpilot_toggles.value("random_events").toBool()) {
a = {tr("openpilot crashed 💩"),
tr("Please post the \"Error Log\" in the FrogPilot Discord!"),
"openpilotCrashedRandomEvent",
cereal::ControlsState::AlertSize::MID,
cereal::ControlsState::AlertStatus::CRITICAL};
if (enableFerg) {
displayFerg = true;
} else {
a = {tr("openpilot crashed 💩"),
tr("Please post the \"Error Log\" in the FrogPilot Discord!"),
"openpilotCrashedRandomEvent",
cereal::ControlsState::AlertSize::MID,
cereal::ControlsState::AlertStatus::CRITICAL};
}
} else {
a = {tr("openpilot crashed"),
tr("Please post the \"Error Log\" in the FrogPilot Discord!"),
@@ -52,6 +57,11 @@ OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started
} else if (controls_frame >= started_frame) { // Don't get old alert.
a = {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(),
cs.getAlertType().cStr(), cs.getAlertSize(), cs.getAlertStatus()};
if (a.size == cereal::ControlsState::AlertSize::NONE) {
a = {fpcs.getAlertText1().cStr(), fpcs.getAlertText2().cStr(),
fpcs.getAlertType().cStr(), static_cast<cereal::ControlsState::AlertSize>(fpcs.getAlertSize()), static_cast<cereal::ControlsState::AlertStatus>(fpcs.getAlertStatus())};
}
}
if (!sm.updated("controlsState") && (sm.frame - started_frame) > 5 * UI_FREQ && !frogpilot_toggles.value("force_onroad").toBool()) {
@@ -81,6 +91,11 @@ OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started
}
void OnroadAlerts::paintEvent(QPaintEvent *event) {
if (displayFerg) {
QPainter p(this);
p.drawPixmap(QPoint((width() - ferg.width()) / 2, (height() - ferg.height()) / 2), ferg);
return;
}
if (alert.size == cereal::ControlsState::AlertSize::NONE) {
alertHeight = 0;
return;
@@ -107,7 +122,7 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
// draw background + gradient
p.setPen(Qt::NoPen);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setBrush(QBrush(alert_colors[alert.status]));
p.setBrush(QBrush(frogpilot_alert_colors[static_cast<cereal::FrogPilotControlsState::AlertStatus>(alert.status)]));
p.drawRoundedRect(r, radius, radius);
QLinearGradient g(0, r.y(), 0, r.bottom());
@@ -124,12 +139,15 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
p.setPen(QColor(0xff, 0xff, 0xff));
p.setRenderHint(QPainter::TextAntialiasing);
if (alert.size == cereal::ControlsState::AlertSize::SMALL) {
p.setFont(InterFont(sidebarsOpen ? 64 : 74, QFont::DemiBold));
bool long_alert1 = alert.text1.length() > 40;
p.setFont(InterFont(long_alert1 && sidebarsOpen ? 64 : 74, QFont::DemiBold));
p.drawText(r, Qt::AlignCenter, alert.text1);
} else if (alert.size == cereal::ControlsState::AlertSize::MID) {
p.setFont(InterFont(sidebarsOpen ? 78 : 88, QFont::Bold));
bool long_alert1 = alert.text1.length() > 30;
p.setFont(InterFont(long_alert1 && sidebarsOpen ? 78 : 88, QFont::Bold));
p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, alert.text1);
p.setFont(InterFont(sidebarsOpen ? 56 : 66));
bool long_alert2 = alert.text2.length() > 40;
p.setFont(InterFont(long_alert2 && sidebarsOpen ? 56 : 66));
p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, alert.text2);
} else if (alert.size == cereal::ControlsState::AlertSize::FULL) {
bool l = alert.text1.length() > 15;
+13 -4
View File
@@ -13,8 +13,18 @@ public:
void clear();
// FrogPilot variables
bool displayFerg;
bool enableFerg;
int alertHeight;
const QMap<cereal::FrogPilotControlsState::AlertStatus, QColor> frogpilot_alert_colors = {
{cereal::FrogPilotControlsState::AlertStatus::NORMAL, QColor(0x15, 0x15, 0x15, 0xf1)},
{cereal::FrogPilotControlsState::AlertStatus::USER_PROMPT, QColor(0xDA, 0x6F, 0x25, 0xf1)},
{cereal::FrogPilotControlsState::AlertStatus::CRITICAL, QColor(0xC9, 0x22, 0x31, 0xf1)},
{cereal::FrogPilotControlsState::AlertStatus::FROGPILOT, QColor(0x17, 0x86, 0x44, 0xf1)},
};
protected:
struct Alert {
QString text1;
@@ -32,17 +42,16 @@ protected:
{cereal::ControlsState::AlertStatus::NORMAL, QColor(0x15, 0x15, 0x15, 0xf1)},
{cereal::ControlsState::AlertStatus::USER_PROMPT, QColor(0xDA, 0x6F, 0x25, 0xf1)},
{cereal::ControlsState::AlertStatus::CRITICAL, QColor(0xC9, 0x22, 0x31, 0xf1)},
// FrogPilot alert colors
{cereal::ControlsState::AlertStatus::FROGPILOT, QColor(0x17, 0x86, 0x44, 0xf1)},
};
void paintEvent(QPaintEvent*) override;
OnroadAlerts::Alert getAlert(const SubMaster &sm, uint64_t started_frame, QJsonObject &frogpilot_toggles);
OnroadAlerts::Alert getAlert(const SubMaster &sm, const SubMaster &fpsm, uint64_t started_frame, QJsonObject &frogpilot_toggles);
QColor bg;
Alert alert = {};
// FrogPilot variables
bool sidebarsOpen;
QPixmap ferg = loadPixmap("../../frogpilot/assets/random_events/icons/ferg.png", {1080, 720});
};
+16 -18
View File
@@ -85,8 +85,10 @@ void AnnotatedCameraWidget::updateState(const UIState &s, const FrogPilotUIState
has_us_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::MUTCD);
has_us_speed_limit |= frogpilot_toggles.value("show_speed_limits").toBool() || frogpilot_toggles.value("speed_limit_controller").toBool();
has_us_speed_limit &= !frogpilot_toggles.value("speed_limit_vienna").toBool();
has_us_speed_limit &= !frogpilot_toggles.value("hide_speed_limit").toBool() || frogpilotPlan.getSpeedLimitChanged();
has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA);
has_eu_speed_limit |= (frogpilot_toggles.value("show_speed_limits").toBool() || frogpilot_toggles.value("speed_limit_controller").toBool()) && frogpilot_toggles.value("speed_limit_vienna").toBool();
has_eu_speed_limit &= !frogpilot_toggles.value("hide_speed_limit").toBool() || frogpilotPlan.getSpeedLimitChanged();
is_metric = s.scene.is_metric;
speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph");
hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
@@ -191,7 +193,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p, const cereal::FrogPilotPlan::Re
const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin);
// US/Canada (MUTCD style) sign
if (has_us_speed_limit && !frogpilot_toggles.value("hide_speed_limit").toBool()) {
if (has_us_speed_limit) {
p.setPen(Qt::NoPen);
p.setBrush(whiteColor());
p.drawRoundedRect(sign_rect, 24, 24);
@@ -218,7 +220,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p, const cereal::FrogPilotPlan::Re
}
// EU (Vienna style) sign
if (has_eu_speed_limit && !frogpilot_toggles.value("hide_speed_limit").toBool()) {
if (has_eu_speed_limit) {
p.setPen(Qt::NoPen);
p.setBrush(whiteColor());
p.drawEllipse(sign_rect);
@@ -459,13 +461,11 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState
const float v_rel = lead_data.getVRel();
float fillAlpha = 0;
if (frogpilotPlan.getTrackingLead() || adjacent) {
fillAlpha = 255 * (1.0 - (d_rel / leadBuff));
if (v_rel < 0) {
fillAlpha += 255 * (-1 * (v_rel / speedBuff));
}
fillAlpha = std::clamp(fillAlpha, 0.f, 255.f);
fillAlpha = 255 * (1.0 - (d_rel / leadBuff));
if (v_rel < 0) {
fillAlpha += 255 * (-1 * (v_rel / speedBuff));
}
fillAlpha = std::clamp(fillAlpha, 0.f, 255.f);
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
float x = std::clamp((float)vd.x(), 0.f, width() - sz / 2);
@@ -475,11 +475,7 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState
float g_yo = sz / 10;
QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_yo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}};
if (lead_data.getFarLead()) {
painter.setBrush(QColor(0, 255, 255, 255));
} else {
painter.setBrush(QColor(218, 202, 37, 255));
}
painter.setBrush(QColor(218, 202, 37, 255));
painter.drawPolygon(glow, std::size(glow));
// chevron
@@ -568,19 +564,21 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) {
if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame && !frogpilot_toggles.value("hide_lead_marker").toBool()) {
auto radar_state = sm["radarState"].getRadarState();
auto frogpilot_radar_state = fpsm["frogpilotRadarState"].getFrogpilotRadarState();
update_leads(s, radar_state, model.getPosition());
update_leads_frogpilot(s, fs, frogpilot_radar_state, model.getPosition());
auto lead_one = radar_state.getLeadOne();
auto lead_two = radar_state.getLeadTwo();
auto lead_left = radar_state.getLeadLeft();
auto lead_right = radar_state.getLeadRight();
auto lead_left = frogpilot_radar_state.getLeadLeft();
auto lead_right = frogpilot_radar_state.getLeadRight();
if (lead_left.getStatus()) {
drawLead(painter, lead_left, frogpilotPlan, s->scene.lead_vertices[2], frogpilot_nvg->blueColor(), fs, true);
drawLead(painter, reinterpret_cast<const cereal::RadarState::LeadData::Reader &>(lead_left), frogpilotPlan, fs->frogpilot_scene.lead_vertices[0], frogpilot_nvg->blueColor(), fs, true);
}
if (lead_right.getStatus()) {
drawLead(painter, lead_right, frogpilotPlan, s->scene.lead_vertices[3], frogpilot_nvg->purpleColor(), fs, true);
drawLead(painter, reinterpret_cast<const cereal::RadarState::LeadData::Reader &>(lead_right), frogpilotPlan, fs->frogpilot_scene.lead_vertices[1], frogpilot_nvg->purpleColor(), fs, true);
}
if (lead_one.getStatus()) {
drawLead(painter, lead_one, frogpilotPlan, s->scene.lead_vertices[0], fs->frogpilot_scene.lead_marker_color, fs);
drawLead(painter, lead_one, frogpilotPlan, s->scene.lead_vertices[0], lead_one.getModelProb() >= frogpilot_toggles.value("lead_detection_probability").toInt() ? fs->frogpilot_scene.lead_marker_color : whiteColor(), fs);
} else {
frogpilot_nvg->leadTextRect = QRect();
}
+13 -1
View File
@@ -103,7 +103,7 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) {
#ifdef ENABLE_MAPS
if (map != nullptr) {
bool sidebarVisible = geometry().x() > 0;
bool show_map = !sidebarVisible;
bool show_map = !sidebarVisible && !frogpilot_toggles.value("hide_map").toBool();
map->setVisible(show_map && !map->isVisible());
if (map->isVisible() && frogpilot_toggles.value("full_map").toBool()) {
nvg->frogpilot_nvg->bigMapOpen = false;
@@ -135,6 +135,13 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) {
}
void OnroadWindow::createMapWidget() {
FrogPilotUIState &fs = *frogpilotUIState();
QJsonObject &frogpilot_toggles = fs.frogpilot_toggles;
if (frogpilot_toggles.value("hide_map").toBool()) {
return;
}
#ifdef ENABLE_MAPS
auto m = new MapPanel(get_mapbox_settings());
map = m;
@@ -158,6 +165,11 @@ void OnroadWindow::offroadTransition(bool offroad) {
}
#endif
alerts->clear();
if (!offroad) {
alerts->enableFerg = util::random_int(0, 1) == 1;
} else {
alerts->displayFerg = false;
}
}
void OnroadWindow::primeChanged(bool prime) {
+1 -1
View File
@@ -57,7 +57,7 @@ void Sidebar::updateTheme() {
sidebar_color2 = frogpilot_scene.use_stock_colors ? good_color : frogpilot_scene.sidebar_color2;
sidebar_color3 = frogpilot_scene.use_stock_colors ? good_color : frogpilot_scene.sidebar_color3;
if (util::random_int(0, 100) == 100 && frogpilot_toggles.value("random_events").toBool()) {
if (util::random_int(0, 100) == 69 && frogpilot_toggles.value("random_events").toBool()) {
loadImage("../../frogpilot/assets/random_events/icons/button_home", home_img, home_gif, home_btn.size(), this);
} else {
loadImage("../../frogpilot/assets/active_theme/icons/button_home", home_img, home_gif, home_btn.size(), this);
+7 -1
View File
@@ -49,7 +49,13 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons
}
if (!description->text().isEmpty()) {
description->setVisible(!description->isVisible());
if (description->isVisible()) {
emit hideDescriptionEvent();
description->setVisible(false);
} else {
description->setVisible(true);
}
}
});
+1
View File
@@ -65,6 +65,7 @@ public slots:
}
signals:
void hideDescriptionEvent();
void showDescriptionEvent();
protected:
+30 -25
View File
@@ -4,8 +4,9 @@ import time
import wave
from pathlib import Path
from typing import Any
from cereal import car, messaging
from cereal import car, custom, messaging
from openpilot.common.basedir import BASEDIR
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.realtime import Ratekeeper
@@ -27,9 +28,10 @@ AMBIENT_DB = 30 # DB where MIN_VOLUME is applied
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
FrogPilotAudibleAlert = custom.FrogPilotCarControl.HUDControl.AudibleAlert
sound_list: dict[int, tuple[str, int | None, float]] = {
sound_list: dict[Any, tuple[str, int | None, float]] = {
# AudibleAlert, file name, play count (none for infinite)
AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME),
AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME),
@@ -43,20 +45,20 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
# FrogPilot sounds
AudibleAlert.angry: ("angry.wav", 1, MAX_VOLUME),
AudibleAlert.continued: ("continued.wav", 1, MAX_VOLUME),
AudibleAlert.dejaVu: ("dejaVu.wav", 1, MAX_VOLUME),
AudibleAlert.doc: ("doc.wav", 1, MAX_VOLUME),
AudibleAlert.fart: ("fart.wav", 1, MAX_VOLUME),
AudibleAlert.firefox: ("firefox.wav", 1, MAX_VOLUME),
AudibleAlert.goat: ("goat.wav", None, MAX_VOLUME),
AudibleAlert.hal9000: ("hal9000.wav", 1, MAX_VOLUME),
AudibleAlert.mail: ("mail.wav", 1, MAX_VOLUME),
AudibleAlert.nessie: ("nessie.wav", 1, MAX_VOLUME),
AudibleAlert.noice: ("noice.wav", 1, MAX_VOLUME),
AudibleAlert.startup: ("startup.wav", 1, MAX_VOLUME),
AudibleAlert.thisIsFine: ("this_is_fine.wav", 1, MAX_VOLUME),
AudibleAlert.uwu: ("uwu.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.angry: ("angry.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.continued: ("continued.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.dejaVu: ("dejaVu.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.doc: ("doc.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.fart: ("fart.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.firefox: ("firefox.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.goat: ("goat.wav", None, MAX_VOLUME),
FrogPilotAudibleAlert.hal9000: ("hal9000.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.mail: ("mail.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.nessie: ("nessie.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.noice: ("noice.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.startup: ("startup.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.thisIsFine: ("this_is_fine.wav", 1, MAX_VOLUME),
FrogPilotAudibleAlert.uwu: ("uwu.wav", 1, MAX_VOLUME),
}
def check_controls_timeout_alert(sm):
@@ -80,8 +82,6 @@ class Soundd:
self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False)
# FrogPilot variables
self.frogpilot_toggles = get_frogpilot_toggles()
self.openpilot_crashed_played = False
self.restart_stream = False
@@ -92,6 +92,8 @@ class Soundd:
self.error_log = ERROR_LOGS_PATH / "error.txt"
self.random_events_directory = RANDOM_EVENTS_PATH / "sounds"
self.frogpilot_toggles = get_frogpilot_toggles()
self.update_frogpilot_sounds()
def load_sounds(self):
@@ -109,9 +111,7 @@ class Soundd:
elif sounds_path.exists():
wavefile = wave.open(str(sounds_path), 'r')
else:
if filename == "prompt_repeat.wav":
filename = "prompt.wav"
elif filename == "startup.wav":
if filename == "startup.wav":
filename = "engage.wav"
wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r')
@@ -161,13 +161,18 @@ class Soundd:
params_memory.remove("TestAlert")
elif not self.openpilot_crashed_played and self.error_log.is_file():
if self.frogpilot_toggles.random_events:
self.update_alert(AudibleAlert.fart)
self.update_alert(FrogPilotAudibleAlert.fart)
else:
self.update_alert(AudibleAlert.prompt)
self.openpilot_crashed_played = True
elif sm.updated['controlsState']:
new_alert = sm['controlsState'].alertSound.raw
new_frogpilot_alert = sm['frogpilotControlsState'].alertSound.raw
if new_alert == AudibleAlert.none and new_frogpilot_alert != FrogPilotAudibleAlert.none:
new_alert = new_frogpilot_alert
self.update_alert(new_alert)
elif check_controls_timeout_alert(sm):
self.update_alert(AudibleAlert.warningImmediate)
@@ -191,7 +196,7 @@ class Soundd:
# sounddevice must be imported after forking processes
import sounddevice as sd
sm = messaging.SubMaster(['controlsState', 'microphone', 'frogpilotPlan'])
sm = messaging.SubMaster(['controlsState', 'microphone', 'frogpilotControlsState', 'frogpilotPlan'])
with self.get_stream(sd) as stream:
rk = Ratekeeper(20)
@@ -246,8 +251,8 @@ class Soundd:
AudibleAlert.warningSoft: self.frogpilot_toggles.warningSoft_volume / 100.0,
AudibleAlert.warningImmediate: self.frogpilot_toggles.warningImmediate_volume / 100.0,
AudibleAlert.goat: self.frogpilot_toggles.prompt_volume / 100.0,
AudibleAlert.startup: self.frogpilot_toggles.engage_volume / 100.0
FrogPilotAudibleAlert.goat: self.frogpilot_toggles.prompt_volume / 100.0,
FrogPilotAudibleAlert.startup: self.frogpilot_toggles.engage_volume / 100.0
}
for sound in sound_list:
+90 -35
View File
@@ -5,7 +5,10 @@ import json
import os
import pathlib
import xml.etree.ElementTree as ET
from concurrent.futures import ThreadPoolExecutor, as_completed
from requests.adapters import HTTPAdapter
from typing import cast
from urllib3.util.retry import Retry
import requests
@@ -18,35 +21,43 @@ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
FUN_LANG_KEYS = {"caveman", "duck", "frog", "pirate", "shakespearean"}
FUN_PROMPT_TEMPLATE = """
You are a safety-critical UI style transformer for an openpilot fork. Rewrite the user's message (an English source string) into the style '{language}'
while preserving the original meaning, readability, and all functional elements. Output ONLY the transformed text, with no quotes or extra words.
You are a playful *style translator* for openpilot. Translate the following message (an English source string) into the style '{language}'. Output ONLY the translated text, with no quotes or extra words.
Output rules:
- Input: one English UI string from a Qt .ts file.
- Style key: {language}.
- Output: a fun, stylized rewrite that keeps technical structure intact.
Hard requirements:
1) Preserve placeholders, variables, and markup exactly as written: {{name}}, {{0}}, {{icu}}, %1, %n, %(speed)d, $SPEED, <b></b>, <a href=""></a>, etc.
2) Keep all non-translatable tokens unchanged: product/brand names (e.g., openpilot, ACC), file paths, error codes, part numbers.
3) Do not add, remove, or reorder placeholders. If grammar absolutely requires reordering, keep all placeholders intact and still produce a correct sentence; prefer wordings that avoid reordering.
4) Do not convert units or numbers (e.g., mphkm/h). Translate unit labels only if standard in the style and not part of a preserved token.
5) Maintain the same warning/priority level and imperative tone. Never soften or intensify safety messages (Do not, Warning, Critical).
5) Maintain the same warning/priority level and imperative tone. Never soften or intensify safety messages ("Do not…", "Warning", "Critical").
6) Preserve hotkeys/accelerators if present (e.g., &F, _O). If the exact letter is impossible, pick the nearest mnemonic but keep the marker.
7) Follow normal punctuation and casing rules while respecting all technical tokens.
7) Follow style punctuation and casing norms while respecting all technical tokens.
8) If ICU MessageFormat/plural/select syntax is present, keep the structure and variable names unchanged and rewrite only the human-readable text.
9) Keep the output as concise as the source. Do not append notes, explanations, or metadata.
If the source is ambiguous or cannot be safely adapted to the style without risking meaning loss, choose the safest literal rendering that preserves meaning.
If you cannot adapt it safely, return the source text unchanged.
Style Hints:
- caveman: Short, blunt sentences. Simple words. Little grammar. Example: "Me want food. You come now."
- duck: Quacky interjections, waddling rhythm, silly tone. Example: "Quack! What you mean? Waddle-waddle, quack!"
- frog: Croaky, ribbit-filled speech, jumpy tone. Example: "Ribbit! I hop to help you. Croak, ribbit!"
- pirate: Rough, nautical slang, dropped consonants, lots of "Arr!" Example: "Arr, ye scallywag! Hoist the sails 'n fetch me rum!"
- shakespearean: Flowery, old-fashioned English, thee/thou, dramatic flair. Example: "Prithee, good sir, thou dost jest most cruelly!"
Your entire reply must be a single line containing only the final rewritten text.
Keep length close to source; avoid bloat. Respond with the styled string only.
"""
OPENAI_PROMPT = """
You are a safety-critical UI translator for an openpilot fork. Translate the user's message (an English source string) into the locale '{language}'. Output ONLY the translated text, with no quotes or extra words.
You are a safety-critical UI translator for openpilot. Translate the following message (an English source string) into the locale '{language}'. Output ONLY the translated text, with no quotes or extra words.
Hard requirements:
1) Preserve placeholders, variables, and markup exactly as written: {{name}}, {{0}}, {{icu}}, %1, %n, %(speed)d, $SPEED, <b></b>, <a href=""></a>, etc.
2) Keep all non-translatable tokens unchanged: product/brand names (e.g., openpilot, ACC), file paths, error codes, part numbers.
3) Do not add, remove, or reorder placeholders. If grammar absolutely requires reordering, keep all placeholders intact and still produce a correct sentence; prefer wordings that avoid reordering.
4) Do not convert units or numbers (e.g., mphkm/h). Translate unit labels only if standard in the target locale and not part of a preserved token.
5) Maintain the same warning/priority level and imperative tone. Never soften or intensify safety messages (Do not, Warning, Critical).
5) Maintain the same warning/priority level and imperative tone. Never soften or intensify safety messages ("Do not…", "Warning", "Critical").
6) Preserve hotkeys/accelerators if present (e.g., &F, _O). If the exact letter is impossible, pick the nearest mnemonic but keep the marker.
7) Follow target-locale punctuation and casing norms while respecting all technical tokens.
8) If ICU MessageFormat/plural/select syntax is present, keep the structure and variable names unchanged and translate only the human-readable text.
@@ -58,7 +69,7 @@ Your entire reply must be a single line containing only the final translation.
"""
OPENAI_EVAL_PROMPT = """
You are a safety-critical reviewer for UI translations for an openpilot fork. Your job is to compare two candidate translations (A and B) of an English source string and select the safest, most accurate option in the locale '{language}'.
You are a safety-critical reviewer for UI translations for openpilot. Your job is to compare two candidate translations (A and B) of an English source string and select the safest, most accurate option in the locale '{language}'.
Output rules:
- Return ONLY one line containing exactly one of these: the full text of Translation A, or the full text of Translation B, or the exact Source string.
@@ -89,10 +100,29 @@ Decision criteria (apply in order):
- Prefer minimal reordering of placeholders if both are valid.
Remember:
- Never fabricate or improve content. Choose A or B, or fall back to the Source if both are unsafe.
- Never fabricate or "improve" content. Choose A or B, or fall back to the Source if both are unsafe.
- Your reply must be exactly the chosen string with no commentary.
"""
SESSION = requests.Session()
def configure_session():
if OPENAI_API_KEY:
SESSION.headers.update({
"Authorization": f"Bearer {OPENAI_API_KEY}",
"Content-Type": "application/json"
})
retry = Retry(
total=10,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=frozenset(["POST"])
)
adapter = HTTPAdapter(pool_connections=100, pool_maxsize=100, max_retries=retry)
SESSION.mount("https://", adapter)
SESSION.mount("http://", adapter)
def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]:
files = {}
@@ -111,7 +141,7 @@ def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]:
def evaluate_translation(source: str, old: str, new: str, language: str) -> str:
try:
response = requests.post(
response = SESSION.post(
"https://api.openai.com/v1/chat/completions",
json={
"model": OPENAI_MODEL,
@@ -120,13 +150,9 @@ def evaluate_translation(source: str, old: str, new: str, language: str) -> str:
{"role": "user", "content": f"Source: {source}\n\nTranslation A: {old}\n\nTranslation B: {new}"},
],
"max_completion_tokens": 2048,
"reasoning_effort": "minimal",
"reasoning_effort": "medium",
"verbosity": "low",
},
headers={
"Authorization": f"Bearer {OPENAI_API_KEY}",
"Content-Type": "application/json",
},
timeout=(10, 60)
)
@@ -148,7 +174,7 @@ def translate_phrase(text: str, language: str) -> str:
prompt = OPENAI_PROMPT.format(language=language)
try:
response = requests.post(
response = SESSION.post(
"https://api.openai.com/v1/chat/completions",
json={
"model": OPENAI_MODEL,
@@ -160,10 +186,6 @@ def translate_phrase(text: str, language: str) -> str:
"reasoning_effort": "minimal",
"verbosity": "low",
},
headers={
"Authorization": f"Bearer {OPENAI_API_KEY}",
"Content-Type": "application/json",
},
timeout=(10, 60)
)
@@ -180,7 +202,6 @@ def translate_phrase(text: str, language: str) -> str:
def translate_file(path: pathlib.Path, language: str, all_: bool, vet_translations: bool) -> None:
tree = ET.parse(path)
root = tree.getroot()
for context in root.findall("./context"):
@@ -190,6 +211,8 @@ def translate_file(path: pathlib.Path, language: str, all_: bool, vet_translatio
print(f"Context: {name.text}")
work_items = []
for message in context.findall("./message"):
source = message.find("source")
translation = message.find("translation")
@@ -210,24 +233,54 @@ def translate_file(path: pathlib.Path, language: str, all_: bool, vet_translatio
continue
text = cast(str, source.text)
numerus = (message.attrib.get("numerus") == "yes") or ("%n" in text)
old_translation = translation.text or ""
work_items.append((message, translation, text, numerus, old_translation))
if not work_items:
continue
def worker(item):
message, translation, text, numerus, old_translation = item
llm_translation = translate_phrase(text, language)
print(f"Source: {text}\n" +
f"Current translation: {translation.text}\n" +
f"LLM translation: {llm_translation}")
if vet_translations:
old_translation = translation.text or ""
print(f"Comparison:\n" +
f"Current translation: {old_translation}\n" +
f"New translation: {llm_translation}")
best = evaluate_translation(text, old_translation, llm_translation, language)
print(f"Chosen translation: {best}")
translation.text = best
return (message, translation, text, numerus, best, True)
else:
return (message, translation, text, numerus, llm_translation, False)
results = []
with ThreadPoolExecutor(max_workers=100) as executor:
future_map = {executor.submit(worker, item): item for item in work_items}
for future in as_completed(future_map):
try:
results.append(future.result())
except Exception as e:
item = future_map[future]
print(f"Task failed for '{item[2][:40]}...': {e}")
for message, translation, text, numerus, chosen_translation, was_vetted in results:
print(f"Source: {text}\nCurrent translation: {translation.text}\nLLM translation: {chosen_translation}")
if was_vetted:
print(f"Chosen translation: {chosen_translation}")
translation.text = chosen_translation
else:
translation.set("type", f"{OPENAI_MODEL}-generated")
translation.text = llm_translation
if numerus:
translations = chosen_translation or (translation.text or text)
for child in list(translation):
translation.remove(child)
translation.text = None
ET.SubElement(translation, "numerusform").text = translations
ET.SubElement(translation, "numerusform").text = translations
else:
translation.text = chosen_translation
with path.open("w", encoding="utf-8") as fp:
fp.write('<?xml version="1.0" encoding="utf-8"?>\n' +
@@ -252,6 +305,8 @@ def main():
"If you don't have one go to: https://beta.openai.com/account/api-keys.")
exit(1)
configure_session()
files = get_language_files(None if args.all_files else args.file)
if args.file:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -45,8 +45,8 @@ int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_h
}
void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line) {
for (int i = 0; i < 4; ++i) {
auto lead_data = (i == 0) ? radar_state.getLeadOne() : (i == 1) ? radar_state.getLeadTwo() : (i == 2) ? radar_state.getLeadLeft() : radar_state.getLeadRight();
for (int i = 0; i < 2; ++i) {
const auto &lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo();
if (lead_data.getStatus()) {
float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())];
calib_frame_to_full_frame(s, lead_data.getDRel(), -lead_data.getYRel(), z + 1.22, &s->scene.lead_vertices[i]);
@@ -54,6 +54,16 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con
}
}
void update_leads_frogpilot(UIState *s, FrogPilotUIState *fs, const cereal::FrogPilotRadarState::Reader &frogpilot_radar_state, const cereal::XYZTData::Reader &line) {
for (int i = 0; i < 2; ++i) {
auto lead_data = (i == 0) ? frogpilot_radar_state.getLeadLeft() : frogpilot_radar_state.getLeadRight();
if (lead_data.getStatus()) {
float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())];
calib_frame_to_full_frame(s, lead_data.getDRel(), -lead_data.getYRel(), z + 1.22, &fs->frogpilot_scene.lead_vertices[i]);
}
}
}
void update_radar_tracks(capnp::List<cereal::LiveTracks>::Reader &tracks_msg, cereal::XYZTData::Reader line, const UIState &s, const SubMaster &sm) {
FrogPilotUIState *fs = frogpilotUIState();
FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene;
+2 -1
View File
@@ -102,7 +102,7 @@ typedef struct UIScene {
QPolygonF road_edge_vertices[2];
// lead
QPointF lead_vertices[4];
QPointF lead_vertices[2];
// DMoji state
float driver_pose_vals[3];
@@ -210,4 +210,5 @@ void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line,
float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert);
// FrogPilot variables
void update_leads_frogpilot(UIState *s, FrogPilotUIState *fs, const cereal::FrogPilotRadarState::Reader &frogpilot_radar_state, const cereal::XYZTData::Reader &line);
void update_radar_tracks(capnp::List<cereal::LiveTracks>::Reader &tracks_msg, cereal::XYZTData::Reader line, const UIState &s, const SubMaster &sm);