|  |  |  | import os
 | 
					
						
							|  |  |  | import time
 | 
					
						
							|  |  |  | import numpy as np
 | 
					
						
							|  |  |  | import tomllib
 | 
					
						
							|  |  |  | from abc import abstractmethod, ABC
 | 
					
						
							|  |  |  | from enum import StrEnum
 | 
					
						
							|  |  |  | from typing import Any, Dict, Optional, Tuple, List, Callable
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from cereal import car
 | 
					
						
							|  |  |  | from openpilot.common.basedir import BASEDIR
 | 
					
						
							|  |  |  | from openpilot.common.conversions import Conversions as CV
 | 
					
						
							|  |  |  | 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.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
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ButtonType = car.CarState.ButtonEvent.Type
 | 
					
						
							|  |  |  | GearShifter = car.CarState.GearShifter
 | 
					
						
							|  |  |  | EventName = car.CarEvent.EventName
 | 
					
						
							|  |  |  | TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, bool], float]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS
 | 
					
						
							|  |  |  | ACCEL_MAX = 2.0
 | 
					
						
							|  |  |  | ACCEL_MIN = -3.5
 | 
					
						
							|  |  |  | FRICTION_THRESHOLD = 0.3
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.toml')
 | 
					
						
							|  |  |  | TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.toml')
 | 
					
						
							|  |  |  | TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.toml')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_torque_params(candidate):
 | 
					
						
							|  |  |  |   with open(TORQUE_SUBSTITUTE_PATH, 'rb') as f:
 | 
					
						
							|  |  |  |     sub = tomllib.load(f)
 | 
					
						
							|  |  |  |   if candidate in sub:
 | 
					
						
							|  |  |  |     candidate = sub[candidate]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   with open(TORQUE_PARAMS_PATH, 'rb') as f:
 | 
					
						
							|  |  |  |     params = tomllib.load(f)
 | 
					
						
							|  |  |  |   with open(TORQUE_OVERRIDE_PATH, 'rb') as f:
 | 
					
						
							|  |  |  |     override = tomllib.load(f)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Ensure no overlap
 | 
					
						
							|  |  |  |   if sum([candidate in x for x in [sub, params, override]]) > 1:
 | 
					
						
							|  |  |  |     raise RuntimeError(f'{candidate} is defined twice in torque config')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if candidate in override:
 | 
					
						
							|  |  |  |     out = override[candidate]
 | 
					
						
							|  |  |  |   elif candidate in params:
 | 
					
						
							|  |  |  |     out = params[candidate]
 | 
					
						
							|  |  |  |   else:
 | 
					
						
							|  |  |  |     raise NotImplementedError(f"Did not find torque params for {candidate}")
 | 
					
						
							|  |  |  |   return {key: out[i] for i, key in enumerate(params['legend'])}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # generic car and radar interfaces
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CarInterfaceBase(ABC):
 | 
					
						
							|  |  |  |   def __init__(self, CP, CarController, CarState):
 | 
					
						
							|  |  |  |     self.CP = CP
 | 
					
						
							|  |  |  |     self.VM = VehicleModel(CP)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.frame = 0
 | 
					
						
							|  |  |  |     self.steering_unpressed = 0
 | 
					
						
							|  |  |  |     self.low_speed_alert = False
 | 
					
						
							|  |  |  |     self.no_steer_warning = False
 | 
					
						
							|  |  |  |     self.silent_steer_warning = True
 | 
					
						
							|  |  |  |     self.v_ego_cluster_seen = False
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.CS = None
 | 
					
						
							|  |  |  |     self.can_parsers = []
 | 
					
						
							|  |  |  |     if CarState is not None:
 | 
					
						
							|  |  |  |       self.CS = CarState(CP)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       self.cp = self.CS.get_can_parser(CP)
 | 
					
						
							|  |  |  |       self.cp_cam = self.CS.get_cam_can_parser(CP)
 | 
					
						
							|  |  |  |       self.cp_adas = self.CS.get_adas_can_parser(CP)
 | 
					
						
							|  |  |  |       self.cp_body = self.CS.get_body_can_parser(CP)
 | 
					
						
							|  |  |  |       self.cp_loopback = self.CS.get_loopback_can_parser(CP)
 | 
					
						
							|  |  |  |       self.can_parsers = [self.cp, self.cp_cam, self.cp_adas, self.cp_body, self.cp_loopback]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.CC = None
 | 
					
						
							|  |  |  |     if CarController is not None:
 | 
					
						
							|  |  |  |       self.CC = CarController(self.cp.dbc_name, CP, self.VM)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def get_pid_accel_limits(CP, current_speed, cruise_speed):
 | 
					
						
							|  |  |  |     return ACCEL_MIN, ACCEL_MAX
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @classmethod
 | 
					
						
							|  |  |  |   def get_non_essential_params(cls, candidate: str):
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Parameters essential to controlling the car may be incomplete or wrong without FW versions or fingerprints.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     return cls.get_params(candidate, gen_empty_fingerprint(), list(), False, False)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @classmethod
 | 
					
						
							|  |  |  |   def get_params(cls, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool, docs: bool):
 | 
					
						
							|  |  |  |     ret = CarInterfaceBase.get_std_params(candidate)
 | 
					
						
							|  |  |  |     ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Vehicle mass is published curb weight plus assumed payload such as a human driver; notCars have no assumed payload
 | 
					
						
							|  |  |  |     if not ret.notCar:
 | 
					
						
							|  |  |  |       ret.mass = ret.mass + STD_CARGO_KG
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Set params dependent on values set by the car interface
 | 
					
						
							|  |  |  |     ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
 | 
					
						
							|  |  |  |     ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, ret.tireStiffnessFactor)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ret
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   @abstractmethod
 | 
					
						
							|  |  |  |   def _get_params(ret: car.CarParams, candidate: str, fingerprint: Dict[int, Dict[int, int]],
 | 
					
						
							|  |  |  |                   car_fw: List[car.CarParams.CarFw], experimental_long: bool, docs: bool):
 | 
					
						
							|  |  |  |     raise NotImplementedError
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def init(CP, logcan, sendcan):
 | 
					
						
							|  |  |  |     pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def get_steer_feedforward_default(desired_angle, v_ego):
 | 
					
						
							|  |  |  |     # Proportional to realigning tire momentum: lateral acceleration.
 | 
					
						
							|  |  |  |     # TODO: something with lateralPlan.curvatureRates
 | 
					
						
							|  |  |  |     return desired_angle * (v_ego**2)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def get_steer_feedforward_function(self):
 | 
					
						
							|  |  |  |     return self.get_steer_feedforward_default
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def torque_from_lateral_accel_linear(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
 | 
					
						
							|  |  |  |                                        lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> 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 (lateral_accel_value / float(torque_params.latAccelFactor)) + friction
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
 | 
					
						
							|  |  |  |     return self.torque_from_lateral_accel_linear
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # returns a set of default params to avoid repetition in car specific params
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def get_std_params(candidate):
 | 
					
						
							|  |  |  |     ret = car.CarParams.new_message()
 | 
					
						
							|  |  |  |     ret.carFingerprint = candidate
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Car docs fields
 | 
					
						
							|  |  |  |     ret.maxLateralAccel = get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED']
 | 
					
						
							|  |  |  |     ret.autoResumeSng = True  # describes whether car can resume from a stop automatically
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # standard ALC params
 | 
					
						
							|  |  |  |     ret.tireStiffnessFactor = 1.0
 | 
					
						
							|  |  |  |     ret.steerControlType = car.CarParams.SteerControlType.torque
 | 
					
						
							|  |  |  |     ret.minSteerSpeed = 0.
 | 
					
						
							|  |  |  |     ret.wheelSpeedFactor = 1.0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ret.pcmCruise = True     # openpilot's state is tied to the PCM's cruise state on most cars
 | 
					
						
							|  |  |  |     ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this
 | 
					
						
							|  |  |  |     ret.steerRatioRear = 0.  # no rear steering, at least on the listed cars aboveA
 | 
					
						
							|  |  |  |     ret.openpilotLongitudinalControl = False
 | 
					
						
							|  |  |  |     ret.stopAccel = -2.0
 | 
					
						
							|  |  |  |     ret.stoppingDecelRate = 0.8 # brake_travel/s while trying to stop
 | 
					
						
							|  |  |  |     ret.vEgoStopping = 0.5
 | 
					
						
							|  |  |  |     ret.vEgoStarting = 0.5
 | 
					
						
							|  |  |  |     ret.stoppingControl = True
 | 
					
						
							|  |  |  |     ret.longitudinalTuning.deadzoneBP = [0.]
 | 
					
						
							|  |  |  |     ret.longitudinalTuning.deadzoneV = [0.]
 | 
					
						
							|  |  |  |     ret.longitudinalTuning.kf = 1.
 | 
					
						
							|  |  |  |     ret.longitudinalTuning.kpBP = [0.]
 | 
					
						
							|  |  |  |     ret.longitudinalTuning.kpV = [1.]
 | 
					
						
							|  |  |  |     ret.longitudinalTuning.kiBP = [0.]
 | 
					
						
							|  |  |  |     ret.longitudinalTuning.kiV = [1.]
 | 
					
						
							|  |  |  |     # TODO estimate car specific lag, use .15s for now
 | 
					
						
							|  |  |  |     ret.longitudinalActuatorDelayLowerBound = 0.15
 | 
					
						
							|  |  |  |     ret.longitudinalActuatorDelayUpperBound = 0.15
 | 
					
						
							|  |  |  |     ret.steerLimitTimer = 1.0
 | 
					
						
							|  |  |  |     return ret
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0, use_steering_angle=True):
 | 
					
						
							|  |  |  |     params = get_torque_params(candidate)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     tune.init('torque')
 | 
					
						
							|  |  |  |     tune.torque.useSteeringAngle = use_steering_angle
 | 
					
						
							|  |  |  |     tune.torque.kp = 1.0
 | 
					
						
							|  |  |  |     tune.torque.kf = 1.0
 | 
					
						
							|  |  |  |     tune.torque.ki = 0.1
 | 
					
						
							|  |  |  |     tune.torque.friction = params['FRICTION']
 | 
					
						
							|  |  |  |     tune.torque.latAccelFactor = params['LAT_ACCEL_FACTOR']
 | 
					
						
							|  |  |  |     tune.torque.latAccelOffset = 0.0
 | 
					
						
							| 
									
										
											  
											
												Live torque (#25456)
* wip torqued
* add basic logic
* setup in manager
* check sanity and publish msg
* add first order filter to outputs
* wire up controlsd, and update gains
* rename intercept to offset
* add cloudlog, live values are not updated
* fix bugs, do not reset points for now
* fix crashes
* rename to main
* fix bugs, works offline
* fix float in cereal bug
* add latacc filter
* randomly choose points, approx for iid
* add variable decay
* local param to capnp instead of dict
* verify works in replay
* use torqued output in controlsd
* use in controlsd; use points from past routes
* controlsd bugfix
* filter before updating gains, needs to be replaced
* save all points to ensure smooth transition across routes, revert friction factor to 1.5
* add filters to prevent noisy low-speed data points; improve fit sanity
* add engaged buffer
* revert lat_acc thresh
* use paramsd realtime process config
* make latacc-to-torque generic, and overrideable
* move freq to 4Hz, avoid storing in np.array, don't publish points in the message
* float instead of np
* remove constant while storing pts
* rename slope, offset to lat_accet_factor, offset
* resolve issues
* use camelcase in all capnp params
* use camelcase everywhere
* reduce latacc threshold or sanity, add car_sane todo, save points properly
* add and check tag
* write param to disk at end of route
* remove args
* rebase op, cereal
* save on exit
* restore default handler
* cpu usage check
* add to process replay
* handle reset better, reduce unnecessary computation
* always publish raw values - useful for debug
* regen routes
* update refs
* checks on cache restore
* check tuning vals too
* clean that up
* reduce cpu usage
* reduce cpu usage by 75%
* cleanup
* optimize further
* handle reset condition better, don't put points in init, use only in corolla
* bump cereal after rebasing
* update refs
* Update common/params.cc
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
* remove unnecessary checks
* Update RELEASES.md
Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
old-commit-hash: 4fa62f146426f76c9c1c2867d9729b33ec612b59
											
										 
											3 years ago
										 |  |  |     tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @abstractmethod
 | 
					
						
							|  |  |  |   def _update(self, c: car.CarControl) -> car.CarState:
 | 
					
						
							|  |  |  |     pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def update(self, c: car.CarControl, can_strings: List[bytes]) -> car.CarState:
 | 
					
						
							|  |  |  |     # parse can
 | 
					
						
							|  |  |  |     for cp in self.can_parsers:
 | 
					
						
							|  |  |  |       if cp is not None:
 | 
					
						
							|  |  |  |         cp.update_strings(can_strings)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # get CarState
 | 
					
						
							|  |  |  |     ret = self._update(c)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ret.canValid = all(cp.can_valid for cp in self.can_parsers if cp is not None)
 | 
					
						
							|  |  |  |     ret.canTimeout = any(cp.bus_timeout for cp in self.can_parsers if cp is not None)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ret.vEgoCluster == 0.0 and not self.v_ego_cluster_seen:
 | 
					
						
							|  |  |  |       ret.vEgoCluster = ret.vEgo
 | 
					
						
							|  |  |  |     else:
 | 
					
						
							|  |  |  |       self.v_ego_cluster_seen = True
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Many cars apply hysteresis to the ego dash speed
 | 
					
						
							|  |  |  |     if self.CS is not None:
 | 
					
						
							|  |  |  |       ret.vEgoCluster = apply_hysteresis(ret.vEgoCluster, self.CS.out.vEgoCluster, self.CS.cluster_speed_hyst_gap)
 | 
					
						
							|  |  |  |       if abs(ret.vEgo) < self.CS.cluster_min_speed:
 | 
					
						
							|  |  |  |         ret.vEgoCluster = 0.0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ret.cruiseState.speedCluster == 0:
 | 
					
						
							|  |  |  |       ret.cruiseState.speedCluster = ret.cruiseState.speed
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # copy back for next iteration
 | 
					
						
							|  |  |  |     reader = ret.as_reader()
 | 
					
						
							|  |  |  |     if self.CS is not None:
 | 
					
						
							|  |  |  |       self.CS.out = reader
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return reader
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @abstractmethod
 | 
					
						
							|  |  |  |   def apply(self, c: car.CarControl, now_nanos: int) -> Tuple[car.CarControl.Actuators, List[bytes]]:
 | 
					
						
							|  |  |  |     pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True,
 | 
					
						
							|  |  |  |                            enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)):
 | 
					
						
							|  |  |  |     events = Events()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if cs_out.doorOpen:
 | 
					
						
							|  |  |  |       events.add(EventName.doorOpen)
 | 
					
						
							|  |  |  |     if cs_out.seatbeltUnlatched:
 | 
					
						
							|  |  |  |       events.add(EventName.seatbeltNotLatched)
 | 
					
						
							|  |  |  |     if cs_out.gearShifter != GearShifter.drive and (extra_gears is None or
 | 
					
						
							|  |  |  |        cs_out.gearShifter not in extra_gears):
 | 
					
						
							|  |  |  |       events.add(EventName.wrongGear)
 | 
					
						
							|  |  |  |     if cs_out.gearShifter == GearShifter.reverse:
 | 
					
						
							|  |  |  |       events.add(EventName.reverseGear)
 | 
					
						
							|  |  |  |     if not cs_out.cruiseState.available:
 | 
					
						
							|  |  |  |       events.add(EventName.wrongCarMode)
 | 
					
						
							|  |  |  |     if cs_out.espDisabled:
 | 
					
						
							|  |  |  |       events.add(EventName.espDisabled)
 | 
					
						
							|  |  |  |     if cs_out.stockFcw:
 | 
					
						
							|  |  |  |       events.add(EventName.stockFcw)
 | 
					
						
							|  |  |  |     if cs_out.stockAeb:
 | 
					
						
							|  |  |  |       events.add(EventName.stockAeb)
 | 
					
						
							|  |  |  |     if cs_out.vEgo > MAX_CTRL_SPEED:
 | 
					
						
							|  |  |  |       events.add(EventName.speedTooHigh)
 | 
					
						
							|  |  |  |     if cs_out.cruiseState.nonAdaptive:
 | 
					
						
							|  |  |  |       events.add(EventName.wrongCruiseMode)
 | 
					
						
							|  |  |  |     if cs_out.brakeHoldActive and self.CP.openpilotLongitudinalControl:
 | 
					
						
							|  |  |  |       events.add(EventName.brakeHold)
 | 
					
						
							|  |  |  |     if cs_out.parkingBrake:
 | 
					
						
							|  |  |  |       events.add(EventName.parkBrake)
 | 
					
						
							|  |  |  |     if cs_out.accFaulted:
 | 
					
						
							|  |  |  |       events.add(EventName.accFaulted)
 | 
					
						
							|  |  |  |     if cs_out.steeringPressed:
 | 
					
						
							|  |  |  |       events.add(EventName.steerOverride)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Handle button presses
 | 
					
						
							|  |  |  |     for b in cs_out.buttonEvents:
 | 
					
						
							|  |  |  |       # Enable OP long on falling edge of enable buttons (defaults to accelCruise and decelCruise, overridable per-port)
 | 
					
						
							|  |  |  |       if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed):
 | 
					
						
							|  |  |  |         events.add(EventName.buttonEnable)
 | 
					
						
							|  |  |  |       # Disable on rising and falling edge of cancel for both stock and OP long
 | 
					
						
							|  |  |  |       if b.type == ButtonType.cancel:
 | 
					
						
							|  |  |  |         events.add(EventName.buttonCancel)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Handle permanent and temporary steering faults
 | 
					
						
							|  |  |  |     self.steering_unpressed = 0 if cs_out.steeringPressed else self.steering_unpressed + 1
 | 
					
						
							|  |  |  |     if cs_out.steerFaultTemporary:
 | 
					
						
							|  |  |  |       if cs_out.steeringPressed and (not self.CS.out.steerFaultTemporary or self.no_steer_warning):
 | 
					
						
							|  |  |  |         self.no_steer_warning = True
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         self.no_steer_warning = False
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # if the user overrode recently, show a less harsh alert
 | 
					
						
							|  |  |  |         if self.silent_steer_warning or cs_out.standstill or self.steering_unpressed < int(1.5 / DT_CTRL):
 | 
					
						
							|  |  |  |           self.silent_steer_warning = True
 | 
					
						
							|  |  |  |           events.add(EventName.steerTempUnavailableSilent)
 | 
					
						
							|  |  |  |         else:
 | 
					
						
							|  |  |  |           events.add(EventName.steerTempUnavailable)
 | 
					
						
							|  |  |  |     else:
 | 
					
						
							|  |  |  |       self.no_steer_warning = False
 | 
					
						
							|  |  |  |       self.silent_steer_warning = False
 | 
					
						
							|  |  |  |     if cs_out.steerFaultPermanent:
 | 
					
						
							|  |  |  |       events.add(EventName.steerUnavailable)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # we engage when pcm is active (rising edge)
 | 
					
						
							|  |  |  |     # enabling can optionally be blocked by the car interface
 | 
					
						
							|  |  |  |     if pcm_enable:
 | 
					
						
							|  |  |  |       if cs_out.cruiseState.enabled and not self.CS.out.cruiseState.enabled and allow_enable:
 | 
					
						
							|  |  |  |         events.add(EventName.pcmEnable)
 | 
					
						
							|  |  |  |       elif not cs_out.cruiseState.enabled:
 | 
					
						
							|  |  |  |         events.add(EventName.pcmDisable)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return events
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RadarInterfaceBase(ABC):
 | 
					
						
							|  |  |  |   def __init__(self, CP):
 | 
					
						
							|  |  |  |     self.rcp = None
 | 
					
						
							|  |  |  |     self.pts = {}
 | 
					
						
							|  |  |  |     self.delay = 0
 | 
					
						
							|  |  |  |     self.radar_ts = CP.radarTimeStep
 | 
					
						
							|  |  |  |     self.no_radar_sleep = 'NO_RADAR_SLEEP' in os.environ
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def update(self, can_strings):
 | 
					
						
							|  |  |  |     ret = car.RadarData.new_message()
 | 
					
						
							|  |  |  |     if not self.no_radar_sleep:
 | 
					
						
							|  |  |  |       time.sleep(self.radar_ts)  # radard runs on RI updates
 | 
					
						
							|  |  |  |     return ret
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CarStateBase(ABC):
 | 
					
						
							|  |  |  |   def __init__(self, CP):
 | 
					
						
							|  |  |  |     self.CP = CP
 | 
					
						
							|  |  |  |     self.car_fingerprint = CP.carFingerprint
 | 
					
						
							|  |  |  |     self.out = car.CarState.new_message()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.cruise_buttons = 0
 | 
					
						
							|  |  |  |     self.left_blinker_cnt = 0
 | 
					
						
							|  |  |  |     self.right_blinker_cnt = 0
 | 
					
						
							|  |  |  |     self.steering_pressed_cnt = 0
 | 
					
						
							|  |  |  |     self.left_blinker_prev = False
 | 
					
						
							|  |  |  |     self.right_blinker_prev = False
 | 
					
						
							|  |  |  |     self.cluster_speed_hyst_gap = 0.0
 | 
					
						
							|  |  |  |     self.cluster_min_speed = 0.0  # min speed before dropping to 0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Q = [[0.0, 0.0], [0.0, 100.0]]
 | 
					
						
							|  |  |  |     R = 0.3
 | 
					
						
							|  |  |  |     A = [[1.0, DT_CTRL], [0.0, 1.0]]
 | 
					
						
							|  |  |  |     C = [[1.0, 0.0]]
 | 
					
						
							|  |  |  |     x0=[[0.0], [0.0]]
 | 
					
						
							|  |  |  |     K = get_kalman_gain(DT_CTRL, np.array(A), np.array(C), np.array(Q), R)
 | 
					
						
							|  |  |  |     self.v_ego_kf = KF1D(x0=x0, A=A, C=C[0], K=K)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def update_speed_kf(self, v_ego_raw):
 | 
					
						
							|  |  |  |     if abs(v_ego_raw - self.v_ego_kf.x[0][0]) > 2.0:  # Prevent large accelerations when car starts at non zero speed
 | 
					
						
							|  |  |  |       self.v_ego_kf.set_x([[v_ego_raw], [0.0]])
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     v_ego_x = self.v_ego_kf.update(v_ego_raw)
 | 
					
						
							|  |  |  |     return float(v_ego_x[0]), float(v_ego_x[1])
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def get_wheel_speeds(self, fl, fr, rl, rr, unit=CV.KPH_TO_MS):
 | 
					
						
							|  |  |  |     factor = unit * self.CP.wheelSpeedFactor
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     wheelSpeeds = car.CarState.WheelSpeeds.new_message()
 | 
					
						
							|  |  |  |     wheelSpeeds.fl = fl * factor
 | 
					
						
							|  |  |  |     wheelSpeeds.fr = fr * factor
 | 
					
						
							|  |  |  |     wheelSpeeds.rl = rl * factor
 | 
					
						
							|  |  |  |     wheelSpeeds.rr = rr * factor
 | 
					
						
							|  |  |  |     return wheelSpeeds
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def update_blinker_from_lamp(self, blinker_time: int, left_blinker_lamp: bool, right_blinker_lamp: bool):
 | 
					
						
							|  |  |  |     """Update blinkers from lights. Enable output when light was seen within the last `blinker_time`
 | 
					
						
							|  |  |  |     iterations"""
 | 
					
						
							|  |  |  |     # TODO: Handle case when switching direction. Now both blinkers can be on at the same time
 | 
					
						
							|  |  |  |     self.left_blinker_cnt = blinker_time if left_blinker_lamp else max(self.left_blinker_cnt - 1, 0)
 | 
					
						
							|  |  |  |     self.right_blinker_cnt = blinker_time if right_blinker_lamp else max(self.right_blinker_cnt - 1, 0)
 | 
					
						
							|  |  |  |     return self.left_blinker_cnt > 0, self.right_blinker_cnt > 0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def update_steering_pressed(self, steering_pressed, steering_pressed_min_count):
 | 
					
						
							|  |  |  |     """Applies filtering on steering pressed for noisy driver torque signals."""
 | 
					
						
							|  |  |  |     self.steering_pressed_cnt += 1 if steering_pressed else -1
 | 
					
						
							|  |  |  |     self.steering_pressed_cnt = clip(self.steering_pressed_cnt, 0, steering_pressed_min_count * 2)
 | 
					
						
							|  |  |  |     return self.steering_pressed_cnt > steering_pressed_min_count
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def update_blinker_from_stalk(self, blinker_time: int, left_blinker_stalk: bool, right_blinker_stalk: bool):
 | 
					
						
							|  |  |  |     """Update blinkers from stalk position. When stalk is seen the blinker will be on for at least blinker_time,
 | 
					
						
							|  |  |  |     or until the stalk is turned off, whichever is longer. If the opposite stalk direction is seen the blinker
 | 
					
						
							|  |  |  |     is forced to the other side. On a rising edge of the stalk the timeout is reset."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if left_blinker_stalk:
 | 
					
						
							|  |  |  |       self.right_blinker_cnt = 0
 | 
					
						
							|  |  |  |       if not self.left_blinker_prev:
 | 
					
						
							|  |  |  |         self.left_blinker_cnt = blinker_time
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if right_blinker_stalk:
 | 
					
						
							|  |  |  |       self.left_blinker_cnt = 0
 | 
					
						
							|  |  |  |       if not self.right_blinker_prev:
 | 
					
						
							|  |  |  |         self.right_blinker_cnt = blinker_time
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.left_blinker_cnt = max(self.left_blinker_cnt - 1, 0)
 | 
					
						
							|  |  |  |     self.right_blinker_cnt = max(self.right_blinker_cnt - 1, 0)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.left_blinker_prev = left_blinker_stalk
 | 
					
						
							|  |  |  |     self.right_blinker_prev = right_blinker_stalk
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return bool(left_blinker_stalk or self.left_blinker_cnt > 0), bool(right_blinker_stalk or self.right_blinker_cnt > 0)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def parse_gear_shifter(gear: Optional[str]) -> car.CarState.GearShifter:
 | 
					
						
							|  |  |  |     if gear is None:
 | 
					
						
							|  |  |  |       return GearShifter.unknown
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     d: Dict[str, car.CarState.GearShifter] = {
 | 
					
						
							|  |  |  |       'P': GearShifter.park, 'PARK': GearShifter.park,
 | 
					
						
							|  |  |  |       'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse,
 | 
					
						
							|  |  |  |       'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral,
 | 
					
						
							|  |  |  |       'E': GearShifter.eco, 'ECO': GearShifter.eco,
 | 
					
						
							|  |  |  |       'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic,
 | 
					
						
							|  |  |  |       'D': GearShifter.drive, 'DRIVE': GearShifter.drive,
 | 
					
						
							|  |  |  |       'S': GearShifter.sport, 'SPORT': GearShifter.sport,
 | 
					
						
							|  |  |  |       'L': GearShifter.low, 'LOW': GearShifter.low,
 | 
					
						
							|  |  |  |       'B': GearShifter.brake, 'BRAKE': GearShifter.brake,
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     return d.get(gear.upper(), GearShifter.unknown)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def get_cam_can_parser(CP):
 | 
					
						
							|  |  |  |     return None
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def get_adas_can_parser(CP):
 | 
					
						
							|  |  |  |     return None
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def get_body_can_parser(CP):
 | 
					
						
							|  |  |  |     return None
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def get_loopback_can_parser(CP):
 | 
					
						
							|  |  |  |     return None
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | INTERFACE_ATTR_FILE = {
 | 
					
						
							|  |  |  |   "FINGERPRINTS": "fingerprints",
 | 
					
						
							|  |  |  |   "FW_VERSIONS": "fingerprints",
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # interface-specific helpers
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str | StrEnum, Any]:
 | 
					
						
							|  |  |  |   # read all the folders in selfdrive/car and return a dict where:
 | 
					
						
							|  |  |  |   # - keys are all the car models or brand names
 | 
					
						
							|  |  |  |   # - values are attr values from all car folders
 | 
					
						
							|  |  |  |   result = {}
 | 
					
						
							|  |  |  |   for car_folder in sorted([x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]):
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       brand_name = car_folder.split('/')[-1]
 | 
					
						
							|  |  |  |       brand_values = __import__(f'openpilot.selfdrive.car.{brand_name}.{INTERFACE_ATTR_FILE.get(attr, "values")}', fromlist=[attr])
 | 
					
						
							|  |  |  |       if hasattr(brand_values, attr) or not ignore_none:
 | 
					
						
							|  |  |  |         attr_data = getattr(brand_values, attr, None)
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if combine_brands:
 | 
					
						
							|  |  |  |         if isinstance(attr_data, dict):
 | 
					
						
							|  |  |  |           for f, v in attr_data.items():
 | 
					
						
							|  |  |  |             result[f] = v
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         result[brand_name] = attr_data
 | 
					
						
							|  |  |  |     except (ImportError, OSError):
 | 
					
						
							|  |  |  |       pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return result
 |