mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-22 00:33:44 +08:00
September 27th, 2025 Update
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,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(), []
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, [=]() {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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});
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ public slots:
|
||||
}
|
||||
|
||||
signals:
|
||||
void hideDescriptionEvent();
|
||||
void showDescriptionEvent();
|
||||
|
||||
protected:
|
||||
|
||||
+30
-25
@@ -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:
|
||||
|
||||
@@ -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., mph↔km/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., mph↔km/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:
|
||||
|
||||
+2791
-2731
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2807
-2743
File diff suppressed because it is too large
Load Diff
+2512
-2450
File diff suppressed because it is too large
Load Diff
+2813
-2754
File diff suppressed because it is too large
Load Diff
+2804
-2743
File diff suppressed because it is too large
Load Diff
+2510
-2450
File diff suppressed because it is too large
Load Diff
+2857
-2799
File diff suppressed because it is too large
Load Diff
+2792
-2726
File diff suppressed because it is too large
Load Diff
+2510
-2450
File diff suppressed because it is too large
Load Diff
+2790
-2728
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2790
-2726
File diff suppressed because it is too large
Load Diff
+2886
-2825
File diff suppressed because it is too large
Load Diff
+2791
-2731
File diff suppressed because it is too large
Load Diff
+2791
-2729
File diff suppressed because it is too large
Load Diff
+12
-2
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user