Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions[bot] 85b17a8252 sunnypilot v2026.09.04-4849
version: sunnypilot v2026.003.000 (feature-branch)
date: 2026-09-04T22:56:57
master commit: b70920a8c0
2026-09-04 22:56:57 +00:00
github-actions[bot] 833adb27e2 sunnypilot v2026.09.04-4849 release 2026-09-04 22:56:47 +00:00
204 changed files with 5286 additions and 11384 deletions
+11
View File
@@ -0,0 +1,11 @@
* @sunnypilot/dev-internal
/.github/ @devtekve @sunnyhaibin
/release/ci/ @devtekve @sunnyhaibin
/tinygrad_repo @devtekve @Discountchubbs
/tinygrad/ @devtekve @Discountchubbs
/selfdrive/controls/lib/longitudinal_planner.py @devtekve @Discountchubbs
/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @devtekve @Discountchubbs
/selfdrive/modeld/ @devtekve @Discountchubbs
/sunnypilot/model* @devtekve @Discountchubbs
/sunnypilot/sunnylink/ @devtekve
/system/athena/ @devtekve
+43
View File
@@ -0,0 +1,43 @@
exclude-labels:
- 'no-changelog'
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
collapse-after: 5
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
collapse-after: 5
label: 'chore'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&'
replacers:
- search: '/[Ss][Uu][Nn][Nn][Yy][Pp][Ii][Ll][Oo][Tt]/g'
replace: 'sunnypilot'
- search: '/\b[Ss][Pp]\b/g'
replace: 'SP'
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
name-template: 'v$RESOLVED_VERSION 🚀'
tag-template: 'v$RESOLVED_VERSION'
version-template: "0.$MAJOR.$MINOR.$PATCH" # The day OP becomes v1, we need to bump this
tag-prefix: "v0." # The day OP becomes v1, we need to bump this
prerelease-identifier: "staging"
template: |
## Changes
$CHANGES
-2
View File
@@ -166,5 +166,3 @@ class CarControlSP:
@auto_dataclass
class CarStateSP:
speedLimit: float = auto_field()
engineOff: bool = auto_field()
engineRpm: float = auto_field()
@@ -11,17 +11,14 @@ from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.values import CAR, NO_STOP_TIMER_CAR, TSS2_CAR, \
CarControllerParams, ToyotaFlags
from opendbc.can import CANPacker
from opendbc.sunnypilot.car.toyota.auto_brake_hold import AutoBrakeHoldCarController
from opendbc.sunnypilot.car.toyota.enhanced_bsm import EnhancedBsmCarController
from opendbc.sunnypilot.car.toyota.gas_interceptor import GasInterceptorCarController
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
from opendbc.sunnypilot.car.toyota.brake_onset import BrakeOnsetShaper, BrakeCommandCorrections, EngageOnsetShaper
Ecu = structs.CarParams.Ecu
LongCtrlState = structs.CarControl.Actuators.LongControlState
SteerControlType = structs.CarParams.SteerControlType
VisualAlert = structs.CarControl.HUDControl.VisualAlert
GearShifter = structs.CarState.GearShifter
# The up limit allows the brakes/gas to unwind quickly leaving a stop,
# the down limit roughly matches the rate of ACCEL_NET, reducing PCM compensation windup
@@ -39,20 +36,11 @@ MAX_STEER_RATE_FRAMES = 17 # tx control frames needed before torque can be cut
# EPS allows user torque above threshold for 50 frames before permanently faulting
MAX_USER_TORQUE = 500
CRUISE_CANCEL_DELAY_FRAMES = 10
def get_long_tune(CP, CP_SP, params):
def get_long_tune(CP, params):
if CP.flags & ToyotaFlags.TSS2:
if CP_SP.flags & ToyotaFlagsSP.TSS2_LONG_TUNING:
#kiBP = [0., 2.0, 9.0, 14., 20., 27.]
#kiV = [0.25, 0.25, 0.15, 0.12, 0.12, 0.12]
#kiBP= [0., 1.0, 2.0, 3.0, 4.0, 5.0, 7., 20., 27., 36.]
#kiV = [0.31, 0.32, 0.301, 0.280, 0.259, 0.226, 0.15, 0.15, 0.101, 0.10]
kiBP= [0., 0.3, 5.0, 12., 27., 36.]
kiV = [0.50, 0.52, 0.25, 0.23, 0.10, 0.09]
else:
kiBP = [2., 5.]
kiV = [0.5, 0.25]
kiBP = [2., 5.]
kiV = [0.5, 0.25]
else:
kiBP = [0., 5., 35.]
kiV = [3.6, 2.4, 1.5]
@@ -75,21 +63,15 @@ class CarController(CarControllerBase, GasInterceptorCarController):
self.permit_braking = True
self.steer_rate_counter = 0
self.distance_button = 0
self.cancel_counter = 0
# *** start long control state ***
self.long_pid = get_long_tune(self.CP, self.CP_SP, self.params)
self.aego = FirstOrderFilter(0.0, 0.45, DT_CTRL * 3)
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_hp = HighPassFilter(0.0, 0.25, 1.5, DT_CTRL)
self.accel = 0
self.prev_accel = 0
# sunnypilot: ToyotaBrakeOnset gates the whole brake_onset module (onset + engage onset + command corrections)
self.brake_onset_enabled = bool(CP_SP.flags & ToyotaFlagsSP.SP_BRAKE_ONSET.value)
self.brake_onset = BrakeOnsetShaper(DT_CTRL * 3, -ACCEL_WINDDOWN_LIMIT / (DT_CTRL * 3))
self.engage_onset = EngageOnsetShaper(DT_CTRL * 3, ACCEL_WINDUP_LIMIT / (DT_CTRL * 3))
self.brake_corrections = BrakeCommandCorrections(DT_CTRL * 3) # sunnypilot: see brake_onset.py
# *** end long control state ***
self.packer = CANPacker(dbc_names[Bus.pt])
@@ -99,20 +81,11 @@ class CarController(CarControllerBase, GasInterceptorCarController):
self.secoc_acc_message_counter = 0
self.secoc_prev_reset_counter = 0
self.enhanced_bsm = EnhancedBsmCarController(CP, CP_SP)
self.auto_brake_hold = AutoBrakeHoldCarController(CP, CP_SP)
self._auto_lock_speed = 0.0
self._auto_lock_once = False
self._gear_prev = GearShifter.park
def update(self, CC, CC_SP, CS, now_nanos):
actuators = CC.actuators
stopping = actuators.longControlState == LongCtrlState.stopping
hud_control = CC.hudControl
self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0
pcm_cancel_cmd = self.cancel_counter > CRUISE_CANCEL_DELAY_FRAMES
pcm_cancel_cmd = CC.cruiseControl.cancel
lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE
if len(CC.orientationNED) == 3:
@@ -223,9 +196,6 @@ class CarController(CarControllerBase, GasInterceptorCarController):
self.last_standstill = CS.out.standstill
if self.auto_brake_hold.enabled:
can_sends.extend(self.auto_brake_hold.update(CS, self.frame, self.packer))
# handle UI messages
fcw_alert = hud_control.visualAlert == VisualAlert.fcw
steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)
@@ -243,22 +213,8 @@ class CarController(CarControllerBase, GasInterceptorCarController):
# internal PCM gas command can get stuck unwinding from negative accel so we apply a generous rate limit
pcm_accel_cmd = actuators.accel
if self.brake_onset_enabled:
self.engage_onset.cruise_state(CS.out.cruiseState.enabled) # sunnypilot hook
if CC.longActive and self.brake_onset_enabled:
self.prev_accel = self.engage_onset.start_accel(self.prev_accel, CS.out.aEgo, CS.out.vEgo) # sunnypilot hook
# a hard request bypasses the soft brake onset, except in the first 0.6 s after engaging (FCW always does)
urgent = self.brake_onset.is_urgent(pcm_accel_cmd, False) and not self.engage_onset.in_engage_window
winddown_step = self.brake_onset.down_step(pcm_accel_cmd, self.prev_accel, bypass=fcw_alert, v_ego=CS.out.vEgo,
urgent=urgent, t_engaged=self.engage_onset.t_since_engage,
a_ego=CS.out.aEgo, brake_force=CS.brake_force)
windup_step = self.engage_onset.up_step(True)
pcm_accel_cmd = rate_limit(pcm_accel_cmd, self.prev_accel, winddown_step, windup_step)
elif CC.longActive:
pcm_accel_cmd = rate_limit(pcm_accel_cmd, self.prev_accel, ACCEL_WINDDOWN_LIMIT, ACCEL_WINDUP_LIMIT) # sunnypilot: stock path
else:
self.brake_onset.reset()
self.engage_onset.reset()
if CC.longActive:
pcm_accel_cmd = rate_limit(pcm_accel_cmd, self.prev_accel, ACCEL_WINDDOWN_LIMIT, ACCEL_WINDUP_LIMIT)
self.prev_accel = pcm_accel_cmd
# calculate amount of acceleration PCM should apply to reach target, given pitch.
@@ -294,19 +250,12 @@ class CarController(CarControllerBase, GasInterceptorCarController):
-MAX_PITCH_COMPENSATION, MAX_PITCH_COMPENSATION))
pcm_accel_cmd += pitch_compensation
# sunnypilot: bleed and freeze the PID integrator at the end of the stop
freeze_i = self.brake_onset_enabled and self.brake_corrections.condition_pid(self.long_pid, self.prev_accel, CS.out.vEgo)
pcm_accel_cmd = self.long_pid.update(error_future,
speed=CS.out.vEgo,
feedforward=pcm_accel_cmd,
freeze_integrator=actuators.longControlState != LongCtrlState.pid or freeze_i)
if self.brake_onset_enabled:
pcm_accel_cmd = self.brake_corrections.apply(pcm_accel_cmd, self.prev_accel, a_ego_future, CS.out.vEgo, stopping,
fcw_alert, a_ego=a_ego_blended,
relaxed=hud_control.leadDistanceBars == 3) # sunnypilot hook
freeze_integrator=actuators.longControlState != LongCtrlState.pid)
else:
self.long_pid.reset()
self.brake_corrections.reset() # sunnypilot hook
# Along with rate limiting positive jerk above, this greatly improves gas response time
# Consider the net acceleration request that the PCM should be applying (pitch included)
@@ -370,9 +319,6 @@ class CarController(CarControllerBase, GasInterceptorCarController):
if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value:
can_sends.append(make_tester_present_msg(0x750, 0, 0xF))
if self.enhanced_bsm.enabled:
can_sends.extend(self.enhanced_bsm.update(CS, self.frame))
new_actuators = actuators.as_builder()
new_actuators.torque = apply_torque / self.params.STEER_MAX
new_actuators.torqueOutputCan = apply_torque
+2 -87
View File
@@ -1,9 +1,6 @@
import copy
from enum import IntEnum
import importlib
from opendbc.can import CANDefine, CANParser
from opendbc.can.dbc import DBC as DBCParser
from opendbc.car import Bus, DT_CTRL, create_button_events, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.common.filter_simple import FirstOrderFilter
@@ -11,34 +8,11 @@ from opendbc.car.interfaces import CarStateBase
from opendbc.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, \
TSS2_CAR, EPS_SCALE
from opendbc.sunnypilot.car.toyota.carstate_ext import CarStateExt
from opendbc.sunnypilot.car.toyota.enhanced_bsm import EnhancedBsmCarState
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
ButtonType = structs.CarState.ButtonEvent.Type
SteerControlType = structs.CarParams.SteerControlType
class AccelPersonality(IntEnum):
eco = 0
normal = 1
sport = 2
def get_accel_personality(sport_mode: int, eco_mode: int) -> AccelPersonality:
if sport_mode == 1:
return AccelPersonality.sport
if eco_mode == 1:
return AccelPersonality.eco
return AccelPersonality.normal
def get_host_params():
"""Return sunnypilot's Params store when opendbc is embedded in openpilot."""
try:
return importlib.import_module("openpilot.common.params").Params()
except ModuleNotFoundError:
return None
# These steering fault definitions seem to be common across LKA (torque) and LTA (angle):
# - high steer rate fault: goes to 21 or 25 for 1 frame, then 9 for 2 seconds
# - lka/lta msg drop out: goes to 9 then 11 for a combined total of 2 seconds, then 3.
@@ -81,21 +55,6 @@ class CarState(CarStateBase, CarStateExt):
self.gvc = 0.0
self.secoc_synchronization = None
self.enhanced_bsm = EnhancedBsmCarState(CP, CP_SP)
if CP_SP.flags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
self.pre_collision_2 = {}
self.brake_force = float('nan')
self._host_params = get_host_params()
self.toyota_drive_mode = self._host_params is not None and self._host_params.get_bool('ToyotaDriveMode')
self._drive_mode_signals_checked = False
self._sport_signal_available = False
self._eco_signal_available = False
self._prev_accel_profile = None
self._accel_profile_init = False
self.frame = 0
def update(self, can_parsers) -> tuple[structs.CarState, structs.CarStateSP]:
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
@@ -113,47 +72,18 @@ class CarState(CarStateBase, CarStateExt):
ret.parkingBrake = cp.vl["BODY_CONTROL_STATE"]["PARKING_BRAKE"] == 1
ret.brakePressed = cp.vl["BRAKE_MODULE"]["BRAKE_PRESSED"] != 0
#ret.brakeHoldActive = cp.vl["ESP_CONTROL"]["BRAKE_HOLD_ACTIVE"] == 1
ret.brakeHoldActive = cp.vl["ESP_CONTROL"]["BRAKE_HOLD_ACTIVE"] == 1
if self.CP.flags & ToyotaFlags.SECOC.value:
self.secoc_synchronization = copy.copy(cp.vl["SECOC_SYNCHRONIZATION"])
ret.gasPressed = cp.vl["GAS_PEDAL"]["GAS_PEDAL_USER"] > 0
can_gear = int(cp.vl["GEAR_PACKET_HYBRID"]["GEAR"])
else:
ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0 # TODO: these also have GAS_PEDAL, come back and unify
ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0
can_gear = int(cp.vl["GEAR_PACKET"]["GEAR"])
if not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value:
ret.stockAeb = bool(cp_acc.vl["PRE_COLLISION"]["PRECOLLISION_ACTIVE"] and cp_acc.vl["PRE_COLLISION"]["FORCE"] < -1e-5)
if self.toyota_drive_mode: # and not self.CP.flags & ToyotaFlags.SECOC.value:
sport_signal = 'SPORT_ON_2' if self.CP.carFingerprint in (CAR.TOYOTA_RAV4_TSS2, CAR.LEXUS_ES_TSS2,
CAR.TOYOTA_HIGHLANDER_TSS2) else 'SPORT_ON'
if not self._drive_mode_signals_checked:
self._drive_mode_signals_checked = True
try:
sport_mode = cp.vl["GEAR_PACKET"][sport_signal]
self._sport_signal_available = True
except KeyError:
sport_mode = 0
self._sport_signal_available = False
try:
eco_mode = cp.vl["GEAR_PACKET"]['ECON_ON']
self._eco_signal_available = True
except KeyError:
eco_mode = 0
self._eco_signal_available = False
else:
sport_mode = cp.vl["GEAR_PACKET"][sport_signal] if self._sport_signal_available else 0
eco_mode = cp.vl["GEAR_PACKET"]['ECON_ON'] if self._eco_signal_available else 0
accel_profile = get_accel_personality(sport_mode, eco_mode)
if not self._accel_profile_init or accel_profile != self._prev_accel_profile:
self._host_params.put('AccelPersonality', int(accel_profile))
self._accel_profile_init = True
self._prev_accel_profile = accel_profile
self.parse_wheel_speeds(ret,
cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"],
cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"],
@@ -282,16 +212,6 @@ class CarState(CarStateBase, CarStateExt):
ret.buttonEvents = buttonEvents
if self.enhanced_bsm.enabled and self.frame > 199:
ret.leftBlindspot, ret.rightBlindspot = self.enhanced_bsm.update(cp, self.frame)
if self.CP_SP.flags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD:
self.pre_collision_2 = copy.copy(cp_cam.vl["PRE_COLLISION_2"])
if "BRAKE" in cp.vl:
self.brake_force = float(cp.vl["BRAKE"]["BRAKE_FORCE"])
self.frame += 1
CarStateExt.update(self, ret, ret_sp, can_parsers)
return ret, ret_sp
@@ -301,11 +221,6 @@ class CarState(CarStateBase, CarStateExt):
pt_messages = [
("BLINKERS_STATE", float('nan')),
]
if CP.flags & ToyotaFlags.HYBRID:
pt_messages.append(("ENGINE_RPM", float('nan')))
if CP_SP.flags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD and "BRAKE" in DBCParser(DBC[CP.carFingerprint][Bus.pt]).name_to_msg:
pt_messages.append(("BRAKE", float('nan')))
cam_messages = [
("RSA1", 0),
+1 -5
View File
@@ -3,7 +3,7 @@ from opendbc.car.toyota.carstate import CarState
from opendbc.car.toyota.carcontroller import CarController
from opendbc.car.toyota.radar_interface import RadarInterface
from opendbc.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \
MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ToyotaSafetyFlags, UNSUPPORTED_DSU_CAR, SECOC_CAR
MIN_ACC_SPEED, EPS_SCALE, NO_STOP_TIMER_CAR, ToyotaSafetyFlags, UNSUPPORTED_DSU_CAR
from opendbc.car.disable_ecu import disable_ecu
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP, ToyotaSafetyFlagsSP
@@ -117,7 +117,6 @@ class CarInterface(CarInterfaceBase):
if ret.flags & ToyotaFlags.TSS2:
ret.flags |= ToyotaFlags.RAISED_ACCEL_LIMIT.value
#ret.stopAccel = -0.02
# Hybrids have much quicker longitudinal actuator response
if ret.flags & ToyotaFlags.HYBRID.value:
@@ -131,9 +130,6 @@ class CarInterface(CarInterfaceBase):
if candidate in UNSUPPORTED_DSU_CAR:
ret.safetyParam |= ToyotaSafetyFlagsSP.UNSUPPORTED_DSU
if candidate == CAR.TOYOTA_PRIUS_TSS2:
ret.flags |= ToyotaFlagsSP.SP_NEED_DEBUG_BSM.value
# Detect smartDSU, which intercepts ACC_CMD from the DSU (or radar) allowing openpilot to send it
# 0x2AA is sent by a similar device which intercepts the radar instead of DSU on NO_DSU_CARs
if 0x2FF in fingerprint[0] or (0x2AA in fingerprint[0] and candidate in NO_DSU_CAR):
@@ -1,5 +1,4 @@
from opendbc.car.structs import CarParams
from opendbc.car.can_definitions import CanData
SteerControlType = CarParams.SteerControlType
@@ -165,47 +164,3 @@ def toyota_checksum(address: int, sig, d: bytearray) -> int:
for i in range(len(d) - 1):
s += d[i]
return s & 0xFF
def create_set_bsm_debug_mode(lr_blindspot, enabled):
dat = b"\x02\x10\x60\x00\x00\x00\x00" if enabled else b"\x02\x10\x01\x00\x00\x00\x00"
dat = lr_blindspot + dat
return CanData(0x750, dat, 0)
def create_bsm_polling_status(lr_blindspot):
return CanData(0x750, lr_blindspot + b"\x02\x21\x69\x00\x00\x00\x00", 0)
# auto brake hold
def create_brake_hold_command(packer, frame, pre_collision_2, brake_hold_active):
# forward PRE_COLLISION_2 when auto brake hold is not active
values = {s: pre_collision_2[s] for s in [
"DSS1GDRV",
"DS1STAT2",
"DS1STBK2",
"PCSWAR",
"PCSALM",
"PCSOPR",
"PCSABK",
"PBATRGR",
"PPTRGR",
"IBTRGR",
"CLEXTRGR",
"IRLT_REQ",
"BRKHLD",
"AVSTRGR",
"VGRSTRGR",
"PREFILL",
"PBRTRGR",
"PCSDIS",
"PBPREPMP",
]}
if brake_hold_active:
values = {
"DSS1GDRV": 0x3FF,
"PBRTRGR": frame % 730 < 727, # cut actuation for 3 frames
}
return packer.make_can_msg("PRE_COLLISION_2", 0, values)
@@ -1,79 +0,0 @@
BO_ 1880 DEBUG: 8 XXX
SG_ BLINDSPOTSIDE : 7|8@0+ (1,0) [0|255] "" XXX
SG_ BLINDSPOT : 38|1@0+ (1,0) [0|15] "" XXX
SG_ BLINDSPOTD1 : 47|8@0+ (1,0) [0|255] "" XXX
SG_ BLINDSPOTD2 : 55|8@0+ (1,0) [0|255] "" XXX
BO_ 836 PRE_COLLISION_2: 8 DSU
SG_ DSS1GDRV : 7|10@0- (0.1,0) [0|0] "m/s^2" Vector__XXX
SG_ DS1STAT2 : 13|3@0+ (1,0) [0|0] "" Vector__XXX
SG_ DS1STBK2 : 10|3@0+ (1,0) [0|0] "" Vector__XXX
SG_ PCSWAR : 18|1@0+ (1,0) [0|0] "" FCM
SG_ PCSALM : 17|1@0+ (1,0) [0|0] "" FCM
SG_ PCSOPR : 16|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ PCSABK : 31|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ PBATRGR : 30|2@0+ (1,0) [0|0] "" Vector__XXX
SG_ PPTRGR : 28|1@0+ (1,0) [0|0] "" FCM
SG_ IBTRGR : 27|1@0+ (1,0) [0|0] "" FCM
SG_ CLEXTRGR : 26|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ IRLT_REQ : 25|2@0+ (1,0) [0|0] "" Vector__XXX
SG_ BRKHLD : 37|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ AVSTRGR : 36|1@0+ (1,0) [0|0] "" SCS
SG_ VGRSTRGR : 35|2@0+ (1,0) [0|0] "" Vector__XXX
SG_ PREFILL : 33|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ PBRTRGR : 32|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ PCSDIS : 43|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ PBPREPMP : 40|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ CHECKSUM : 63|8@0+ (1,0) [0|0] "" XXX
BO_ 713 HYBRID_POWERTRAIN: 8 XXX
SG_ COAST_FUEL_CUT : 5|1@0+ (1,0) [0|1] "" XXX
SG_ HYBRID_DRIVE_STATE : 11|4@0+ (1,0) [0|15] "" XXX
BO_ 814 BRAKE_HYDRAULIC: 8 XXX
SG_ BRAKE_PRESSURE : 15|8@0+ (1,0) [0|255] "" XXX
SG_ BRAKE_PRESSURE_COPY1 : 31|8@0+ (1,0) [0|255] "" XXX
SG_ BRAKE_PRESSURE_COPY2 : 47|8@0+ (1,0) [0|255] "" XXX
BO_ 971 MOTOR_SPEED_CLUSTER: 7 XXX
SG_ CLUSTER_SPEED : 55|8@0+ (1,-21) [0|255] "km/h" XXX
BO_ 896 HYBRID_BATTERY: 8 XXX
SG_ HV_SOC_PCT : 55|8@0+ (1,0) [0|100] "%" XXX
BO_ 1654 HV_POWER_INTEGRATOR: 8 XXX
SG_ HV_POWER_ACCUM : 39|8@0+ (1,0) [0|255] "" XXX
BO_ 975 HV_INVERTER: 5 XXX
SG_ HV_VOLTAGE_LO : 23|8@0+ (1,0) [0|255] "" XXX
SG_ HV_CURRENT_LO : 39|8@0+ (1,0) [0|255] "" XXX
BO_ 955 BRAKE_REDUNDANT: 8 XXX
SG_ BRAKE_PRESSED_REDUNDANT : 0|1@0+ (1,0) [0|1] "" XXX
BO_ 918 VEHICLE_DRIVE_MODE: 8 XXX
SG_ DRIVE_MODE_STATE : 7|8@0+ (1,0) [0|255] "" XXX
CM_ "Sunnypilot Prius TSS2 reverse-engineered signals — route 550a71ee4c7a7fbe/0000038f--6b48497966, 2026-05-19. Confidence: high unless noted.";
CM_ BO_ 713 "Hybrid powertrain status. 100Hz on bus 0.";
CM_ SG_ 713 COAST_FUEL_CUT "byte0 bit5. 1 when accelerator lifted at speed -> ICE fuel cut / decel-fuel-cut active. P(=1|coast)=0.95, P(=1|accel)=0.008, P(=1|brake)=0.0. Use: lift detection for accel feedforward, EV-only mode hint.";
CM_ SG_ 713 HYBRID_DRIVE_STATE "byte1 lower-nibble (bits 8-11). Enum (validated): 4=brake/regen-active, 5-8=coast/cruise levels, 10/12=ICE running active, 11=idle-stop. Not a scalar magnitude. Use: distinguish ICE-on vs EV for accel command shaping.";
CM_ BO_ 814 "Brake hydraulic. 5Hz on bus 0. Bytes 0,2,4,6 are status/flag bytes (not yet decoded). Saturates 255 under hard brake.";
CM_ SG_ 814 BRAKE_PRESSURE "byte1. Master cylinder pressure proxy. 0 during coast, 33-45 mean during brake, 254-255 max. NOT same as BRAKE_MODULE.BRAKE_PRESSURE.";
CM_ SG_ 814 BRAKE_PRESSURE_COPY1 "byte3. Identical to BRAKE_PRESSURE 90% of brake samples. Redundant copy (CRC/safety).";
CM_ SG_ 814 BRAKE_PRESSURE_COPY2 "byte5. Identical to BRAKE_PRESSURE 90% of brake samples. Redundant copy.";
CM_ BO_ 971 "Cluster speedometer feed. 10Hz on bus 0.";
CM_ SG_ 971 CLUSTER_SPEED "byte6. Encoded as raw_byte = v_kph + 21. After (1,-21) factor/offset: km/h matching dashboard speedometer. Corr 0.998 w/ WHEEL_SPEEDS. ~5km/h higher than wheel mean = cluster display bias.";
CM_ BO_ 896 "HV battery status. 5Hz on bus 0.";
CM_ SG_ 896 HV_SOC_PCT "byte6. HV battery state-of-charge percent. Validated range 53-62% on route 0000038f (Prius hybrid SoC normal band 40-80%). Use: energy-aware planner — reduce regen demand at high SoC (>70%), lift accel demand at low SoC (<50%).";
CM_ BO_ 1654 "HV power integrator. 1Hz on bus 0. Slow drift signal, semantic not fully confirmed.";
CM_ SG_ 1654 HV_POWER_ACCUM "byte4. Slow-drifting integer 0-59, independent of HV_SOC_PCT. Hypothesis: HV power flow accumulator or charging-cycle counter. Needs more routes.";
CM_ BO_ 975 "HV inverter telemetry. 10Hz on bus 0. 5-byte message.";
CM_ SG_ 975 HV_VOLTAGE_LO "byte2. Sequential integer 40-53 range. Hypothesis: HV bus voltage low-byte scaled. Moderate corr w/ brake (regen-loaded voltage).";
CM_ SG_ 975 HV_CURRENT_LO "byte4. Sequential integer 17-36 range. JUMPS -16 at brake-release (regen current cutoff signature). Use: real-time regen current readback for brake-blend tuning.";
CM_ BO_ 955 "Brake redundant signal. 10Hz on bus 0.";
CM_ SG_ 955 BRAKE_PRESSED_REDUNDANT "byte0 bit0. 99.9% agreement with BRAKE_MODULE.BRAKE_PRESSED. Safety-redundant copy. Use: cross-check brake state for fault detection.";
CM_ BO_ 918 "Vehicle drive mode state. 5Hz on bus 0.";
CM_ SG_ 918 DRIVE_MODE_STATE "byte0. 3 states observed: 185=parked-with-brake, 189=ready-to-drive, 191=ready-no-brake. Transitions on GEAR shifts + brake. Use: more reliable drive-state machine than gear+brake combo.";
VAL_ 713 HYBRID_DRIVE_STATE 4 "brake_regen_active" 5 "coast_5" 6 "coast_6" 7 "coast_7" 8 "coast_cruise" 10 "ice_active_10" 11 "idle_stop" 12 "ice_active_12";
VAL_ 918 DRIVE_MODE_STATE 185 "parked_brake" 189 "ready" 191 "ready_no_brake";
@@ -234,7 +234,6 @@ BO_ 956 GEAR_PACKET: 8 XXX
SG_ SPORT_GEAR_ON : 33|1@0+ (1,0) [0|1] "" XXX
SG_ SPORT_GEAR : 38|3@0+ (1,0) [0|7] "" XXX
SG_ ECON_ON : 40|1@0+ (1,0) [0|1] "" XXX
SG_ SPORT_ON_2 : 55|1@0+ (1,0) [0|1] "" XXX
SG_ B_GEAR_ENGAGED : 41|1@0+ (1,0) [0|1] "" XXX
SG_ DRIVE_ENGAGED : 47|1@0+ (1,0) [0|1] "" XXX
@@ -546,7 +545,6 @@ VAL_ 956 GEAR 0 "D" 1 "S" 8 "N" 16 "R" 32 "P";
VAL_ 956 SPORT_GEAR_ON 0 "off" 1 "on";
VAL_ 956 SPORT_GEAR 1 "S1" 2 "S2" 3 "S3" 4 "S4" 5 "S5" 6 "S6";
VAL_ 956 ECON_ON 0 "off" 1 "on";
VAL_ 956 SPORT_ON_2 0 "off" 1 "on";
VAL_ 956 B_GEAR_ENGAGED 0 "off" 1 "on";
VAL_ 956 DRIVE_ENGAGED 0 "off" 1 "on";
VAL_ 1005 REVERSE_CAMERA_GUIDELINES 3 "No guidelines" 2 "Static guidelines" 1 "Active guidelines";
@@ -35,6 +35,14 @@ BO_ 740 STEERING_LKA: 5 XXX
SG_ STEER_TORQUE_CMD : 15|16@0- (1,0) [0|65535] "" XXX
SG_ CHECKSUM : 39|8@0+ (1,0) [0|255] "" XXX
BO_ 836 PRE_COLLISION_2: 8 DSU
SG_ DSS1GDRV : 7|10@0- (0.1,0) [0|0] "m/s^2" Vector__XXX
SG_ PCSALM : 17|1@0+ (1,0) [0|0] "" FCM
SG_ IBTRGR : 27|1@0+ (1,0) [0|0] "" FCM
SG_ PBATRGR : 30|2@0+ (1,0) [0|0] "" Vector__XXX
SG_ PREFILL : 33|1@0+ (1,0) [0|0] "" Vector__XXX
SG_ AVSTRGR : 36|1@0+ (1,0) [0|0] "" SCS
SG_ CHECKSUM : 63|8@0+ (1,0) [0|0] "" XXX
CM_ SG_ 466 NEUTRAL_FORCE "force in newtons the engine/electric motors are applying without any acceleration commands or user input";
CM_ SG_ 466 ACC_BRAKING "whether brakes are being actuated from ACC command";
@@ -1,7 +1,6 @@
CM_ "IMPORT _community.dbc";
CM_ "IMPORT _toyota_2017.dbc";
CM_ "IMPORT _toyota_adas_standard.dbc";
CM_ "IMPORT _sp_debug_toyota.dbc";
BO_ 548 BRAKE_MODULE: 8 XXX
SG_ BRAKE_PRESSURE : 43|12@0+ (1,0) [0|4047] "" XXX
@@ -1,7 +1,6 @@
CM_ "IMPORT _community.dbc";
CM_ "IMPORT _toyota_2017.dbc";
CM_ "IMPORT _toyota_adas_standard.dbc";
CM_ "IMPORT _sp_debug_toyota.dbc";
BO_ 401 STEERING_LTA: 8 XXX
SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX
@@ -1,7 +1,6 @@
CM_ "IMPORT _community.dbc";
CM_ "IMPORT _toyota_2017.dbc";
CM_ "IMPORT _toyota_adas_standard.dbc";
CM_ "IMPORT _sp_debug_toyota.dbc";
BO_ 550 BRAKE_MODULE: 8 XXX
SG_ BRAKE_PRESSURE : 0|9@0+ (1,0) [0|511] "" XXX
+2 -44
View File
@@ -4,7 +4,7 @@
// Stock longitudinal
#define TOYOTA_BASE_TX_MSGS \
{0x191, 0, 8, .check_relay = true}, {0x412, 0, 8, .check_relay = true}, {0x1D2, 0, 8, .check_relay = false}, {0x750, 0, 8, .check_relay = false}, /* LKAS + LTA + PCM cancel cmd */ \
{0x191, 0, 8, .check_relay = true}, {0x412, 0, 8, .check_relay = true}, {0x1D2, 0, 8, .check_relay = false}, /* LKAS + LTA + PCM cancel cmd */ \
#define TOYOTA_COMMON_TX_MSGS \
TOYOTA_BASE_TX_MSGS \
@@ -69,7 +69,6 @@ static bool toyota_secoc = false;
static bool toyota_alt_brake = false;
static bool toyota_stock_longitudinal = false;
static bool toyota_lta = false;
static bool toyota_cruise_engaged = false; // SP: PCM_CRUISE.CRUISE_ACTIVE, narrows the auto brake hold AEB window below
static int toyota_dbc_eps_torque_factor = 100; // conversion factor for STEER_TORQUE_EPS in %: see dbc file
static uint32_t toyota_compute_checksum(const CANPacket_t *msg) {
@@ -152,7 +151,6 @@ static void toyota_rx_hook(const CANPacket_t *msg) {
if (msg->addr == 0x176U) {
bool cruise_engaged = GET_BIT(msg, 5U); // PCM_CRUISE.CRUISE_ACTIVE
pcm_cruise_check(cruise_engaged);
toyota_cruise_engaged = cruise_engaged;
}
if (msg->addr == 0x116U) {
gas_pressed = msg->data[1] != 0U; // GAS_PEDAL.GAS_PEDAL_USER
@@ -164,7 +162,6 @@ static void toyota_rx_hook(const CANPacket_t *msg) {
if (msg->addr == 0x1D2U) {
bool cruise_engaged = GET_BIT(msg, 5U); // PCM_CRUISE.CRUISE_ACTIVE
pcm_cruise_check(cruise_engaged);
toyota_cruise_engaged = cruise_engaged;
if (!enable_gas_interceptor) {
gas_pressed = !GET_BIT(msg, 4U); // PCM_CRUISE.GAS_RELEASED
@@ -389,35 +386,13 @@ static bool toyota_tx_hook(const CANPacket_t *msg) {
tx = false;
}
}
// SP: auto brake hold https://github.com/AlexandreSato
if ((msg->addr == 0x344U) && (alternative_experience & ALT_EXP_ALLOW_AEB)) {
if (vehicle_moving || gas_pressed || !acc_main_on || toyota_cruise_engaged) {
tx = false;
}
}
}
// UDS: Only tester present ("\x0F\x02\x3E\x00\x00\x00\x00\x00") allowed on diagnostics address
if (msg->addr == 0x750U) {
// this address is sub-addressed. only allow tester present to radar (0xF)
bool invalid_uds_msg = (GET_BYTES(msg, 0, 4) != 0x003E020FU) || (GET_BYTES(msg, 4, 4) != 0x0U);
// SP: Secret sauce from dp. (ask @rav4kumar prior to modifying)
// Enhanced BSM
bool sp_valid_uds_msgs = ((GET_BYTES(msg, 0, 4) == 0x01100241U) || // disable left BSM debug
(GET_BYTES(msg, 0, 4) == 0x60100241U) || // enable left BSM debug
(GET_BYTES(msg, 0, 4) == 0x69210241U) || // poll left BSM status
(GET_BYTES(msg, 0, 4) == 0x01100242U) || // disable right BSM debug
(GET_BYTES(msg, 0, 4) == 0x60100242U) || // enable right BSM debug
(GET_BYTES(msg, 0, 4) == 0x69210242U)) // poll right BSM status
&& (GET_BYTES(msg, 4, 4) == 0x0U);
sp_valid_uds_msgs |= (GET_BYTES(msg, 0, 4) == 0x11300540U) && // automatic door locking and unlocking
((GET_BYTES(msg, 4, 4) == 0x00004000U) || // unlock
(GET_BYTES(msg, 4, 4) == 0x00008000U)); // lock
bool valid_tester_present = !invalid_uds_msg && !toyota_stock_longitudinal && !toyota_secoc;
if (!valid_tester_present && !sp_valid_uds_msgs) {
if (invalid_uds_msg) {
tx = false;
}
}
@@ -591,27 +566,10 @@ static safety_config toyota_init(uint16_t param) {
return ret;
}
static bool toyota_fwd_hook(int bus_num, int addr) {
bool block_msg = false;
if (bus_num == 2) {
// SP: block AEB when auto brake hold is active, unblock AEB when auto brake hold is not active.
// Narrowed to match auto brake hold's own precondition (cruise must be off) - previously this
// blocked native AEB forwarding, forcing a slower software relay, any time the car was simply
// stopped with the gas released and ACC main on, even while cruise was actively engaged and
// auto brake hold couldn't be active at all.
bool is_aeb_msg = (addr == 0x344);
block_msg = (is_aeb_msg && (alternative_experience & ALT_EXP_ALLOW_AEB) && !vehicle_moving && !gas_pressed && acc_main_on &&
!toyota_cruise_engaged);
}
return block_msg;
}
const safety_hooks toyota_hooks = {
.init = toyota_init,
.rx = toyota_rx_hook,
.tx = toyota_tx_hook,
.fwd = toyota_fwd_hook,
.get_checksum = toyota_get_checksum,
.compute_checksum = toyota_compute_checksum,
.get_quality_flag_valid = toyota_get_quality_flag_valid,
@@ -101,29 +101,6 @@ class TestToyotaSafetyBase(common.CarSafetyTest, common.LongitudinalAccelSafetyT
tester_present = libsafety_py.make_CANPacket(0x750, 0, msg)
self.assertEqual(should_tx and ecu_disabled and not stock_longitudinal, self._tx(tester_present))
def test_enhanced_bsm(self):
# SP: enable/disable/poll left+right blind spot debug mode, sent to the radar diagnostic address
valid_msgs = [
b"\x41\x02\x10\x60\x00\x00\x00\x00", # enable left
b"\x41\x02\x10\x01\x00\x00\x00\x00", # disable left
b"\x41\x02\x21\x69\x00\x00\x00\x00", # poll left
b"\x42\x02\x10\x60\x00\x00\x00\x00", # enable right
b"\x42\x02\x10\x01\x00\x00\x00\x00", # disable right
b"\x42\x02\x21\x69\x00\x00\x00\x00", # poll right
]
for msg in valid_msgs:
pkt = libsafety_py.make_CANPacket(0x750, 0, msg)
self.assertTrue(self._tx(pkt), msg.hex())
invalid_msgs = [
b"\x41\x02\x10\x61\x00\x00\x00\x00", # wrong subfunction
b"\x43\x02\x10\x60\x00\x00\x00\x00", # wrong sub-address (not left/right)
b"\x41\x02\x10\x60\x01\x00\x00\x00", # non-zero trailing bytes
]
for msg in invalid_msgs:
pkt = libsafety_py.make_CANPacket(0x750, 0, msg)
self.assertFalse(self._tx(pkt), msg.hex())
def test_block_aeb(self, stock_longitudinal: bool = False):
for controls_allowed in (True, False):
for bad in (True, False):
@@ -14,7 +14,7 @@ from opendbc.car import structs
from opendbc.car.can_definitions import CanRecvCallable, CanSendCallable
from opendbc.car.hyundai.values import HyundaiFlags
from opendbc.car.subaru.values import SubaruFlags
from opendbc.car.toyota.values import RADAR_ACC_CAR, SECOC_CAR, TSS2_CAR, ToyotaSafetyFlags
from opendbc.car.toyota.values import ToyotaSafetyFlags
from opendbc.sunnypilot.car.hyundai.enable_radar_tracks import enable_radar_tracks as hyundai_enable_radar_tracks
from opendbc.sunnypilot.car.hyundai.longitudinal.helpers import LongitudinalTuningType
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP
@@ -157,10 +157,6 @@ def _initialize_toyota(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params
if CP.brand == 'toyota':
toyota_stock_long = int(params_dict.get("ToyotaEnforceStockLongitudinal", 0)) == 1
toyota_stop_and_go_hack = int(params_dict.get("ToyotaStopAndGoHack", 0)) == 1
toyota_tss2_long_tuning = int(params_dict.get("ToyotaTSS2Long", 0)) == 1
toyota_enhanced_bsm = int(params_dict.get("ToyotaEnhancedBsm", 0)) == 1
toyota_auto_brake_hold = int(params_dict.get("ToyotaAutoHold", 0)) == 1
toyota_brake_onset = int(params_dict.get("ToyotaBrakeOnset", 0)) == 1
if toyota_stock_long:
CP_SP.flags |= ToyotaFlagsSP.STOCK_LONGITUDINAL.value
@@ -170,15 +166,3 @@ def _initialize_toyota(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params
if toyota_stop_and_go_hack and CP.openpilotLongitudinalControl:
CP_SP.flags |= ToyotaFlagsSP.STOP_AND_GO_HACK.value
if toyota_tss2_long_tuning:
CP_SP.flags |= ToyotaFlagsSP.TSS2_LONG_TUNING.value
if toyota_enhanced_bsm and CP.carFingerprint in (TSS2_CAR - SECOC_CAR):
CP_SP.flags |= ToyotaFlagsSP.SP_ENHANCED_BSM.value
if toyota_auto_brake_hold and CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR - SECOC_CAR):
CP_SP.flags |= ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD.value
if toyota_brake_onset:
CP_SP.flags |= ToyotaFlagsSP.SP_BRAKE_ONSET.value
@@ -1,92 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import math
from opendbc.car import structs
from opendbc.car.toyota import toyotacan
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
GearShifter = structs.CarState.GearShifter
# frames of confirmed hold-eligible standstill required before engaging
BRAKE_HOLD_ALLOWED_TIMER = 100
BRAKE_HOLD_MIN_FORCE = 1400.0 #n
BRAKE_HOLD_LIGHT_TIMER = 250
DISALLOWED_GEARS = (GearShifter.park, GearShifter.reverse)
# PRE_COLLISION_2 fields that go high when the camera's own PCS/AEB is genuinely intervening this
# frame (PCSALM mirrors PRECOLLISION_ACTIVE; IBTRGR/PBATRGR/PREFILL/AVSTRGR/PBRTRGR/PPTRGR are its
# actuation triggers - see create_pcs_commands for the same field set on the stock-DSU PCS path).
# Deliberately over-inclusive: a false positive here just means we pass a quiescent frame through
# instead of holding it, never the other way around, so err toward checking more fields, not fewer.
PCS_TRIGGER_FIELDS = ("PCSALM", "IBTRGR", "PBATRGR", "PREFILL", "AVSTRGR", "PBRTRGR", "PPTRGR")
def pcs_is_active(pre_collision_2: dict) -> bool:
return any(pre_collision_2.get(field, 0) for field in PCS_TRIGGER_FIELDS) or pre_collision_2.get("DSS1GDRV", 0) != 0
class AutoBrakeHold:
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP):
self.CP = CP
self.CP_SP = CP_SP
@property
def enabled(self):
return bool(self.CP_SP.flags & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD)
# Auto Brake Hold (@AlexandreSato, @rav4kumar): holds the car at a stop with cruise off by
# overriding PRE_COLLISION_2 - the only channel on this platform that can command the brake
# independent of ACC engagement, since PCS/AEB is an always-on active safety system by design.
# Yields to any genuine PCS activation this frame - the real message is only ever overridden while
# it's quiescent - and releases for the rest of the current standstill episode on a brake press,
# rather than for a single frame.
class AutoBrakeHoldCarController(AutoBrakeHold):
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP):
super().__init__(CP, CP_SP)
self.active = False
self._counter = 0
self._released = False
self._armed = False
self._firm_frame = 0
self._prev_brake_pressed = False
def update(self, CS: structs.CarState, frame: int, packer) -> list:
relay_blocked = (CS.out.standstill and CS.out.cruiseState.available and not CS.out.cruiseState.enabled and
not CS.out.gasPressed)
hold_allowed = relay_blocked and CS.out.gearShifter not in DISALLOWED_GEARS
if hold_allowed:
# only a fresh press releases hold - the press that caused the stop is already reflected in
# _prev_brake_pressed by the time standstill is reached, so it doesn't count as a release
if CS.out.brakePressed and not self._prev_brake_pressed:
self._released = True
force = getattr(CS, "brake_force", float("nan"))
self._counter += 1
if not self._armed and (math.isnan(force) or force >= BRAKE_HOLD_MIN_FORCE):
self._armed = True
self._firm_frame = self._counter
firm_ready = self._armed and self._counter - self._firm_frame >= BRAKE_HOLD_ALLOWED_TIMER
held_long = self._counter > BRAKE_HOLD_LIGHT_TIMER
self.active = (firm_ready or held_long) and not self._released
else:
self._counter = 0
self.active = False
self._released = False
self._armed = False
self._prev_brake_pressed = CS.out.brakePressed
can_sends = []
if relay_blocked and frame % 2 == 0:
override = self.active and not pcs_is_active(CS.pre_collision_2)
can_sends.append(toyotacan.create_brake_hold_command(packer, frame, CS.pre_collision_2, override))
return can_sends
@@ -1,416 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import math
import numpy as np
ONSET_T_BP = [0.0, 0.15, 0.6] # s
# Speed schedule of the brake onset. Below 10 km/h (creep follow: the lead moves a little, the car catches up and
# stops again) the Altis PCM bites hard on a modest request (route 000000ed 04:05:41: output -0.42 -> -1.25 m/s^2,
# 1400 N; five ACC re-stops on 10-03/04 all -0.9..-1.25), so at creep speeds the onset uses the same numbers as the
# engage schedule (driver 2026-10-04 "yes, apply it"): 0.5 m/s^3 for 0.2 s, stock by 0.7 s; from 20 km/h the normal
# 1.0 m/s^3 for 0.1 s, stock by 0.4 s (driver 2026-10-03), interpolated between.
# Driver 2026-10-04 after route 000000f2: "the brake after creeping forward again improved but is still not soft
# enough, softer again" -> below 10 km/h 0.3 m/s^3 for 0.3 s (-0.09 m/s^2 at 0.3 s), stock by 0.9 s.
# Driver 2026-10-05: "at 50 km/h the brake still feels heavy and there was plenty of distance; every brake should start
# extremely light, and if that needs distance, start earlier" -> the creep-speed numbers (0.3 m/s^3 for 0.3 s, stock
# by 0.9 s) now apply at every speed up to 60 km/h, fading to the highway onset (1.0 m/s^3, 0.1 s, stock by 0.4 s) at
# 80 km/h, which stays as it was. The planner's light-onset margin (long_mpc) supplies the extra distance.
# Driver 2026-10-05, same drive: "at 40-50 km/h the build from light to firm is still not smooth, a bit abrupt" -> the
# jerk the onset ramps UP TO is also scheduled: 2.0 m/s^3 up to 60 km/h (was the stock 4.0 from 0.9 s on), stock at 80.
ONSET_V_BP = [16.7, 22.2] # m/s (60, 80 km/h)
ONSET_T1_V = [0.3, 0.1]
ONSET_T3_V = [0.9, 0.4]
ONSET_J1_V = [0.3, 1.0] # m/s^3 during the first phase
ONSET_J3_V = [2.0, 4.0] # m/s^3 the ramp ends at (the shaper never exceeds the stock limit)
ONSET_J_DOWN = [1.0, 1.0, 4.0] # m/s^3 (reference shape; the first two entries follow ONSET_J1_V)
# Engaging ACC (SET-) while creeping up to a lead (driver 2026-10-04: "the car used to brake hard the moment I press
# SET-; the first ~0.2 s must be a light touch, then blend into the decel the speed needs"). Route 000000ee 11:48:07:
# engaged at 9 km/h 4 m behind a 4 km/h lead, the request went 0 -> -0.42 in 0.2 s and -1.6 by 0.8 s, the car hit
# -2.7 m/s^2 (3280 N). While the engage window is open the down jerk is capped on time SINCE ENGAGING: 0.5 m/s^3 for
# the first 0.2 s (-0.1 m/s^2 at 0.2 s), then rising to stock by 0.7 s; the planner's full request still arrives, just
# later. The normal onset schedule applies on top (the stricter of the two wins).
ENGAGE_BRAKE_T_BP = [0.0, 0.2, 0.7] # s since engaging
ENGAGE_BRAKE_J_DOWN = [0.5, 0.5, 4.0] # m/s^3
# Owner 2026-10-06: only a plan beyond -3.0 (a genuine emergency) bypasses the soft onset; -2.0 .. -3.0 keep it (at
# 40 km/h -2.5 is reached slowly in ~1.8 s). History: -1.5 until 2026-10-03, -2.0, then -3.5 (= never) on 2026-10-05.
HARD_BRAKE_ACCEL = -3.0
URGENT_T = 0.1 # s
URGENT_J = 1.0 # m/s^3
URGENT_T_RAMP = 0.1 # s
# Creep-follow gas release, with the creep-follow stop (see
# stopping_controller.py). Route 0000010d 23:56:59: after a +0.5 launch the plan turned to braking within 0.2 s, but the
# soft onset above treated the drop from +0.5 as the start of a brake (0.3 m/s^3) and the command stayed positive for
# ~0.7 s while the car kept accelerating into a 2 m gap; then the brake bit 0 -> 1640 N. Taking away gas is not
# braking: below CREEP_GAS_V the positive part of the command is now taken away at the stock rate; the soft onset
# starts from zero, where the braking starts.
CREEP_GAS_V = 8.0 / 3.6 # m/s
# Friction entry (hybrid). Owner 2026-10-09: "no heavy-press jolt at the start of a brake - this is what I most want
# gone; even -2.6 is fine if it is reached smoothly". On this hybrid the first ~-0.6 m/s^2 of a brake is regen; past it
# the PCM brings in the friction brake (BRAKE 0xA6 BRAKE_FORCE), and how hard that bites depends on how fast the
# command is moving at that moment. 27 engaged onsets with a friction entry, routes 00000109-00000119 (onset_bite.py):
# command rate at friction entry -1.91 / -1.05 / -0.44 m/s^3 -> delivered jerk -3.70 / -2.22 / -1.22 m/s^3,
# friction force rate 3067 / 1067 / 533 N/s (r = 0.78; gap command-delivered at entry r = 0.80).
# The onset schedule above opens to 2.0 m/s^3 by 0.9 s - right when the friction usually enters (median 0.9 s), e.g.
# route 00000119 09:23:02 (46 km/h, lead braking to a stop): command -0.9 -> -1.66 smoothly, the car sat at the regen
# limit (-0.55), then the friction came in 680 -> 1520 N and the car went -0.76 -> -1.98 m/s^2 in 0.5 s.
# So while the command is more than ENTRY_GAP beyond what the car delivers and the friction force is still below
# ENTRY_FORCE_N, the command moves at most ENTRY_J: the friction enters slowly and the car catches up. After the entry
# (force established or ENTRY_T_MAX) the command closes the remaining gap at CATCHUP_J, then the normal limits apply.
# Not for FCW / urgent (< HARD_BRAKE_ACCEL) requests, below ENTRY_V_MIN (creep follow has its own onset), or without
# measurements (a_ego None).
ENTRY_GAP = 0.25 # m/s^2 the command may lead the delivered decel before the entry limit applies
ENTRY_FORCE_N = 600.0 # N: friction established
ENTRY_J = 0.5 # m/s^3 while the friction enters
ENTRY_T_MAX = 1.0 # s per brake onset
CATCHUP_J = 1.5 # m/s^3 after the entry until the command reaches the request
ENTRY_V_MIN = 10.0 / 3.6 # m/s
class BrakeOnsetShaper:
def __init__(self, dt: float, stock_down_jerk: float):
self.dt = dt
self.stock_down_jerk = stock_down_jerk
self.t_onset = 0.0
self.t_entry = 0.0 # time spent limited by the friction entry in this brake
self.catchup = False # after an entry: close the gap gently
self.friction_in = False
def reset(self) -> None:
self.t_onset = 0.0
self.t_entry = 0.0
self.catchup = False
self.friction_in = False
def friction_entry_step(self, accel_request: float, prev_accel: float, v_ego: float, a_ego: float | None,
brake_force: float) -> float | None:
"""largest down step (> 0, m/s^2 per step) while the friction brake enters, None = no limit"""
if a_ego is None or v_ego < ENTRY_V_MIN or prev_accel > -0.1:
self.t_entry, self.catchup, self.friction_in = 0.0, False, False
return None
if math.isfinite(brake_force) and brake_force >= ENTRY_FORCE_N:
self.friction_in = True
if accel_request >= prev_accel:
self.catchup = False
return None
leading = prev_accel - a_ego < -ENTRY_GAP
if leading and not self.friction_in and self.t_entry < ENTRY_T_MAX:
self.t_entry += self.dt
self.catchup = True
return ENTRY_J * self.dt
if self.catchup:
return CATCHUP_J * self.dt
return None
@staticmethod
def schedule_t(v_ego: float) -> list[float]:
t1 = float(np.interp(v_ego, ONSET_V_BP, ONSET_T1_V))
t3 = float(np.interp(v_ego, ONSET_V_BP, ONSET_T3_V))
return [0.0, t1, t3]
@staticmethod
def schedule_j(v_ego: float) -> list[float]:
j1 = float(np.interp(v_ego, ONSET_V_BP, ONSET_J1_V))
j3 = float(np.interp(v_ego, ONSET_V_BP, ONSET_J3_V))
return [j1, j1, j3]
def down_step(self, accel_request: float, prev_accel: float, bypass: bool = False, v_ego: float = 30.0,
urgent: bool = False, t_engaged: float | None = None, a_ego: float | None = None,
brake_force: float = float('nan')) -> float:
if bypass:
self.t_onset = 0.0
self.t_entry, self.catchup = 0.0, False
return -self.stock_down_jerk * self.dt
entry = None if urgent else self.friction_entry_step(accel_request, prev_accel, v_ego, a_ego, brake_force)
step = self._down_step(accel_request, prev_accel, v_ego, urgent, t_engaged)
return step if entry is None else -min(-step, entry)
def _down_step(self, accel_request: float, prev_accel: float, v_ego: float, urgent: bool,
t_engaged: float | None) -> float:
if prev_accel > 0.0 and v_ego < CREEP_GAS_V and accel_request < prev_accel:
self.t_onset = 0.0
return -min(self.stock_down_jerk * self.dt, prev_accel - max(accel_request, 0.0))
if urgent:
t_bp, j_bp = [0.0, URGENT_T, URGENT_T + max(URGENT_T_RAMP, self.dt)], [URGENT_J, URGENT_J, self.stock_down_jerk]
else:
t_bp, j_bp = self.schedule_t(v_ego), self.schedule_j(v_ego)
gentlest_step = -ONSET_J_DOWN[0] * self.dt
onset = (accel_request - prev_accel) < gentlest_step - 1e-9
if onset:
j_down = float(np.interp(self.t_onset, t_bp, j_bp))
self.t_onset = min(self.t_onset + self.dt, t_bp[-1])
else:
j_down = self.stock_down_jerk
self.t_onset = max(self.t_onset - self.dt, 0.0)
if t_engaged is not None and t_engaged < ENGAGE_BRAKE_T_BP[-1]:
j_down = min(j_down, float(np.interp(t_engaged, ENGAGE_BRAKE_T_BP, ENGAGE_BRAKE_J_DOWN)))
return -min(j_down, self.stock_down_jerk) * self.dt
def is_urgent(self, accel_request: float, fcw: bool) -> bool:
return fcw or accel_request < HARD_BRAKE_ACCEL
ENGAGE_T_BP = [0.0, 0.1, 0.6] # s; the window is still used to keep the soft BRAKE onset for a hard request after engaging
# Driver 2026-10-04: "if Kumar's base added a soft start again, remove it - I want an immediate response, not a lurch,
# following the eco/normal/sport profile". The gas-side engage ramp (0.25 -> 0.6 -> 4.0 m/s^3 over 0.6 s) is gone:
# the up jerk after engaging is the stock windup limit; the accel profile sets how much.
ENGAGE_J_UP = [4.0, 4.0, 4.0] # m/s^3
# Resume while moving. Owner 2026-10-08: "when resuming the
# speed (RES), can the first 0.4 s accelerate more gently?". Routes 0000010e-00000112, eight RES engagements at 15-61
# km/h: the car was coasting at -0.3..-0.5 m/s^2 (regen), the command started from 0 and reached +0.3..+0.5 within
# 0.4-0.6 s, so in the first 0.4 s the regen vanished AND the gas came in (aEgo -0.43 -> +0.19 in 0.4 s at 23:09:07,
# +0.12 -> +0.64 in 0.6 s at 17:43:01, ~1.5-2 m/s^3). Now, when engaging above RESUME_V_MIN, the command starts from
# the coasting decel (aEgo, clipped to RESUME_START_MIN..0) instead of 0, and rises at RESUME_J_UP: 0.6 m/s^3 for the
# first 0.4 s, opening to the stock limit by 0.8 s. Replays of the logged plans: the command meets the stock one by
# 0.6-0.8 s, identical afterwards (the eco/normal/sport profile is untouched). Standstill launches are not affected.
RESUME_V_MIN = 15.0 / 3.6 # m/s
RESUME_T_BP = [0.0, 0.4, 0.8] # s since engaging
RESUME_J_UP = [0.6, 0.6, 4.0] # m/s^3
RESUME_START_MIN = -0.6 # m/s^2
RESUME_EDGE_T = 0.5 # s: the engagement must follow the cruise switching on (not a gas-override release)
class EngageOnsetShaper:
def __init__(self, dt: float, stock_up_jerk: float):
self.dt = dt
self.stock_up_jerk = stock_up_jerk
self.t_engaged: float | None = None
self.resume = False
self.cruise_on_t = 1e9 # s since the PCM cruise went from off to on (RES / SET)
self.prev_cruise = False
def reset(self) -> None:
self.t_engaged = None
self.resume = False
def cruise_state(self, cruise_enabled: bool) -> None:
"""called every frame: tracks RES / SET (cruise off -> on), so a gas-override release is not a resume"""
self.cruise_on_t = 0.0 if (cruise_enabled and not self.prev_cruise) else self.cruise_on_t + self.dt
self.prev_cruise = cruise_enabled
def start_accel(self, prev_accel: float, a_ego: float, v_ego: float) -> float:
"""on the first engaged frame of a resume while moving, the command starts from the coasting decel"""
if self.t_engaged is None and v_ego > RESUME_V_MIN and self.cruise_on_t < RESUME_EDGE_T:
self.resume = True
return float(np.clip(a_ego, RESUME_START_MIN, 0.0))
return prev_accel
@property
def in_engage_window(self) -> bool:
return self.t_engaged is None or self.t_engaged < ENGAGE_T_BP[-1]
@property
def t_since_engage(self) -> float | None:
"""seconds since engaging while the engage brake schedule still applies, else None"""
t = 0.0 if self.t_engaged is None else self.t_engaged
return t if t < ENGAGE_BRAKE_T_BP[-1] else None
def up_step(self, active: bool) -> float:
if not active:
self.t_engaged = None
return self.stock_up_jerk * self.dt
if self.t_engaged is None:
self.t_engaged = 0.0
else:
self.t_engaged += self.dt
if self.resume:
if self.t_engaged < RESUME_T_BP[-1]:
return min(float(np.interp(self.t_engaged, RESUME_T_BP, RESUME_J_UP)), self.stock_up_jerk) * self.dt
self.resume = False
if self.t_engaged >= ENGAGE_T_BP[-1]:
return self.stock_up_jerk * self.dt
j_up = float(np.interp(self.t_engaged, ENGAGE_T_BP, ENGAGE_J_UP))
return min(j_up, self.stock_up_jerk) * self.dt
# Hard-brake overshoot limit (owner 2026-10-06, route 00000100 19:41:42-45: the lead braked from 44 km/h at -3.3 m/s^2;
# openpilot asked for -3.49 at most, but the PCM with the hybrid's regen delivered -4.3 m/s^2 for 0.8 s (BRAKE_FORCE
# 4880 N), ~30% more than asked - the last, heaviest part of the "sudden brake" feel. The stock PID only lifted the
# command ~0.3 above the request.) Once the request is a hard brake, the car's own deceleration is watched: when it
# runs OVERSHOOT_DEADBAND past the request (predicted a moment ahead, as the PID does), the command is lifted by
# OVERSHOOT_GAIN times the excess, at most OVERSHOOT_MAX, so the car settles near what openpilot asked for. The request
# itself is never weakened: the limit only takes back braking the car delivered ON TOP of it (an MPC replay of 19:41
# with the asked-for -3.5 ends 4.9 m behind the stopped lead). It never lifts the command above OVERSHOOT_CMD_CEIL.
OVERSHOOT_ACTIVE_ACCEL = -2.5 # m/s^2: the limit works only while the request is harder than this
OVERSHOOT_DEADBAND = 0.2 # m/s^2 of overshoot that is left alone
OVERSHOOT_GAIN = 0.8
OVERSHOOT_MAX = 1.0 # m/s^2 largest lift
OVERSHOOT_RATE_UP = 3.0 # m/s^3 how fast the lift may grow
OVERSHOOT_RATE_DOWN = 2.0 # m/s^3 how fast it is given back (also when the hard request ends)
OVERSHOOT_CMD_CEIL = -1.5 # m/s^2: the lifted command always stays a firm brake
# Moderate band - Corolla Altis Hybrid only (owner 2026-10-07, route 0000010e 17:38:41: "at 50 km/h the braking started
# soft, but the speed dropped too fast, still a bit fierce"). A stopped queue appeared 74 m ahead after a lane change;
# openpilot asked for at most -1.60 m/s^2, the car delivered -1.82..-1.95 (regen on top). Routes 00000100..0000010e:
# above 20 km/h the Altis delivers 0.1-0.4 m/s^2 more than a -1.0..-2.0 request (e.g. 0000010d 15-25 km/h: asked
# -1.68, got -2.05). So above OVERSHOOT_MOD_V the limit also works for requests harder than OVERSHOOT_MOD_ACCEL, with
# a smaller deadband, and the lifted command never gets lighter than OVERSHOOT_MOD_KEEP of the command (the request
# is never weakened by more than a quarter). The hard band above is unchanged for every car.
OVERSHOOT_MOD_ACCEL = -1.0 # m/s^2
OVERSHOOT_MOD_V = 12.0 / 3.6 # m/s (0000010d 23:54:58: asked -1.72 at 17-23 km/h, delivered -1.96..-2.19)
OVERSHOOT_MOD_DEADBAND = 0.15 # m/s^2
OVERSHOOT_MOD_KEEP = 0.75
# The moderate band works as a slow trim, not a fast limiter: a replay of 17:38:36-38 with the hard band's logic
# (predicted aEgo, 3.0 / 2.0 m/s^3) pulsed the command by 0.2-0.3 m/s^2 for 0.2 s at a time on the noisy aEgo (-1.2 ..
# -1.8 at a steady request), and the stock PID was already lifting the command (-1.6 asked, ~-1.1 sent). So the band
# uses the overshoot of the measured aEgo, low-passed over OVERSHOOT_MOD_TAU, and moves at OVERSHOOT_MOD_RATE.
# Relaxed personality only (owner 2026-10-08). A process_replay of 14 Aggressive-mode segments (routes fe/100/101/104/
# 107/109) found the band acting mostly in CLOSE following: 0000109 23:19:44, lead 16 m away and still braking, request
# -1.99/-1.75 at 26-21 km/h, the car delivered -2.73/-2.27 and the band lifted the command by up to 0.35 - taking away a
# margin the close gap needs (open loop, no planner reaction: +6 m travelled). Tonight's Relaxed drives (routes
# 00000112-00000116) never triggered it. Owner: "keep D in Relaxed". The carcontroller passes
# hud_control.leadDistanceBars == 3 (controlsd: personality + 1; relaxed = 2 -> 3 bars).
# OFF since 2026-10-08 (owner: "so D never acted in Relaxed last night? then switch it off and try"): routes
# 00000112-00000116 never triggered it, and in Aggressive close following it removes margin. Kept in the code; set True
# to bring it back (Relaxed only).
OVERSHOOT_MOD_ENABLED = False
OVERSHOOT_MOD_TAU = 0.5 # s
OVERSHOOT_MOD_RATE = 0.5 # m/s^3
class BrakeOvershootLimiter:
def __init__(self, dt: float, moderate: bool = False):
self.dt = dt
self.moderate = moderate # also the moderate band (OVERSHOOT_MOD_*)
self.lift = 0.0
self.in_moderate = False
self.mod_excess = 0.0
def reset(self) -> None:
self.lift = 0.0
self.in_moderate = False
self.mod_excess = 0.0
def update(self, accel_request: float, a_ego_future: float, active: bool = True, v_ego: float = 0.0,
a_ego: float | None = None, moderate_allowed: bool = True) -> float:
"""returns the lift (>= 0, m/s^2) to add to the command"""
target = 0.0
hard = accel_request < OVERSHOOT_ACTIVE_ACCEL
self.in_moderate = (self.moderate and moderate_allowed and not hard and accel_request < OVERSHOOT_MOD_ACCEL and
v_ego > OVERSHOOT_MOD_V)
if self.in_moderate:
measured = a_ego_future if a_ego is None else a_ego
alpha = self.dt / (OVERSHOOT_MOD_TAU + self.dt)
self.mod_excess += alpha * ((accel_request - measured) - self.mod_excess)
else:
self.mod_excess = 0.0
if active and hard:
excess = accel_request - a_ego_future - OVERSHOOT_DEADBAND # > 0: decelerating harder than asked
target = float(np.clip(OVERSHOOT_GAIN * excess, 0.0, OVERSHOOT_MAX))
elif active and self.in_moderate:
target = float(np.clip(OVERSHOOT_GAIN * (self.mod_excess - OVERSHOOT_MOD_DEADBAND), 0.0, OVERSHOOT_MAX))
if self.in_moderate and not hard:
step = OVERSHOOT_MOD_RATE * self.dt
self.lift = float(np.clip(target, self.lift - step, self.lift + step))
else:
self.lift = float(np.clip(target, self.lift - OVERSHOOT_RATE_DOWN * self.dt, self.lift + OVERSHOOT_RATE_UP * self.dt))
return self.lift
def apply(self, accel_cmd: float) -> float:
if self.lift <= 0.0:
return accel_cmd
ceiling = OVERSHOOT_MOD_KEEP * accel_cmd if self.in_moderate else OVERSHOOT_CMD_CEIL
return min(accel_cmd + self.lift, max(accel_cmd, ceiling))
# Low-speed regen hand-over feed-forward. History: route 0000010d 23:54:58-23:55:03 - between 20 and 9 km/h the car cut its own
# friction brake (BRAKE 0xA6 BRAKE_FORCE 1640 -> 440 N) and delivered ~70% of the request, felt as "brakes, goes light,
# brakes again". A feedback compensation (shortfall x 0.8 past a 0.2 deadband, 1.0 m/s^3) was the first fix; its
# first drive, route 0000010e 2026-10-07, showed it acting only in 0.1-0.25 m/s^2 pulses that never filled the gap
# (owner: "the stops are sometimes soft, sometimes still not smooth; several stops still braked twice").
# Measured on routes 00000100..0000010e (engaged stops, request / delivered, m/s^2): the request eases smoothly, but the
# delivered decel DIPS at 15-9 km/h (10e: -0.84 asked, -0.61 delivered; 109: -0.87 / -0.69; 107: -1.04 / -0.81) and
# comes back ON TOP of the request at 7-1 km/h (10e: -0.47 / -0.60; 109: -0.54 / -0.80), the second part pushed by the
# PID integrator that wound up during the dip (command - request -0.10..-0.17 there). So, instead of feedback:
# - a FEED-FORWARD extra brake by speed, the measured dip: HANDOVER_EXTRA_MAX (or HANDOVER_EXTRA_FRAC of a lighter
# request) at 10-14 km/h, fading to 0 at 6 and 17 km/h, changing at HANDOVER_RATE, only while braking harder than
# HANDOVER_MIN_REQUEST;
# - below PID_HOLD_V, while the request brakes, the PID integrator is frozen and bled toward zero (a positive
# integral, left over from a launch, faster), so the catch-up no longer adds braking at the end of the stop.
# Expected delivered decel on 10e (by speed band 15-12/12-9/9-7/7-5/5-3/3-1 km/h): -0.86/-0.81/-0.75/-0.75/-0.63/-0.48,
# continuous easing, instead of -0.66/-0.61/-0.75/-0.75/-0.71/-0.60.
HANDOVER_V_BP = [6.0 / 3.6, 8.0 / 3.6, 10.0 / 3.6, 14.0 / 3.6, 17.0 / 3.6] # m/s
HANDOVER_V_W = [0.0, 0.5, 1.0, 1.0, 0.0]
HANDOVER_MIN_REQUEST = -0.3 # m/s^2
HANDOVER_EXTRA_MAX = 0.2 # m/s^2
HANDOVER_EXTRA_FRAC = 0.25 # of the request, for lighter requests
HANDOVER_RATE = 1.0 # m/s^3
# The dip is a moderate-brake effect: at a firm request the regen gives MORE, not less (0000010d 15-12 km/h: asked
# -1.46, delivered -1.72), so the extra fades out between these requests (full at -1.0 and lighter, none at -1.5).
HANDOVER_REQ_BP = [-1.5, -1.0] # m/s^2
HANDOVER_REQ_W = [0.0, 1.0]
# Owner 2026-10-10 (route 0000011c 00:19:10, Aggressive stop 1.6 m behind the lead): "the end of the stop slides too
# much, the last two metres feel too light". With the PID held below PID_HOLD_V nothing makes up the under-delivery
# below the hand-over band; asked / delivered by band (m/s^2), three stops of 11c (00:18:07 / 00:19:10 / 00:20:58):
# 8-7 km/h -0.75/-0.62 -0.89/-0.58 -0.77/-0.54 6-5 km/h -0.63/-0.45 -0.75/-0.50 -0.66/-0.49
# 5-4 km/h -0.60/-0.40 -0.67/-0.28 -0.64/-0.35 4-3 km/h -0.58/-0.62 -0.62/-0.45 -0.61/-0.57
# 3-2 km/h on target or above. Brake force 650-850 N at 6-4 km/h, below what beats the hybrid creep torque.
# So a second, low-speed feed-forward: LOW_EXTRA at LOW_EXTRA_V_BP (0.25 at 5-6.5 km/h, gone by 3 km/h, led by about
# 0.5 km/h for the ~0.3 s actuator lag), added to the hand-over extra, same request gate and rate.
LOW_EXTRA_V_BP = [3.0 / 3.6, 4.0 / 3.6, 5.0 / 3.6, 6.5 / 3.6, 8.5 / 3.6] # m/s
LOW_EXTRA = [0.0, 0.15, 0.25, 0.25, 0.0] # m/s^2
PID_HOLD_V = 9.0 / 3.6 # m/s
PID_BLEED_NEG = 0.5 # m/s^2 per s: a braking integral fades this fast
PID_BLEED_POS = 2.0 # m/s^2 per s: a gas integral (left from the launch) fades this fast
class BrakeHandoverFeedforward:
def __init__(self, dt: float):
self.dt = dt
self.extra = 0.0
def reset(self) -> None:
self.extra = 0.0
def update(self, accel_request: float, v_ego: float, active: bool = True) -> float:
"""returns the extra braking (>= 0, m/s^2) to subtract from the command"""
target = 0.0
if active and accel_request < HANDOVER_MIN_REQUEST:
w = float(np.interp(v_ego, HANDOVER_V_BP, HANDOVER_V_W)) * float(np.interp(accel_request, HANDOVER_REQ_BP, HANDOVER_REQ_W))
target = w * min(HANDOVER_EXTRA_MAX, HANDOVER_EXTRA_FRAC * -accel_request)
target += float(np.interp(v_ego, LOW_EXTRA_V_BP, LOW_EXTRA)) * float(np.interp(accel_request, HANDOVER_REQ_BP, HANDOVER_REQ_W))
step = HANDOVER_RATE * self.dt
self.extra = float(np.clip(target, self.extra - step, self.extra + step))
return self.extra
def apply(self, accel_cmd: float) -> float:
return accel_cmd - self.extra
def condition_pid(self, pid, accel_request: float, v_ego: float) -> bool:
"""below PID_HOLD_V while braking: bleed the integrator toward zero; returns True = freeze it this step"""
if v_ego >= PID_HOLD_V or accel_request >= 0.0:
return False
if pid.i > 0.0:
pid.i = max(0.0, pid.i - PID_BLEED_POS * self.dt)
else:
pid.i = min(0.0, pid.i + PID_BLEED_NEG * self.dt)
return True
class BrakeCommandCorrections:
"""tnpb2 corrections to the final braking command."""
def __init__(self, dt: float):
self.overshoot = BrakeOvershootLimiter(dt, moderate=OVERSHOOT_MOD_ENABLED)
self.handover = BrakeHandoverFeedforward(dt)
def reset(self) -> None:
self.overshoot.reset()
self.handover.reset()
def apply(self, accel_cmd: float, accel_request: float, a_ego_future: float, v_ego: float, stopping: bool, fcw: bool,
a_ego: float | None = None, relaxed: bool = True) -> float:
# take back braking the car delivers beyond a hard request (never under FCW); the moderate band only in Relaxed
self.overshoot.update(accel_request, a_ego_future, active=not stopping and not fcw, v_ego=v_ego, a_ego=a_ego,
moderate_allowed=relaxed)
accel_cmd = self.overshoot.apply(accel_cmd)
# feed-forward the braking the car does not deliver in the low-speed regen hand-over
self.handover.update(accel_request, v_ego, active=not fcw)
return self.handover.apply(accel_cmd)
def condition_pid(self, pid, accel_request: float, v_ego: float) -> bool:
"""called before the stock PID update; True = freeze the integrator this step"""
return self.handover.condition_pid(pid, accel_request, v_ego)
@@ -11,11 +11,8 @@ from opendbc.car import Bus, structs
from opendbc.car.carlog import carlog
from opendbc.can.parser import CANParser
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.toyota.values import ToyotaFlags
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
ENGINE_RUNNING_RPM = 300.0
TRAFFIC_SIGNAL_MAP = {
1: "kph",
36: "mph",
@@ -171,7 +168,3 @@ class CarStateExt:
# Update traffic signals and speed limit
self.update_traffic_signals(cp_cam)
ret_sp.speedLimit = self.calculate_speed_limit()
if self.CP.flags & ToyotaFlags.HYBRID and "ENGINE_RPM" in cp.vl:
ret_sp.engineRpm = float(cp.vl["ENGINE_RPM"]["RPM"])
ret_sp.engineOff = ret_sp.engineRpm < ENGINE_RUNNING_RPM and not bool(cp.vl["ENGINE_RPM"]["ENGINE_RUNNING"])
@@ -1,126 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from opendbc.car import structs
from opendbc.car.can_definitions import CanData
from opendbc.car.toyota import toyotacan
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
LEFT_BLINDSPOT = b"\x41"
RIGHT_BLINDSPOT = b"\x42"
LEFT_SIDE = LEFT_BLINDSPOT[0]
RIGHT_SIDE = RIGHT_BLINDSPOT[0]
# BLINDSPOTD1/D2 aren't real distances despite the DBC name: verified against 3 real routes, values
# fall into 3 clean bands - idle (0), a small transitional-noise band (seen 1-31, rare: single-digit
# occurrence counts, present during side/state transitions), and two real zone codes (~46-47 and
# ~50-53, consistent across routes and cars - likely mirroring stock BSM's own ADJACENT/APPROACHING
# split). A plain nonzero check lets the noise band through as false "occupied" hits; gate on the
# real-zone floor instead - comfortably above every noise value seen, comfortably below neither zone code.
BLINDSPOT_NOISE_FLOOR = 35
# DEBUG also carries a 1-bit BLINDSPOT flag (byte 4) that isn't read here. Checked against the same
# route: stayed 0 across all frames, including every confirmed real detection - not a usable signal.
class EnhancedBsm:
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP):
self.CP = CP
self.CP_SP = CP_SP
@property
def enabled(self):
return bool(self.CP_SP.flags & ToyotaFlagsSP.SP_ENHANCED_BSM)
class _BsmSideState:
def __init__(self):
self.blindspot = False
self.counter = 0
def update(self, distance_1, distance_2):
# every fresh matching-side reading directly reflects the current occupancy check - this is not
# a latch. A drop to an idle/noise reading on this exact side clears it immediately, same as the
# original. The counter is purely a silence timeout for when this side stops responding entirely,
# not a "hold the last detection for a while" mechanism.
self.blindspot = distance_1 > BLINDSPOT_NOISE_FLOOR or distance_2 > BLINDSPOT_NOISE_FLOOR
self.counter = 100
def decay(self):
self.counter = max(0, self.counter - 1)
if self.counter == 0:
self.blindspot = False
# Enhanced BSM (@arne182, @rav4kumar)
class EnhancedBsmCarState(EnhancedBsm):
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP):
super().__init__(CP, CP_SP)
self._sides = {LEFT_SIDE: _BsmSideState(), RIGHT_SIDE: _BsmSideState()}
def update(self, cp, frame: int) -> tuple[bool, bool]:
# Let's keep all the commented out code for easy debug purposes in the future.
distance_1 = cp.vl["DEBUG"].get('BLINDSPOTD1')
distance_2 = cp.vl["DEBUG"].get('BLINDSPOTD2')
side = cp.vl["DEBUG"].get('BLINDSPOTSIDE')
if all(val is not None for val in [distance_1, distance_2, side]) and side in self._sides:
self._sides[side].update(distance_1, distance_2)
for side_state in self._sides.values():
side_state.decay()
return self._sides[LEFT_SIDE].blindspot, self._sides[RIGHT_SIDE].blindspot
class _BsmSideController:
def __init__(self, addr_byte: bytes):
self.addr_byte = addr_byte
self.debug_enabled = False
self.last_poll_frame = 0
def update(self, frame: int, poll_phase: int, e_bsm_rate: int, always_on: bool, vego_ok: bool) -> list[CanData]:
can_sends = []
if not self.debug_enabled:
if always_on or vego_ok: # eagle eye camera will stop working if bsm is switched on under 6m/s
can_sends.append(toyotacan.create_set_bsm_debug_mode(self.addr_byte, True))
self.debug_enabled = True
self.last_poll_frame = frame # give the poll loop a fresh baseline so the stale-poll disable check below can't fire before the first real poll
else:
# no periodic re-assert: re-sending DiagnosticSessionControl(extendedSession) while already in that
# session appears to make the ECU intermittently drop its own detection state (confirmed against a
# real route - two reasserts landed inside an 8s window where the ECU went silent on that side).
# send it once and leave it alone, matching the original, proven-stable behavior.
if not always_on and frame - self.last_poll_frame > 50:
can_sends.append(toyotacan.create_set_bsm_debug_mode(self.addr_byte, False))
self.debug_enabled = False
if frame % e_bsm_rate == poll_phase:
can_sends.append(toyotacan.create_bsm_polling_status(self.addr_byte))
self.last_poll_frame = frame
return can_sends
# Enhanced BSM (@arne182, @rav4kumar)
class EnhancedBsmCarController(EnhancedBsm):
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP):
super().__init__(CP, CP_SP)
self._left = _BsmSideController(LEFT_BLINDSPOT)
self._right = _BsmSideController(RIGHT_BLINDSPOT)
def update(self, CS: structs.CarState, frame: int, e_bsm_rate: int = 20, always_on: bool = True) -> list[CanData]:
if frame <= 200:
return []
vego_ok = CS.out.vEgo > 6
can_sends = self._left.update(frame, 0, e_bsm_rate, always_on, vego_ok)
can_sends += self._right.update(frame, e_bsm_rate // 2, e_bsm_rate, always_on, vego_ok)
return can_sends
@@ -1,231 +0,0 @@
import unittest
from unittest.mock import patch
from opendbc.car import structs
from opendbc.sunnypilot.car.toyota.auto_brake_hold import AutoBrakeHoldCarController, BRAKE_HOLD_ALLOWED_TIMER, pcs_is_active
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
GearShifter = structs.CarState.GearShifter
def make_car_params_sp(enabled: bool = True) -> structs.CarParamsSP:
cp_sp = structs.CarParamsSP()
cp_sp.flags = ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD if enabled else 0
return cp_sp
class FakeCarState:
def __init__(self, standstill=True, cruise_enabled=False, cruise_available=True, gas_pressed=False,
gear=GearShifter.drive, brake_pressed=False, pre_collision_2=None):
self.out = structs.CarState()
self.out.standstill = standstill
self.out.cruiseState.enabled = cruise_enabled
self.out.cruiseState.available = cruise_available
self.out.gasPressed = gas_pressed
self.out.gearShifter = gear
self.out.brakePressed = brake_pressed
self.pre_collision_2 = pre_collision_2 if pre_collision_2 is not None else {}
@patch("opendbc.sunnypilot.car.toyota.auto_brake_hold.toyotacan.create_brake_hold_command")
class TestAutoBrakeHoldCarController(unittest.TestCase):
def _make(self):
return AutoBrakeHoldCarController(structs.CarParams(), make_car_params_sp())
def test_enabled_reflects_flag(self, mock_create):
for value in range(256):
with self.subTest(flags=value):
cp_sp = structs.CarParamsSP()
cp_sp.flags = value
ctrl = AutoBrakeHoldCarController(structs.CarParams(), cp_sp)
self.assertEqual(ctrl.enabled, bool(value & ToyotaFlagsSP.SP_AUTO_BRAKE_HOLD))
def test_does_not_engage_before_timer(self, mock_create):
ctrl = self._make()
cs = FakeCarState(brake_pressed=False)
for i in range(BRAKE_HOLD_ALLOWED_TIMER):
ctrl.update(cs, i, None)
self.assertFalse(ctrl.active)
def test_engages_after_timer_once_brake_released(self, mock_create):
ctrl = self._make()
cs = FakeCarState(brake_pressed=False)
for i in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, i, None)
self.assertTrue(ctrl.active)
def test_brake_still_down_from_the_stop_does_not_block_engagement(self, mock_create):
# the driver's foot is normally still on the brake on the very frame standstill is reached -
# that must not count as a "fresh press" release, or the feature could never engage
ctrl = self._make()
# decelerating into the stop with the brake held continuously
cs = FakeCarState(standstill=False, brake_pressed=True)
for i in range(30):
ctrl.update(cs, i, None)
# reaches standstill, foot stays down for a while, then lifts
cs.out.standstill = True
for i in range(30, 50):
ctrl.update(cs, i, None)
self.assertFalse(ctrl.active, "must not engage while the original stopping press is still held")
cs.out.brakePressed = False
for i in range(50, 50 + BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, i, None)
self.assertTrue(ctrl.active, "should engage once the stopping press is released and the timer elapses")
def test_fresh_brake_press_mid_hold_releases_and_stays_released(self, mock_create):
ctrl = self._make()
cs = FakeCarState(brake_pressed=False)
frame = 0
for _ in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, frame, None)
frame += 1
self.assertTrue(ctrl.active)
cs.out.brakePressed = True
ctrl.update(cs, frame, None)
frame += 1
self.assertFalse(ctrl.active, "a fresh press should release immediately")
for _ in range(20):
ctrl.update(cs, frame, None)
frame += 1
self.assertFalse(ctrl.active, "must stay released for the rest of the episode while continuously held")
cs.out.brakePressed = False
for _ in range(20):
ctrl.update(cs, frame, None)
frame += 1
self.assertFalse(ctrl.active, "must stay released for the rest of the episode even after lifting off again")
def test_drive_off_and_restop_rearms(self, mock_create):
ctrl = self._make()
cs = FakeCarState(brake_pressed=False)
frame = 0
for _ in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, frame, None)
frame += 1
cs.out.brakePressed = True
ctrl.update(cs, frame, None)
frame += 1
self.assertFalse(ctrl.active)
# drives off - leaves the standstill episode entirely
cs.out.standstill = False
ctrl.update(cs, frame, None)
frame += 1
# stops again
cs.out.standstill = True
cs.out.brakePressed = False
for _ in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, frame, None)
frame += 1
self.assertTrue(ctrl.active, "should re-arm and engage again at the next stop")
def test_cruise_engaged_blocks_hold(self, mock_create):
ctrl = self._make()
cs = FakeCarState(cruise_enabled=True, brake_pressed=False)
for i in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, i, None)
self.assertFalse(ctrl.active, "must not hold while ACC is engaged - that's the point of the constraint")
def test_gas_pressed_blocks_hold(self, mock_create):
ctrl = self._make()
cs = FakeCarState(gas_pressed=True, brake_pressed=False)
for i in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, i, None)
self.assertFalse(ctrl.active)
def test_park_and_reverse_block_hold(self, mock_create):
for gear in (GearShifter.park, GearShifter.reverse):
with self.subTest(gear=gear):
ctrl = self._make()
cs = FakeCarState(gear=gear, brake_pressed=False)
for i in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, i, None)
self.assertFalse(ctrl.active)
def test_yields_to_live_pcs_without_dropping_active_state(self, mock_create):
ctrl = self._make()
cs = FakeCarState(brake_pressed=False)
frame = 0
for _ in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, frame, None)
frame += 1
self.assertTrue(ctrl.active)
mock_create.reset_mock()
# a real PCS event shows up on the live signal
cs.pre_collision_2 = {"PCSALM": 1}
# advance to the next even frame the message is actually built on
while frame % 2 != 0:
frame += 1
ctrl.update(cs, frame, None)
override_arg = mock_create.call_args.args[-1]
self.assertTrue(ctrl.active, "internal hold state should not be cleared by a live PCS event")
self.assertFalse(override_arg, "must not override PRE_COLLISION_2 while PCS is genuinely active")
# once PCS goes quiet again, override resumes on our own signal, not stale PCS state
frame += 2
cs.pre_collision_2 = {}
ctrl.update(cs, frame, None)
override_arg = mock_create.call_args.args[-1]
self.assertTrue(override_arg)
def test_message_only_built_every_other_frame(self, mock_create):
ctrl = self._make()
cs = FakeCarState(brake_pressed=False)
for i in range(10):
ctrl.update(cs, i, None)
self.assertEqual(mock_create.call_count, 5)
def test_no_message_sent_outside_relay_blocked_window(self, mock_create):
# outside relay_blocked, toyota_fwd_hook lets the real PRE_COLLISION_2 relay through on its own -
# our passthrough copy would just be redundant traffic panda rejects, so it must not be sent at all
cases = [
dict(cruise_enabled=True),
dict(gas_pressed=True),
dict(cruise_available=False),
]
for kwargs in cases:
with self.subTest(kwargs=kwargs):
ctrl = self._make()
cs = FakeCarState(brake_pressed=False, **kwargs)
for i in range(10):
ctrl.update(cs, i, None)
mock_create.assert_not_called()
def test_message_still_sent_in_park_or_reverse(self, mock_create):
# relay_blocked has no gear check, matching toyota_fwd_hook - park/reverse must not open a gap
# in the PRE_COLLISION_2 relay even though hold can never engage there (this was a real
# regression: gating the send on hold_allowed's gear check left bus 0 with nothing at this
# address while parked, which is what tripped a genuine PCS dash fault on-road)
for gear in (GearShifter.park, GearShifter.reverse):
with self.subTest(gear=gear):
ctrl = self._make()
cs = FakeCarState(gear=gear, brake_pressed=False)
for i in range(BRAKE_HOLD_ALLOWED_TIMER + 1):
ctrl.update(cs, i, None)
self.assertFalse(ctrl.active, "must never actually hold in park/reverse")
self.assertGreater(mock_create.call_count, 0, "must still relay PRE_COLLISION_2 in park/reverse")
override_arg = mock_create.call_args.args[-1]
self.assertFalse(override_arg, "never override while gear disallows an actual hold")
class TestPcsIsActive(unittest.TestCase):
def test_all_zero_is_not_active(self):
self.assertFalse(pcs_is_active({}))
self.assertFalse(pcs_is_active({"PCSALM": 0, "DSS1GDRV": 0}))
def test_any_trigger_field_is_active(self):
for field in ("PCSALM", "IBTRGR", "PBATRGR", "PREFILL", "AVSTRGR", "PBRTRGR", "PPTRGR"):
with self.subTest(field=field):
self.assertTrue(pcs_is_active({field: 1}))
def test_nonzero_force_signal_is_active(self):
self.assertTrue(pcs_is_active({"DSS1GDRV": -5}))
self.assertFalse(pcs_is_active({"DSS1GDRV": 0}))
if __name__ == "__main__":
unittest.main()
@@ -1,171 +0,0 @@
import numpy as np
from opendbc.car import rate_limit, DT_CTRL
from opendbc.sunnypilot.car.toyota.brake_onset import BrakeOnsetShaper, ONSET_J_DOWN, HARD_BRAKE_ACCEL
DT = DT_CTRL * 3
STOCK_J = 4.0
UP_STEP = 4.0 * DT
def run(requests, a0=0.0, fcw=False):
shaper = BrakeOnsetShaper(DT, STOCK_J)
a = a0
out = []
for req in requests:
step = shaper.down_step(req, a, bypass=shaper.is_urgent(req, fcw))
a = rate_limit(req, a, step, UP_STEP)
out.append(a)
return np.array(out)
def at(out, t):
return out[int(round(t / DT)) - 1]
class TestBrakeOnset:
def test_step_brake_eases_in_over_the_first_second(self):
out = run([-1.5] * 60)
assert at(out, 0.09) > -0.06 # first 0.15 s: barely anything
assert at(out, 0.21) > -0.12 # 0.2 s: still very light
assert -0.45 < at(out, 0.36) < -0.10 # ~0.35 s: building slowly
assert -0.60 < at(out, 0.60) < -0.15 # 0.6 s: still gentle, about a fifth of the way
assert -1.10 < at(out, 1.00) < -0.55 # 1.0 s: about half way, the ramp is now running up
assert np.isclose(at(out, 1.5), -1.5, atol=1e-6) # settled once the ramp reaches the stock limit
def test_jerk_follows_the_schedule_and_never_exceeds_stock(self):
out = run([-1.9] * 60, a0=1.0) # large step, still above the hard-brake bypass
jerks = np.diff(np.concatenate([[1.0], out])) / DT
assert jerks.min() >= -STOCK_J - 1e-6
assert jerks[0] >= -ONSET_J_DOWN[0] - 1e-6
assert jerks[int(0.09 / DT)] >= -ONSET_J_DOWN[1] - 1e-6
# the blend is one straight ramp: jerk keeps growing monotonically until the stock limit
ramp = -jerks[int(0.1 / DT):int(0.6 / DT)]
assert np.all(np.diff(ramp) >= -1e-6)
def test_lead_switch_during_a_settled_brake_gets_the_same_soft_onset(self):
reqs = [-0.6] * 70 + [-1.8] * 60 # long enough for the schedule to wind fully back before the switch
out = run(reqs)
t_switch = 70 * DT
assert np.isclose(at(out, t_switch), -0.6, atol=1e-6)
assert at(out, t_switch + 0.09) > -0.6 - 0.05
assert at(out, t_switch + 0.21) > -0.6 - 0.25
assert np.isclose(out[-1], -1.8, atol=1e-6)
def test_hard_brake_request_bypasses_the_schedule(self):
out = run([HARD_BRAKE_ACCEL - 0.5] * 20)
assert np.isclose(out[0], -STOCK_J * DT, atol=1e-6)
assert at(out, 0.3) <= -1.1
def test_fcw_bypasses_the_schedule(self):
out = run([-1.5] * 20, fcw=True)
assert np.isclose(out[0], -STOCK_J * DT, atol=1e-6)
def test_release_is_untouched(self):
out = run([1.0] * 20, a0=-1.5)
assert np.isclose(out[0], -1.5 + UP_STEP, atol=1e-6)
def test_gentle_request_passes_straight_through(self):
reqs = list(np.linspace(0.0, -0.2, 40)) # ~0.17 m/s^3, below the gentlest onset limit (0.25)
out = run(reqs)
assert np.allclose(out, reqs, atol=1e-9)
def test_short_pause_does_not_fully_rearm(self):
shaper = BrakeOnsetShaper(DT, STOCK_J)
a = 0.0
for _ in range(int(0.3 / DT)):
a = rate_limit(-1.5, a, shaper.down_step(-1.5, a), UP_STEP)
t_after_ramp = shaper.t_onset
for _ in range(2): # hold for two frames, request steady
shaper.down_step(a, a)
assert 0.0 < shaper.t_onset < t_after_ramp
for _ in range(int(1.0 / DT)): # a full second of settled brake winds it back completely
shaper.down_step(a, a)
assert shaper.t_onset == 0.0
class TestEngageBrake:
"""2026-10-04: SET- while creeping up to a lead: the first 0.2 s after engaging is a light touch."""
def test_first_0_2_s_after_engaging_is_light_then_blends_to_stock(self):
from opendbc.sunnypilot.car.toyota.brake_onset import BrakeOnsetShaper, EngageOnsetShaper, ENGAGE_BRAKE_T_BP
dt = 0.03
shaper, engage = BrakeOnsetShaper(dt, 4.0), EngageOnsetShaper(dt, 4.0)
a, t, trace = 0.0, 0.0, {}
while t < 1.6:
engage.up_step(True)
step = shaper.down_step(-1.65, a, v_ego=2.5, t_engaged=engage.t_since_engage)
a = max(-1.65, a + step)
t += dt
for mark in (0.2, 0.5, 1.5):
if mark not in trace and t >= mark:
trace[mark] = a
assert trace[0.2] > -0.12 # light first touch (at creep speed the creep onset schedule is the stricter one)
assert -1.0 < trace[0.5] < -0.15 # blending in
assert trace[1.5] < -1.4 # the planner's request has arrived (end jerk 2.0 m/s^3 below 60 km/h)
assert ENGAGE_BRAKE_T_BP[-1] <= 1.0
class TestCreepOnset:
"""2026-10-04: a brake onset at creep speed (re-stop behind a lead) is as light as the engage schedule."""
def test_below_10kph_first_0_2_s_is_light(self):
from opendbc.sunnypilot.car.toyota.brake_onset import BrakeOnsetShaper
dt = 0.03
for v, light_limit in ((1.0, -0.15), (30.0, -0.45)): # creep: barely anything by 0.2 s; highway: the normal onset
shaper = BrakeOnsetShaper(dt, 4.0)
a, t, at_02 = 0.0, 0.0, None
while t < 1.2:
a = max(-1.0, a + shaper.down_step(-1.0, a, v_ego=v))
t += dt
if at_02 is None and t >= 0.2:
at_02 = a
assert at_02 > light_limit, (v, at_02)
assert a < -0.9, (v, a) # the full request arrives within the window at both speeds
class TestCreepGasRelease:
"""route 0000010d 23:56:59: the drop from a +0.5 launch request was shaped like a brake onset"""
def _shaper(self):
from opendbc.sunnypilot.car.toyota.brake_onset import BrakeOnsetShaper
return BrakeOnsetShaper(DT_CTRL * 3, 4.0)
def test_positive_part_goes_at_the_stock_rate_below_8kph(self):
sh = self._shaper()
step = sh.down_step(-0.24, 0.5, v_ego=2.0 / 3.6)
assert abs(step - (-4.0 * DT_CTRL * 3)) < 1e-9
step = sh.down_step(-0.24, 0.05, v_ego=2.0 / 3.6)
assert abs(step - (-0.05)) < 1e-9 # stops at zero; the braking starts from there
def test_faster_keeps_the_soft_onset(self):
step = self._shaper().down_step(-0.24, 0.5, v_ego=20.0 / 3.6)
assert step > -4.0 * DT_CTRL * 3 + 1e-9
class TestResumeSoftStart:
"""RES while coasting at 46 km/h: start from the coasting decel, 0.6 m/s^3 for 0.4 s, stock by 0.8 s"""
def _run(self, a_ego=-0.45, v=46.0, req=0.4, edge=True, n=40):
from opendbc.sunnypilot.car.toyota.brake_onset import EngageOnsetShaper
sh = EngageOnsetShaper(DT, 4.0)
sh.cruise_state(False)
sh.cruise_state(True if edge else False)
if not edge:
for _ in range(30):
sh.cruise_state(True)
prev, out = 0.0, []
for _ in range(n):
prev = sh.start_accel(prev, a_ego, v / 3.6)
prev = rate_limit(req, prev, -4.0 * DT, sh.up_step(True))
out.append(prev)
return np.array(out)
def test_gentle_first_0_4s_then_stock(self):
out = self._run()
assert abs(out[0] - (-0.45 + 0.6 * DT)) < 1e-9 # starts from the coasting decel
assert at(out, 0.39) < -0.45 + 0.6 * 0.4 + 0.02 # 0.6 m/s^3 for the first 0.4 s
assert np.isclose(out[-1], 0.4) # reaches the plan
def test_not_at_low_speed_or_a_gas_override_release(self):
assert self._run(v=10.0)[0] == 4.0 * DT
assert self._run(edge=False)[0] == 4.0 * DT
def test_a_positive_a_ego_starts_from_zero(self):
assert abs(self._run(a_ego=0.3)[0] - 0.6 * DT) < 1e-9
@@ -1,105 +0,0 @@
import unittest
from opendbc.sunnypilot.car.toyota.brake_onset import (BrakeOvershootLimiter, OVERSHOOT_CMD_CEIL, OVERSHOOT_DEADBAND,
OVERSHOOT_MAX, OVERSHOOT_MOD_DEADBAND, OVERSHOOT_MOD_KEEP,
OVERSHOOT_MOD_RATE)
DT = 0.03
class TestBrakeOvershootLimiter(unittest.TestCase):
def run_steady(self, req, a_ego, n=200, active=True):
lim = BrakeOvershootLimiter(DT)
for _ in range(n):
lim.update(req, a_ego, active)
return lim
def test_moderate_braking_is_untouched(self):
self.assertEqual(self.run_steady(-2.0, -3.0).lift, 0.0)
def test_overshoot_inside_the_deadband_is_untouched(self):
self.assertEqual(self.run_steady(-3.4, -3.4 - OVERSHOOT_DEADBAND + 0.01).lift, 0.0)
def test_19_41_overshoot_is_taken_back(self):
lim = self.run_steady(-3.4, -4.3)
self.assertAlmostEqual(lim.lift, 0.8 * (0.9 - OVERSHOOT_DEADBAND), places=6)
self.assertAlmostEqual(lim.apply(-3.4), -3.4 + lim.lift, places=6)
def test_under_braking_never_lifts(self):
self.assertEqual(self.run_steady(-3.4, -2.5).lift, 0.0)
def test_lift_is_capped_and_command_stays_a_firm_brake(self):
lim = self.run_steady(-2.6, -6.0)
self.assertEqual(lim.lift, OVERSHOOT_MAX)
self.assertAlmostEqual(lim.apply(-2.6), -2.6 + OVERSHOOT_MAX)
self.assertEqual(lim.apply(-2.2), OVERSHOOT_CMD_CEIL) # a PID-lightened command is not lifted past a firm brake
def test_lift_grows_and_fades_gradually(self):
lim = BrakeOvershootLimiter(DT)
prev = 0.0
for _ in range(10):
lift = lim.update(-3.4, -5.0)
self.assertLessEqual(lift - prev, 3.0 * DT + 1e-9)
prev = lift
for _ in range(10):
lift = lim.update(-1.0, -1.0) # the hard request ends
self.assertLessEqual(prev - lift, 2.0 * DT + 1e-9)
prev = lift
def test_inactive_gives_it_back(self):
lim = self.run_steady(-3.4, -4.3)
for _ in range(100):
lim.update(-3.4, -4.3, active=False)
self.assertEqual(lim.lift, 0.0)
class TestModerateBand(unittest.TestCase):
"""Altis Hybrid only: route 0000010e 17:38:41, asked -1.60 at 40-50 km/h, the car delivered -1.95."""
def run_steady(self, req, a_ego, v_kph, moderate=True, n=200):
lim = BrakeOvershootLimiter(DT, moderate=moderate)
for _ in range(n):
lim.update(req, a_ego, True, v_ego=v_kph / 3.6)
return lim
def test_17_38_41_overshoot_is_taken_back(self):
lim = self.run_steady(-1.60, -1.95, 45.0)
self.assertAlmostEqual(lim.lift, 0.8 * (0.35 - OVERSHOOT_MOD_DEADBAND), places=4) # low-pass converged
self.assertAlmostEqual(lim.apply(-1.60), -1.60 + lim.lift, places=6)
def test_never_lighter_than_three_quarters_of_the_command(self):
lim = self.run_steady(-1.2, -3.0, 45.0)
self.assertAlmostEqual(lim.apply(-1.2), OVERSHOOT_MOD_KEEP * -1.2, places=6)
def test_off_for_other_cars_low_speed_and_light_requests(self):
self.assertEqual(self.run_steady(-1.60, -1.95, 45.0, moderate=False).lift, 0.0)
self.assertEqual(self.run_steady(-1.60, -1.95, 10.0).lift, 0.0)
self.assertEqual(self.run_steady(-0.9, -1.4, 45.0).lift, 0.0)
self.assertEqual(self.run_steady(-1.60, -1.70, 45.0).lift, 0.0) # inside the deadband
def test_hard_band_unchanged_with_moderate_on(self):
lim = self.run_steady(-3.4, -4.3, 45.0)
self.assertAlmostEqual(lim.lift, 0.8 * (0.9 - OVERSHOOT_DEADBAND), places=6)
self.assertAlmostEqual(lim.apply(-3.4), -3.4 + lim.lift, places=6)
def test_moderate_band_is_a_slow_smooth_trim(self):
lim = BrakeOvershootLimiter(DT, moderate=True)
prev = 0.0
for k in range(100):
a = -1.95 if k % 2 else -1.45 # noisy aEgo around -1.70 at a steady -1.60 request
lift = lim.update(-1.60, a, True, v_ego=45 / 3.6, a_ego=a)
self.assertLessEqual(abs(lift - prev), OVERSHOOT_MOD_RATE * DT + 1e-9)
prev = lift
self.assertEqual(lift, 0.0) # averaged overshoot 0.10 is inside the deadband
def test_moderate_band_only_in_relaxed(self):
lim = BrakeOvershootLimiter(DT, moderate=True)
for _ in range(200):
lim.update(-1.60, -1.95, True, v_ego=45 / 3.6, moderate_allowed=False)
self.assertEqual(lim.lift, 0.0)
for _ in range(200):
lim.update(-3.4, -4.3, True, v_ego=45 / 3.6, moderate_allowed=False) # the hard band in every personality
self.assertGreater(lim.lift, 0.5)
if __name__ == "__main__":
unittest.main()
@@ -1,66 +0,0 @@
import unittest
from types import SimpleNamespace
from opendbc.sunnypilot.car.toyota.brake_onset import (BrakeHandoverFeedforward, HANDOVER_EXTRA_MAX,
HANDOVER_EXTRA_FRAC, HANDOVER_RATE, PID_BLEED_NEG, PID_BLEED_POS)
DT = 0.03
class TestHandoverFeedforward(unittest.TestCase):
def run_steady(self, req, v_kph, n=200):
c = BrakeHandoverFeedforward(DT)
for _ in range(n):
c.update(req, v_kph / 3.6)
return c
def test_full_extra_in_the_dip(self):
c = self.run_steady(-0.84, 12.0) # route 0000010e: asked -0.84 at 12-9 km/h, the car gave -0.61
self.assertAlmostEqual(c.extra, HANDOVER_EXTRA_MAX, places=9)
self.assertAlmostEqual(c.apply(-0.84), -0.84 - HANDOVER_EXTRA_MAX, places=9)
def test_lighter_request_gets_a_quarter(self):
self.assertAlmostEqual(self.run_steady(-0.5, 12.0).extra, HANDOVER_EXTRA_FRAC * 0.5, places=9)
def test_fades_at_the_edges(self):
self.assertAlmostEqual(self.run_steady(-1.0, 8.0).extra, 0.5 * HANDOVER_EXTRA_MAX, places=6)
self.assertEqual(self.run_steady(-1.0, 5.0).extra, 0.0)
self.assertEqual(self.run_steady(-1.0, 20.0).extra, 0.0)
self.assertEqual(self.run_steady(-0.25, 12.0).extra, 0.0)
def test_fades_out_for_a_firm_request(self):
self.assertEqual(self.run_steady(-1.5, 12.0).extra, 0.0) # 0000010d: at -1.46 the car delivered -1.72
self.assertAlmostEqual(self.run_steady(-1.25, 12.0).extra, 0.5 * HANDOVER_EXTRA_MAX, places=6)
def test_gradual(self):
c = BrakeHandoverFeedforward(DT)
prev = 0.0
for _ in range(50):
e = c.update(-1.0, 12 / 3.6)
self.assertLessEqual(e - prev, HANDOVER_RATE * DT + 1e-9)
prev = e
class TestPidHold(unittest.TestCase):
def test_braking_integral_bleeds_and_freezes_below_9kph(self):
c = BrakeHandoverFeedforward(DT)
pid = SimpleNamespace(i=-0.15)
self.assertTrue(c.condition_pid(pid, -0.6, 5 / 3.6))
self.assertAlmostEqual(pid.i, -0.15 + PID_BLEED_NEG * DT, places=9)
for _ in range(100):
c.condition_pid(pid, -0.6, 5 / 3.6)
self.assertEqual(pid.i, 0.0)
def test_gas_integral_left_from_a_launch_bleeds_fast(self):
c = BrakeHandoverFeedforward(DT)
pid = SimpleNamespace(i=0.3)
c.condition_pid(pid, -0.2, 3 / 3.6)
self.assertAlmostEqual(pid.i, 0.3 - PID_BLEED_POS * DT, places=9)
def test_untouched_above_9kph_or_when_not_braking(self):
c = BrakeHandoverFeedforward(DT)
pid = SimpleNamespace(i=-0.15)
self.assertFalse(c.condition_pid(pid, -0.6, 12 / 3.6))
self.assertFalse(c.condition_pid(pid, 0.3, 3 / 3.6))
self.assertEqual(pid.i, -0.15)
if __name__ == "__main__":
unittest.main()
@@ -1,15 +0,0 @@
import unittest
from opendbc.car.toyota.carstate import AccelPersonality, get_accel_personality
class TestDriveMode(unittest.TestCase):
def test_acceleration_profile_mapping(self):
self.assertEqual(get_accel_personality(0, 0), AccelPersonality.normal)
self.assertEqual(get_accel_personality(0, 1), AccelPersonality.eco)
self.assertEqual(get_accel_personality(1, 0), AccelPersonality.sport)
self.assertEqual(get_accel_personality(1, 1), AccelPersonality.sport)
if __name__ == "__main__":
unittest.main()
@@ -1,57 +0,0 @@
import unittest
from types import SimpleNamespace
from opendbc.car import structs
from opendbc.car.toyota.carcontroller import get_long_tune
from opendbc.car.toyota.values import CAR, ToyotaFlags
from opendbc.sunnypilot.car.interfaces import _initialize_toyota
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
def build_params(*, tss2=True):
flags = ToyotaFlags.TSS2.value if tss2 else 0
CP = structs.CarParams(
brand="toyota",
carFingerprint=str(CAR.TOYOTA_COROLLA_TSS2),
flags=flags,
)
return CP, structs.CarParamsSP()
class TestTss2LongTuning(unittest.TestCase):
def test_param_handoff(self):
for enabled in (False, True):
with self.subTest(enabled=enabled):
CP, CP_SP = build_params()
_initialize_toyota(CP, CP_SP, {"ToyotaTSS2Long": int(enabled)})
self.assertEqual(bool(CP_SP.flags & ToyotaFlagsSP.TSS2_LONG_TUNING), enabled)
def test_tss2_tune_selection(self):
controller_params = SimpleNamespace(ACCEL_MAX=2.0, ACCEL_MIN=-3.5)
CP, CP_SP = build_params()
stock_tune = get_long_tune(CP, CP_SP, controller_params)
stock_tune.speed = 2.0
self.assertEqual(stock_tune.k_i, 0.5)
stock_tune.speed = 5.0
self.assertEqual(stock_tune.k_i, 0.25)
CP_SP.flags |= ToyotaFlagsSP.TSS2_LONG_TUNING.value
custom_tune = get_long_tune(CP, CP_SP, controller_params)
custom_tune.speed = 0.0
self.assertEqual(custom_tune.k_i, 0.30)
custom_tune.speed = 5.0
self.assertEqual(custom_tune.k_i, 0.28)
def test_non_tss2_ignores_custom_tune_flag(self):
controller_params = SimpleNamespace(ACCEL_MAX=2.0, ACCEL_MIN=-3.5)
CP, CP_SP = build_params(tss2=False)
CP_SP.flags |= ToyotaFlagsSP.TSS2_LONG_TUNING.value
standard_tune = get_long_tune(CP, CP_SP, controller_params)
standard_tune.speed = 0.0
self.assertEqual(standard_tune.k_i, 3.6)
if __name__ == "__main__":
unittest.main()
@@ -14,11 +14,6 @@ class ToyotaFlagsSP(IntFlag):
ZSS = 4
STOCK_LONGITUDINAL = 8
STOP_AND_GO_HACK = 16
SP_ENHANCED_BSM = 32
SP_NEED_DEBUG_BSM = 64
SP_AUTO_BRAKE_HOLD = 128
SP_BRAKE_ONSET = 256
TSS2_LONG_TUNING = 512
class ToyotaSafetyFlagsSP:
-18
View File
@@ -204,16 +204,11 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
aTarget @5 :Float32;
events @6 :List(OnroadEventSP.Event);
e2eAlerts @7 :E2eAlerts;
accelController @8 :AccelController;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
enabled @1 :Bool;
active @2 :Bool;
decelIntent @3 :Float32;
curveDetected @4 :Bool;
wantBlended @5 :Bool;
leadVeto @6 :Bool;
enum DynamicExperimentalControlState {
acc @0;
@@ -311,17 +306,6 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
greenLightAlert @0 :Bool;
leadDepartAlert @1 :Bool;
}
struct AccelController {
enabled @0 :Bool;
active @1 :Bool;
profile @2 :Profile;
enum Profile {
eco @0;
normal @1;
sport @2;
}
}
}
struct OnroadEventSP @0xda96579883444c35 {
@@ -463,8 +447,6 @@ struct BackupManagerSP @0xf98d843bfd7004a3 {
struct CarStateSP @0xb86e6369214c01c8 {
speedLimit @0 :Float32;
engineOff @1 :Bool;
engineRpm @2 :Float32;
}
struct LiveMapDataSP @0xf416ec09499d9d19 {
+66 -321
View File
@@ -1897,36 +1897,34 @@ const ::capnp::_::RawSchema s_e60821c0505ad473 = {
4, 12, i_e60821c0505ad473, nullptr, nullptr, { &s_e60821c0505ad473, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
static const ::capnp::_::AlignedData<192> b_f35cc4560bbf6ec2 = {
static const ::capnp::_::AlignedData<172> b_f35cc4560bbf6ec2 = {
{ 0, 0, 0, 0, 5, 0, 6, 0,
194, 110, 191, 11, 86, 196, 92, 243,
13, 0, 0, 0, 1, 0, 2, 0,
89, 10, 85, 29, 102, 186, 38, 181,
6, 0, 7, 0, 0, 0, 0, 0,
5, 0, 7, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
21, 0, 0, 0, 2, 1, 0, 0,
33, 0, 0, 0, 103, 0, 0, 0,
33, 0, 0, 0, 87, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
141, 0, 0, 0, 255, 1, 0, 0,
125, 0, 0, 0, 199, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
99, 117, 115, 116, 111, 109, 46, 99,
97, 112, 110, 112, 58, 76, 111, 110,
103, 105, 116, 117, 100, 105, 110, 97,
108, 80, 108, 97, 110, 83, 80, 0,
24, 0, 0, 0, 1, 0, 1, 0,
20, 0, 0, 0, 1, 0, 1, 0,
119, 191, 14, 16, 174, 91, 106, 188,
41, 0, 0, 0, 218, 0, 0, 0,
33, 0, 0, 0, 218, 0, 0, 0,
163, 23, 92, 92, 71, 153, 141, 193,
49, 0, 0, 0, 154, 0, 0, 0,
41, 0, 0, 0, 154, 0, 0, 0,
187, 34, 168, 255, 81, 184, 158, 154,
53, 0, 0, 0, 90, 0, 0, 0,
45, 0, 0, 0, 90, 0, 0, 0,
196, 110, 217, 86, 5, 68, 71, 173,
53, 0, 0, 0, 186, 0, 0, 0,
45, 0, 0, 0, 186, 0, 0, 0,
254, 40, 174, 34, 232, 188, 103, 165,
57, 0, 0, 0, 82, 0, 0, 0,
161, 196, 243, 6, 208, 214, 220, 136,
57, 0, 0, 0, 130, 0, 0, 0,
49, 0, 0, 0, 82, 0, 0, 0,
68, 121, 110, 97, 109, 105, 99, 69,
120, 112, 101, 114, 105, 109, 101, 110,
116, 97, 108, 67, 111, 110, 116, 114,
@@ -1941,72 +1939,63 @@ static const ::capnp::_::AlignedData<192> b_f35cc4560bbf6ec2 = {
83, 111, 117, 114, 99, 101, 0, 0,
69, 50, 101, 65, 108, 101, 114, 116,
115, 0, 0, 0, 0, 0, 0, 0,
65, 99, 99, 101, 108, 67, 111, 110,
116, 114, 111, 108, 108, 101, 114, 0,
36, 0, 0, 0, 3, 0, 4, 0,
32, 0, 0, 0, 3, 0, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
237, 0, 0, 0, 34, 0, 0, 0,
209, 0, 0, 0, 34, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
232, 0, 0, 0, 3, 0, 1, 0,
244, 0, 0, 0, 2, 0, 1, 0,
204, 0, 0, 0, 3, 0, 1, 0,
216, 0, 0, 0, 2, 0, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
241, 0, 0, 0, 186, 0, 0, 0,
213, 0, 0, 0, 186, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
244, 0, 0, 0, 3, 0, 1, 0,
0, 1, 0, 0, 2, 0, 1, 0,
216, 0, 0, 0, 3, 0, 1, 0,
228, 0, 0, 0, 2, 0, 1, 0,
2, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
253, 0, 0, 0, 154, 0, 0, 0,
225, 0, 0, 0, 154, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 3, 0, 1, 0,
12, 1, 0, 0, 2, 0, 1, 0,
228, 0, 0, 0, 3, 0, 1, 0,
240, 0, 0, 0, 2, 0, 1, 0,
3, 0, 0, 0, 2, 0, 0, 0,
0, 0, 1, 0, 3, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
9, 1, 0, 0, 90, 0, 0, 0,
237, 0, 0, 0, 90, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
8, 1, 0, 0, 3, 0, 1, 0,
20, 1, 0, 0, 2, 0, 1, 0,
236, 0, 0, 0, 3, 0, 1, 0,
248, 0, 0, 0, 2, 0, 1, 0,
4, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 4, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
17, 1, 0, 0, 66, 0, 0, 0,
245, 0, 0, 0, 66, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
12, 1, 0, 0, 3, 0, 1, 0,
24, 1, 0, 0, 2, 0, 1, 0,
240, 0, 0, 0, 3, 0, 1, 0,
252, 0, 0, 0, 2, 0, 1, 0,
5, 0, 0, 0, 2, 0, 0, 0,
0, 0, 1, 0, 5, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
21, 1, 0, 0, 66, 0, 0, 0,
249, 0, 0, 0, 66, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
16, 1, 0, 0, 3, 0, 1, 0,
28, 1, 0, 0, 2, 0, 1, 0,
244, 0, 0, 0, 3, 0, 1, 0,
0, 1, 0, 0, 2, 0, 1, 0,
6, 0, 0, 0, 3, 0, 0, 0,
0, 0, 1, 0, 6, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
25, 1, 0, 0, 58, 0, 0, 0,
253, 0, 0, 0, 58, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
20, 1, 0, 0, 3, 0, 1, 0,
48, 1, 0, 0, 2, 0, 1, 0,
248, 0, 0, 0, 3, 0, 1, 0,
20, 1, 0, 0, 2, 0, 1, 0,
7, 0, 0, 0, 4, 0, 0, 0,
0, 0, 1, 0, 7, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
45, 1, 0, 0, 82, 0, 0, 0,
17, 1, 0, 0, 82, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
44, 1, 0, 0, 3, 0, 1, 0,
56, 1, 0, 0, 2, 0, 1, 0,
8, 0, 0, 0, 5, 0, 0, 0,
0, 0, 1, 0, 8, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
53, 1, 0, 0, 130, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
52, 1, 0, 0, 3, 0, 1, 0,
64, 1, 0, 0, 2, 0, 1, 0,
16, 1, 0, 0, 3, 0, 1, 0,
28, 1, 0, 0, 2, 0, 1, 0,
100, 101, 99, 0, 0, 0, 0, 0,
16, 0, 0, 0, 0, 0, 0, 0,
119, 191, 14, 16, 174, 91, 106, 188,
@@ -2078,15 +2067,6 @@ static const ::capnp::_::AlignedData<192> b_f35cc4560bbf6ec2 = {
254, 40, 174, 34, 232, 188, 103, 165,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
16, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
97, 99, 99, 101, 108, 67, 111, 110,
116, 114, 111, 108, 108, 101, 114, 0,
16, 0, 0, 0, 0, 0, 0, 0,
161, 196, 243, 6, 208, 214, 220, 136,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
16, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, }
@@ -2094,7 +2074,6 @@ static const ::capnp::_::AlignedData<192> b_f35cc4560bbf6ec2 = {
::capnp::word const* const bp_f35cc4560bbf6ec2 = b_f35cc4560bbf6ec2.words;
#if !CAPNP_LITE
static const ::capnp::_::RawSchema* const d_f35cc4560bbf6ec2[] = {
&s_88dcd6d006f3c4a1,
&s_9a9eb851ffa822bb,
&s_a567bce822ae28fe,
&s_ad47440556d96ec4,
@@ -2102,14 +2081,14 @@ static const ::capnp::_::RawSchema* const d_f35cc4560bbf6ec2[] = {
&s_c18d99475c5c17a3,
&s_f6e831752fcdf793,
};
static const uint16_t m_f35cc4560bbf6ec2[] = {5, 8, 0, 7, 6, 1, 2, 3, 4};
static const uint16_t i_f35cc4560bbf6ec2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
static const uint16_t m_f35cc4560bbf6ec2[] = {5, 0, 7, 6, 1, 2, 3, 4};
static const uint16_t i_f35cc4560bbf6ec2[] = {0, 1, 2, 3, 4, 5, 6, 7};
const ::capnp::_::RawSchema s_f35cc4560bbf6ec2 = {
0xf35cc4560bbf6ec2, b_f35cc4560bbf6ec2.words, 192, d_f35cc4560bbf6ec2, m_f35cc4560bbf6ec2,
7, 9, i_f35cc4560bbf6ec2, nullptr, nullptr, { &s_f35cc4560bbf6ec2, nullptr, nullptr, 0, 0, nullptr }, false
0xf35cc4560bbf6ec2, b_f35cc4560bbf6ec2.words, 172, d_f35cc4560bbf6ec2, m_f35cc4560bbf6ec2,
6, 8, i_f35cc4560bbf6ec2, nullptr, nullptr, { &s_f35cc4560bbf6ec2, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
static const ::capnp::_::AlignedData<137> b_bc6a5bae100ebf77 = {
static const ::capnp::_::AlignedData<73> b_bc6a5bae100ebf77 = {
{ 0, 0, 0, 0, 5, 0, 6, 0,
119, 191, 14, 16, 174, 91, 106, 188,
32, 0, 0, 0, 1, 0, 1, 0,
@@ -2119,7 +2098,7 @@ static const ::capnp::_::AlignedData<137> b_bc6a5bae100ebf77 = {
21, 0, 0, 0, 218, 1, 0, 0,
49, 0, 0, 0, 23, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
69, 0, 0, 0, 143, 1, 0, 0,
69, 0, 0, 0, 175, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
99, 117, 115, 116, 111, 109, 46, 99,
@@ -2137,56 +2116,28 @@ static const ::capnp::_::AlignedData<137> b_bc6a5bae100ebf77 = {
120, 112, 101, 114, 105, 109, 101, 110,
116, 97, 108, 67, 111, 110, 116, 114,
111, 108, 83, 116, 97, 116, 101, 0,
28, 0, 0, 0, 3, 0, 4, 0,
12, 0, 0, 0, 3, 0, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
181, 0, 0, 0, 50, 0, 0, 0,
69, 0, 0, 0, 50, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
176, 0, 0, 0, 3, 0, 1, 0,
188, 0, 0, 0, 2, 0, 1, 0,
64, 0, 0, 0, 3, 0, 1, 0,
76, 0, 0, 0, 2, 0, 1, 0,
1, 0, 0, 0, 16, 0, 0, 0,
0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
185, 0, 0, 0, 66, 0, 0, 0,
73, 0, 0, 0, 66, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
180, 0, 0, 0, 3, 0, 1, 0,
192, 0, 0, 0, 2, 0, 1, 0,
68, 0, 0, 0, 3, 0, 1, 0,
80, 0, 0, 0, 2, 0, 1, 0,
2, 0, 0, 0, 17, 0, 0, 0,
0, 0, 1, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
189, 0, 0, 0, 58, 0, 0, 0,
77, 0, 0, 0, 58, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
184, 0, 0, 0, 3, 0, 1, 0,
196, 0, 0, 0, 2, 0, 1, 0,
3, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 3, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
193, 0, 0, 0, 98, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
192, 0, 0, 0, 3, 0, 1, 0,
204, 0, 0, 0, 2, 0, 1, 0,
4, 0, 0, 0, 18, 0, 0, 0,
0, 0, 1, 0, 4, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
201, 0, 0, 0, 114, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
200, 0, 0, 0, 3, 0, 1, 0,
212, 0, 0, 0, 2, 0, 1, 0,
5, 0, 0, 0, 19, 0, 0, 0,
0, 0, 1, 0, 5, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
209, 0, 0, 0, 98, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
208, 0, 0, 0, 3, 0, 1, 0,
220, 0, 0, 0, 2, 0, 1, 0,
6, 0, 0, 0, 20, 0, 0, 0,
0, 0, 1, 0, 6, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
217, 0, 0, 0, 74, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
216, 0, 0, 0, 3, 0, 1, 0,
228, 0, 0, 0, 2, 0, 1, 0,
72, 0, 0, 0, 3, 0, 1, 0,
84, 0, 0, 0, 2, 0, 1, 0,
115, 116, 97, 116, 101, 0, 0, 0,
15, 0, 0, 0, 0, 0, 0, 0,
55, 64, 112, 160, 167, 242, 246, 216,
@@ -2204,42 +2155,6 @@ static const ::capnp::_::AlignedData<137> b_bc6a5bae100ebf77 = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
97, 99, 116, 105, 118, 101, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
100, 101, 99, 101, 108, 73, 110, 116,
101, 110, 116, 0, 0, 0, 0, 0,
10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
99, 117, 114, 118, 101, 68, 101, 116,
101, 99, 116, 101, 100, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
119, 97, 110, 116, 66, 108, 101, 110,
100, 101, 100, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
108, 101, 97, 100, 86, 101, 116, 111,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
@@ -2253,11 +2168,11 @@ static const ::capnp::_::AlignedData<137> b_bc6a5bae100ebf77 = {
static const ::capnp::_::RawSchema* const d_bc6a5bae100ebf77[] = {
&s_d8f6f2a7a0704037,
};
static const uint16_t m_bc6a5bae100ebf77[] = {2, 4, 3, 1, 6, 0, 5};
static const uint16_t i_bc6a5bae100ebf77[] = {0, 1, 2, 3, 4, 5, 6};
static const uint16_t m_bc6a5bae100ebf77[] = {2, 1, 0};
static const uint16_t i_bc6a5bae100ebf77[] = {0, 1, 2};
const ::capnp::_::RawSchema s_bc6a5bae100ebf77 = {
0xbc6a5bae100ebf77, b_bc6a5bae100ebf77.words, 137, d_bc6a5bae100ebf77, m_bc6a5bae100ebf77,
1, 7, i_bc6a5bae100ebf77, nullptr, nullptr, { &s_bc6a5bae100ebf77, nullptr, nullptr, 0, 0, nullptr }, false
0xbc6a5bae100ebf77, b_bc6a5bae100ebf77.words, 73, d_bc6a5bae100ebf77, m_bc6a5bae100ebf77,
1, 3, i_bc6a5bae100ebf77, nullptr, nullptr, { &s_bc6a5bae100ebf77, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
static const ::capnp::_::AlignedData<34> b_d8f6f2a7a0704037 = {
@@ -3342,132 +3257,6 @@ const ::capnp::_::RawSchema s_a567bce822ae28fe = {
0, 2, i_a567bce822ae28fe, nullptr, nullptr, { &s_a567bce822ae28fe, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
static const ::capnp::_::AlignedData<68> b_88dcd6d006f3c4a1 = {
{ 0, 0, 0, 0, 5, 0, 6, 0,
161, 196, 243, 6, 208, 214, 220, 136,
32, 0, 0, 0, 1, 0, 1, 0,
194, 110, 191, 11, 86, 196, 92, 243,
0, 0, 7, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
21, 0, 0, 0, 130, 1, 0, 0,
41, 0, 0, 0, 23, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 0, 0, 175, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
99, 117, 115, 116, 111, 109, 46, 99,
97, 112, 110, 112, 58, 76, 111, 110,
103, 105, 116, 117, 100, 105, 110, 97,
108, 80, 108, 97, 110, 83, 80, 46,
65, 99, 99, 101, 108, 67, 111, 110,
116, 114, 111, 108, 108, 101, 114, 0,
4, 0, 0, 0, 1, 0, 1, 0,
166, 80, 70, 176, 68, 149, 159, 140,
1, 0, 0, 0, 66, 0, 0, 0,
80, 114, 111, 102, 105, 108, 101, 0,
12, 0, 0, 0, 3, 0, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
69, 0, 0, 0, 66, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
64, 0, 0, 0, 3, 0, 1, 0,
76, 0, 0, 0, 2, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
73, 0, 0, 0, 58, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
68, 0, 0, 0, 3, 0, 1, 0,
80, 0, 0, 0, 2, 0, 1, 0,
2, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
77, 0, 0, 0, 66, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
72, 0, 0, 0, 3, 0, 1, 0,
84, 0, 0, 0, 2, 0, 1, 0,
101, 110, 97, 98, 108, 101, 100, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
97, 99, 116, 105, 118, 101, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
112, 114, 111, 102, 105, 108, 101, 0,
15, 0, 0, 0, 0, 0, 0, 0,
166, 80, 70, 176, 68, 149, 159, 140,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
15, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, }
};
::capnp::word const* const bp_88dcd6d006f3c4a1 = b_88dcd6d006f3c4a1.words;
#if !CAPNP_LITE
static const ::capnp::_::RawSchema* const d_88dcd6d006f3c4a1[] = {
&s_8c9f9544b04650a6,
};
static const uint16_t m_88dcd6d006f3c4a1[] = {1, 0, 2};
static const uint16_t i_88dcd6d006f3c4a1[] = {0, 1, 2};
const ::capnp::_::RawSchema s_88dcd6d006f3c4a1 = {
0x88dcd6d006f3c4a1, b_88dcd6d006f3c4a1.words, 68, d_88dcd6d006f3c4a1, m_88dcd6d006f3c4a1,
1, 3, i_88dcd6d006f3c4a1, nullptr, nullptr, { &s_88dcd6d006f3c4a1, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
static const ::capnp::_::AlignedData<33> b_8c9f9544b04650a6 = {
{ 0, 0, 0, 0, 5, 0, 6, 0,
166, 80, 70, 176, 68, 149, 159, 140,
48, 0, 0, 0, 2, 0, 0, 0,
161, 196, 243, 6, 208, 214, 220, 136,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
21, 0, 0, 0, 194, 1, 0, 0,
45, 0, 0, 0, 7, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 0, 0, 79, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
99, 117, 115, 116, 111, 109, 46, 99,
97, 112, 110, 112, 58, 76, 111, 110,
103, 105, 116, 117, 100, 105, 110, 97,
108, 80, 108, 97, 110, 83, 80, 46,
65, 99, 99, 101, 108, 67, 111, 110,
116, 114, 111, 108, 108, 101, 114, 46,
80, 114, 111, 102, 105, 108, 101, 0,
0, 0, 0, 0, 1, 0, 1, 0,
12, 0, 0, 0, 1, 0, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0,
29, 0, 0, 0, 34, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
21, 0, 0, 0, 58, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
2, 0, 0, 0, 0, 0, 0, 0,
13, 0, 0, 0, 50, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
101, 99, 111, 0, 0, 0, 0, 0,
110, 111, 114, 109, 97, 108, 0, 0,
115, 112, 111, 114, 116, 0, 0, 0, }
};
::capnp::word const* const bp_8c9f9544b04650a6 = b_8c9f9544b04650a6.words;
#if !CAPNP_LITE
static const uint16_t m_8c9f9544b04650a6[] = {0, 1, 2};
const ::capnp::_::RawSchema s_8c9f9544b04650a6 = {
0x8c9f9544b04650a6, b_8c9f9544b04650a6.words, 33, nullptr, m_8c9f9544b04650a6,
0, 3, nullptr, nullptr, nullptr, { &s_8c9f9544b04650a6, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
CAPNP_DEFINE_ENUM(Profile_8c9f9544b04650a6, 8c9f9544b04650a6);
static const ::capnp::_::AlignedData<44> b_da96579883444c35 = {
{ 0, 0, 0, 0, 5, 0, 6, 0,
53, 76, 68, 131, 152, 87, 150, 218,
@@ -5023,65 +4812,33 @@ const ::capnp::_::RawSchema s_9e62278160b7df26 = {
2, 8, i_9e62278160b7df26, nullptr, nullptr, { &s_9e62278160b7df26, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
static const ::capnp::_::AlignedData<65> b_b86e6369214c01c8 = {
static const ::capnp::_::AlignedData<33> b_b86e6369214c01c8 = {
{ 0, 0, 0, 0, 5, 0, 6, 0,
200, 1, 76, 33, 105, 99, 110, 184,
13, 0, 0, 0, 1, 0, 2, 0,
13, 0, 0, 0, 1, 0, 1, 0,
89, 10, 85, 29, 102, 186, 38, 181,
0, 0, 7, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
21, 0, 0, 0, 194, 0, 0, 0,
29, 0, 0, 0, 7, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 0, 0, 175, 0, 0, 0,
25, 0, 0, 0, 63, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
99, 117, 115, 116, 111, 109, 46, 99,
97, 112, 110, 112, 58, 67, 97, 114,
83, 116, 97, 116, 101, 83, 80, 0,
0, 0, 0, 0, 1, 0, 1, 0,
12, 0, 0, 0, 3, 0, 4, 0,
4, 0, 0, 0, 3, 0, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
69, 0, 0, 0, 90, 0, 0, 0,
13, 0, 0, 0, 90, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
68, 0, 0, 0, 3, 0, 1, 0,
80, 0, 0, 0, 2, 0, 1, 0,
1, 0, 0, 0, 32, 0, 0, 0,
0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
77, 0, 0, 0, 82, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
76, 0, 0, 0, 3, 0, 1, 0,
88, 0, 0, 0, 2, 0, 1, 0,
2, 0, 0, 0, 2, 0, 0, 0,
0, 0, 1, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
85, 0, 0, 0, 82, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
84, 0, 0, 0, 3, 0, 1, 0,
96, 0, 0, 0, 2, 0, 1, 0,
12, 0, 0, 0, 3, 0, 1, 0,
24, 0, 0, 0, 2, 0, 1, 0,
115, 112, 101, 101, 100, 76, 105, 109,
105, 116, 0, 0, 0, 0, 0, 0,
10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
101, 110, 103, 105, 110, 101, 79, 102,
102, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
101, 110, 103, 105, 110, 101, 82, 112,
109, 0, 0, 0, 0, 0, 0, 0,
10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
@@ -5092,11 +4849,11 @@ static const ::capnp::_::AlignedData<65> b_b86e6369214c01c8 = {
};
::capnp::word const* const bp_b86e6369214c01c8 = b_b86e6369214c01c8.words;
#if !CAPNP_LITE
static const uint16_t m_b86e6369214c01c8[] = {1, 2, 0};
static const uint16_t i_b86e6369214c01c8[] = {0, 1, 2};
static const uint16_t m_b86e6369214c01c8[] = {0};
static const uint16_t i_b86e6369214c01c8[] = {0};
const ::capnp::_::RawSchema s_b86e6369214c01c8 = {
0xb86e6369214c01c8, b_b86e6369214c01c8.words, 65, nullptr, m_b86e6369214c01c8,
0, 3, i_b86e6369214c01c8, nullptr, nullptr, { &s_b86e6369214c01c8, nullptr, nullptr, 0, 0, nullptr }, false
0xb86e6369214c01c8, b_b86e6369214c01c8.words, 33, nullptr, m_b86e6369214c01c8,
0, 1, i_b86e6369214c01c8, nullptr, nullptr, { &s_b86e6369214c01c8, nullptr, nullptr, 0, 0, nullptr }, false
};
#endif // !CAPNP_LITE
static const ::capnp::_::AlignedData<116> b_f416ec09499d9d19 = {
@@ -5878,18 +5635,6 @@ constexpr ::capnp::_::RawSchema const* LongitudinalPlanSP::E2eAlerts::_capnpPriv
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
#endif // !CAPNP_LITE
// LongitudinalPlanSP::AccelController
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr uint16_t LongitudinalPlanSP::AccelController::_capnpPrivate::dataWordSize;
constexpr uint16_t LongitudinalPlanSP::AccelController::_capnpPrivate::pointerCount;
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
#if !CAPNP_LITE
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr ::capnp::Kind LongitudinalPlanSP::AccelController::_capnpPrivate::kind;
constexpr ::capnp::_::RawSchema const* LongitudinalPlanSP::AccelController::_capnpPrivate::schema;
#endif // !CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
#endif // !CAPNP_LITE
// OnroadEventSP
#if CAPNP_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr uint16_t OnroadEventSP::_capnpPrivate::dataWordSize;
+2 -320
View File
@@ -178,14 +178,6 @@ enum class LongitudinalPlanSource_ad47440556d96ec4: uint16_t {
};
CAPNP_DECLARE_ENUM(LongitudinalPlanSource, ad47440556d96ec4);
CAPNP_DECLARE_SCHEMA(a567bce822ae28fe);
CAPNP_DECLARE_SCHEMA(88dcd6d006f3c4a1);
CAPNP_DECLARE_SCHEMA(8c9f9544b04650a6);
enum class Profile_8c9f9544b04650a6: uint16_t {
ECO,
NORMAL,
SPORT,
};
CAPNP_DECLARE_ENUM(Profile, 8c9f9544b04650a6);
CAPNP_DECLARE_SCHEMA(da96579883444c35);
CAPNP_DECLARE_SCHEMA(f6e831752fcdf793);
CAPNP_DECLARE_SCHEMA(b8007ed8a646b5e6);
@@ -485,10 +477,9 @@ struct LongitudinalPlanSP {
typedef ::capnp::schemas::LongitudinalPlanSource_ad47440556d96ec4 LongitudinalPlanSource;
struct E2eAlerts;
struct AccelController;
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(f35cc4560bbf6ec2, 2, 6)
CAPNP_DECLARE_STRUCT_HEADER(f35cc4560bbf6ec2, 2, 5)
#if !CAPNP_LITE
static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; }
#endif // !CAPNP_LITE
@@ -629,23 +620,6 @@ struct LongitudinalPlanSP::E2eAlerts {
};
};
struct LongitudinalPlanSP::AccelController {
AccelController() = delete;
class Reader;
class Builder;
class Pipeline;
typedef ::capnp::schemas::Profile_8c9f9544b04650a6 Profile;
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(88dcd6d006f3c4a1, 1, 0)
#if !CAPNP_LITE
static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; }
#endif // !CAPNP_LITE
};
};
struct OnroadEventSP {
OnroadEventSP() = delete;
@@ -832,7 +806,7 @@ struct CarStateSP {
class Pipeline;
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(b86e6369214c01c8, 2, 0)
CAPNP_DECLARE_STRUCT_HEADER(b86e6369214c01c8, 1, 0)
#if !CAPNP_LITE
static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; }
#endif // !CAPNP_LITE
@@ -2326,9 +2300,6 @@ public:
inline bool hasE2eAlerts() const;
inline ::cereal::LongitudinalPlanSP::E2eAlerts::Reader getE2eAlerts() const;
inline bool hasAccelController() const;
inline ::cereal::LongitudinalPlanSP::AccelController::Reader getAccelController() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -2401,13 +2372,6 @@ public:
inline void adoptE2eAlerts(::capnp::Orphan< ::cereal::LongitudinalPlanSP::E2eAlerts>&& value);
inline ::capnp::Orphan< ::cereal::LongitudinalPlanSP::E2eAlerts> disownE2eAlerts();
inline bool hasAccelController();
inline ::cereal::LongitudinalPlanSP::AccelController::Builder getAccelController();
inline void setAccelController( ::cereal::LongitudinalPlanSP::AccelController::Reader value);
inline ::cereal::LongitudinalPlanSP::AccelController::Builder initAccelController();
inline void adoptAccelController(::capnp::Orphan< ::cereal::LongitudinalPlanSP::AccelController>&& value);
inline ::capnp::Orphan< ::cereal::LongitudinalPlanSP::AccelController> disownAccelController();
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -2430,7 +2394,6 @@ public:
inline ::cereal::LongitudinalPlanSP::SmartCruiseControl::Pipeline getSmartCruiseControl();
inline ::cereal::LongitudinalPlanSP::SpeedLimit::Pipeline getSpeedLimit();
inline ::cereal::LongitudinalPlanSP::E2eAlerts::Pipeline getE2eAlerts();
inline ::cereal::LongitudinalPlanSP::AccelController::Pipeline getAccelController();
private:
::capnp::AnyPointer::Pipeline _typeless;
friend class ::capnp::PipelineHook;
@@ -2462,14 +2425,6 @@ public:
inline bool getActive() const;
inline float getDecelIntent() const;
inline bool getCurveDetected() const;
inline bool getWantBlended() const;
inline bool getLeadVeto() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -2507,18 +2462,6 @@ public:
inline bool getActive();
inline void setActive(bool value);
inline float getDecelIntent();
inline void setDecelIntent(float value);
inline bool getCurveDetected();
inline void setCurveDetected(bool value);
inline bool getWantBlended();
inline void setWantBlended(bool value);
inline bool getLeadVeto();
inline void setLeadVeto(bool value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -3226,92 +3169,6 @@ private:
};
#endif // !CAPNP_LITE
class LongitudinalPlanSP::AccelController::Reader {
public:
typedef AccelController Reads;
Reader() = default;
inline explicit Reader(::capnp::_::StructReader base): _reader(base) {}
inline ::capnp::MessageSize totalSize() const {
return _reader.totalSize().asPublic();
}
#if !CAPNP_LITE
inline ::kj::StringTree toString() const {
return ::capnp::_::structString(_reader, *_capnpPrivate::brand());
}
#endif // !CAPNP_LITE
inline bool getEnabled() const;
inline bool getActive() const;
inline ::cereal::LongitudinalPlanSP::AccelController::Profile getProfile() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
friend struct ::capnp::ToDynamic_;
template <typename, ::capnp::Kind>
friend struct ::capnp::_::PointerHelpers;
template <typename, ::capnp::Kind>
friend struct ::capnp::List;
friend class ::capnp::MessageBuilder;
friend class ::capnp::Orphanage;
};
class LongitudinalPlanSP::AccelController::Builder {
public:
typedef AccelController Builds;
Builder() = delete; // Deleted to discourage incorrect usage.
// You can explicitly initialize to nullptr instead.
inline Builder(decltype(nullptr)) {}
inline explicit Builder(::capnp::_::StructBuilder base): _builder(base) {}
inline operator Reader() const { return Reader(_builder.asReader()); }
inline Reader asReader() const { return *this; }
inline ::capnp::MessageSize totalSize() const { return asReader().totalSize(); }
#if !CAPNP_LITE
inline ::kj::StringTree toString() const { return asReader().toString(); }
#endif // !CAPNP_LITE
inline bool getEnabled();
inline void setEnabled(bool value);
inline bool getActive();
inline void setActive(bool value);
inline ::cereal::LongitudinalPlanSP::AccelController::Profile getProfile();
inline void setProfile( ::cereal::LongitudinalPlanSP::AccelController::Profile value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
friend struct ::capnp::ToDynamic_;
friend class ::capnp::Orphanage;
template <typename, ::capnp::Kind>
friend struct ::capnp::_::PointerHelpers;
};
#if !CAPNP_LITE
class LongitudinalPlanSP::AccelController::Pipeline {
public:
typedef AccelController Pipelines;
inline Pipeline(decltype(nullptr)): _typeless(nullptr) {}
inline explicit Pipeline(::capnp::AnyPointer::Pipeline&& typeless)
: _typeless(kj::mv(typeless)) {}
private:
::capnp::AnyPointer::Pipeline _typeless;
friend class ::capnp::PipelineHook;
template <typename, ::capnp::Kind>
friend struct ::capnp::ToDynamic_;
};
#endif // !CAPNP_LITE
class OnroadEventSP::Reader {
public:
typedef OnroadEventSP Reads;
@@ -4521,10 +4378,6 @@ public:
inline float getSpeedLimit() const;
inline bool getEngineOff() const;
inline float getEngineRpm() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -4556,12 +4409,6 @@ public:
inline float getSpeedLimit();
inline void setSpeedLimit(float value);
inline bool getEngineOff();
inline void setEngineOff(bool value);
inline float getEngineRpm();
inline void setEngineRpm(float value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -7036,45 +6883,6 @@ inline ::capnp::Orphan< ::cereal::LongitudinalPlanSP::E2eAlerts> LongitudinalPla
::capnp::bounded<4>() * ::capnp::POINTERS));
}
inline bool LongitudinalPlanSP::Reader::hasAccelController() const {
return !_reader.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS).isNull();
}
inline bool LongitudinalPlanSP::Builder::hasAccelController() {
return !_builder.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS).isNull();
}
inline ::cereal::LongitudinalPlanSP::AccelController::Reader LongitudinalPlanSP::Reader::getAccelController() const {
return ::capnp::_::PointerHelpers< ::cereal::LongitudinalPlanSP::AccelController>::get(_reader.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS));
}
inline ::cereal::LongitudinalPlanSP::AccelController::Builder LongitudinalPlanSP::Builder::getAccelController() {
return ::capnp::_::PointerHelpers< ::cereal::LongitudinalPlanSP::AccelController>::get(_builder.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS));
}
#if !CAPNP_LITE
inline ::cereal::LongitudinalPlanSP::AccelController::Pipeline LongitudinalPlanSP::Pipeline::getAccelController() {
return ::cereal::LongitudinalPlanSP::AccelController::Pipeline(_typeless.getPointerField(5));
}
#endif // !CAPNP_LITE
inline void LongitudinalPlanSP::Builder::setAccelController( ::cereal::LongitudinalPlanSP::AccelController::Reader value) {
::capnp::_::PointerHelpers< ::cereal::LongitudinalPlanSP::AccelController>::set(_builder.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS), value);
}
inline ::cereal::LongitudinalPlanSP::AccelController::Builder LongitudinalPlanSP::Builder::initAccelController() {
return ::capnp::_::PointerHelpers< ::cereal::LongitudinalPlanSP::AccelController>::init(_builder.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS));
}
inline void LongitudinalPlanSP::Builder::adoptAccelController(
::capnp::Orphan< ::cereal::LongitudinalPlanSP::AccelController>&& value) {
::capnp::_::PointerHelpers< ::cereal::LongitudinalPlanSP::AccelController>::adopt(_builder.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS), kj::mv(value));
}
inline ::capnp::Orphan< ::cereal::LongitudinalPlanSP::AccelController> LongitudinalPlanSP::Builder::disownAccelController() {
return ::capnp::_::PointerHelpers< ::cereal::LongitudinalPlanSP::AccelController>::disown(_builder.getPointerField(
::capnp::bounded<5>() * ::capnp::POINTERS));
}
inline ::cereal::LongitudinalPlanSP::DynamicExperimentalControl::DynamicExperimentalControlState LongitudinalPlanSP::DynamicExperimentalControl::Reader::getState() const {
return _reader.getDataField< ::cereal::LongitudinalPlanSP::DynamicExperimentalControl::DynamicExperimentalControlState>(
::capnp::bounded<0>() * ::capnp::ELEMENTS);
@@ -7117,62 +6925,6 @@ inline void LongitudinalPlanSP::DynamicExperimentalControl::Builder::setActive(b
::capnp::bounded<17>() * ::capnp::ELEMENTS, value);
}
inline float LongitudinalPlanSP::DynamicExperimentalControl::Reader::getDecelIntent() const {
return _reader.getDataField<float>(
::capnp::bounded<1>() * ::capnp::ELEMENTS);
}
inline float LongitudinalPlanSP::DynamicExperimentalControl::Builder::getDecelIntent() {
return _builder.getDataField<float>(
::capnp::bounded<1>() * ::capnp::ELEMENTS);
}
inline void LongitudinalPlanSP::DynamicExperimentalControl::Builder::setDecelIntent(float value) {
_builder.setDataField<float>(
::capnp::bounded<1>() * ::capnp::ELEMENTS, value);
}
inline bool LongitudinalPlanSP::DynamicExperimentalControl::Reader::getCurveDetected() const {
return _reader.getDataField<bool>(
::capnp::bounded<18>() * ::capnp::ELEMENTS);
}
inline bool LongitudinalPlanSP::DynamicExperimentalControl::Builder::getCurveDetected() {
return _builder.getDataField<bool>(
::capnp::bounded<18>() * ::capnp::ELEMENTS);
}
inline void LongitudinalPlanSP::DynamicExperimentalControl::Builder::setCurveDetected(bool value) {
_builder.setDataField<bool>(
::capnp::bounded<18>() * ::capnp::ELEMENTS, value);
}
inline bool LongitudinalPlanSP::DynamicExperimentalControl::Reader::getWantBlended() const {
return _reader.getDataField<bool>(
::capnp::bounded<19>() * ::capnp::ELEMENTS);
}
inline bool LongitudinalPlanSP::DynamicExperimentalControl::Builder::getWantBlended() {
return _builder.getDataField<bool>(
::capnp::bounded<19>() * ::capnp::ELEMENTS);
}
inline void LongitudinalPlanSP::DynamicExperimentalControl::Builder::setWantBlended(bool value) {
_builder.setDataField<bool>(
::capnp::bounded<19>() * ::capnp::ELEMENTS, value);
}
inline bool LongitudinalPlanSP::DynamicExperimentalControl::Reader::getLeadVeto() const {
return _reader.getDataField<bool>(
::capnp::bounded<20>() * ::capnp::ELEMENTS);
}
inline bool LongitudinalPlanSP::DynamicExperimentalControl::Builder::getLeadVeto() {
return _builder.getDataField<bool>(
::capnp::bounded<20>() * ::capnp::ELEMENTS);
}
inline void LongitudinalPlanSP::DynamicExperimentalControl::Builder::setLeadVeto(bool value) {
_builder.setDataField<bool>(
::capnp::bounded<20>() * ::capnp::ELEMENTS, value);
}
inline bool LongitudinalPlanSP::SmartCruiseControl::Reader::hasVision() const {
return !_reader.getPointerField(
::capnp::bounded<0>() * ::capnp::POINTERS).isNull();
@@ -7721,48 +7473,6 @@ inline void LongitudinalPlanSP::E2eAlerts::Builder::setLeadDepartAlert(bool valu
::capnp::bounded<1>() * ::capnp::ELEMENTS, value);
}
inline bool LongitudinalPlanSP::AccelController::Reader::getEnabled() const {
return _reader.getDataField<bool>(
::capnp::bounded<0>() * ::capnp::ELEMENTS);
}
inline bool LongitudinalPlanSP::AccelController::Builder::getEnabled() {
return _builder.getDataField<bool>(
::capnp::bounded<0>() * ::capnp::ELEMENTS);
}
inline void LongitudinalPlanSP::AccelController::Builder::setEnabled(bool value) {
_builder.setDataField<bool>(
::capnp::bounded<0>() * ::capnp::ELEMENTS, value);
}
inline bool LongitudinalPlanSP::AccelController::Reader::getActive() const {
return _reader.getDataField<bool>(
::capnp::bounded<1>() * ::capnp::ELEMENTS);
}
inline bool LongitudinalPlanSP::AccelController::Builder::getActive() {
return _builder.getDataField<bool>(
::capnp::bounded<1>() * ::capnp::ELEMENTS);
}
inline void LongitudinalPlanSP::AccelController::Builder::setActive(bool value) {
_builder.setDataField<bool>(
::capnp::bounded<1>() * ::capnp::ELEMENTS, value);
}
inline ::cereal::LongitudinalPlanSP::AccelController::Profile LongitudinalPlanSP::AccelController::Reader::getProfile() const {
return _reader.getDataField< ::cereal::LongitudinalPlanSP::AccelController::Profile>(
::capnp::bounded<1>() * ::capnp::ELEMENTS);
}
inline ::cereal::LongitudinalPlanSP::AccelController::Profile LongitudinalPlanSP::AccelController::Builder::getProfile() {
return _builder.getDataField< ::cereal::LongitudinalPlanSP::AccelController::Profile>(
::capnp::bounded<1>() * ::capnp::ELEMENTS);
}
inline void LongitudinalPlanSP::AccelController::Builder::setProfile( ::cereal::LongitudinalPlanSP::AccelController::Profile value) {
_builder.setDataField< ::cereal::LongitudinalPlanSP::AccelController::Profile>(
::capnp::bounded<1>() * ::capnp::ELEMENTS, value);
}
inline bool OnroadEventSP::Reader::hasEvents() const {
return !_reader.getPointerField(
::capnp::bounded<0>() * ::capnp::POINTERS).isNull();
@@ -9097,34 +8807,6 @@ inline void CarStateSP::Builder::setSpeedLimit(float value) {
::capnp::bounded<0>() * ::capnp::ELEMENTS, value);
}
inline bool CarStateSP::Reader::getEngineOff() const {
return _reader.getDataField<bool>(
::capnp::bounded<32>() * ::capnp::ELEMENTS);
}
inline bool CarStateSP::Builder::getEngineOff() {
return _builder.getDataField<bool>(
::capnp::bounded<32>() * ::capnp::ELEMENTS);
}
inline void CarStateSP::Builder::setEngineOff(bool value) {
_builder.setDataField<bool>(
::capnp::bounded<32>() * ::capnp::ELEMENTS, value);
}
inline float CarStateSP::Reader::getEngineRpm() const {
return _reader.getDataField<float>(
::capnp::bounded<2>() * ::capnp::ELEMENTS);
}
inline float CarStateSP::Builder::getEngineRpm() {
return _builder.getDataField<float>(
::capnp::bounded<2>() * ::capnp::ELEMENTS);
}
inline void CarStateSP::Builder::setEngineRpm(float value) {
_builder.setDataField<float>(
::capnp::bounded<2>() * ::capnp::ELEMENTS, value);
}
inline bool LiveMapDataSP::Reader::getSpeedLimitValid() const {
return _reader.getDataField<bool>(
::capnp::bounded<0>() * ::capnp::ELEMENTS);
Binary file not shown.
-12
View File
@@ -192,16 +192,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SunnypilotStoppingController", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
// toyota specific params
{"ToyotaAutoHold", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaBrakeOnset", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaEnhancedBsm", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaTSS2Long", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaDriveMode", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
{"MadsMainCruiseAllowed", {PERSISTENT | BACKUP, BOOL, "1"}},
@@ -251,10 +243,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
// Accel Controller profiles (Eco / Normal / Sport)
{"AccelPersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AccelPersonality", {PERSISTENT | BACKUP, INT, "1"}},
// sunnypilot model params
{"CameraOffset", {PERSISTENT | BACKUP, FLOAT, "0.0"}},
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
-4
View File
@@ -117,16 +117,12 @@ class TestParams(OpenpilotTestCase):
def test_params_default_value(self):
self.params.remove("LanguageSetting")
self.params.remove("LongitudinalPersonality")
self.params.remove("AccelPersonalityEnabled")
self.params.remove("AccelPersonality")
self.params.remove("LiveParametersV2")
assert self.params.get("LanguageSetting") is None
assert self.params.get("LanguageSetting", return_default=False) is None
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
assert self.params.get("AccelPersonalityEnabled", return_default=True) is False
assert self.params.get("AccelPersonality", return_default=True) == 1
assert self.params.get("LiveParametersV2") is None
assert self.params.get("LiveParametersV2", return_default=True) is None
+1 -4
View File
@@ -11,13 +11,13 @@ from opendbc.car.structs import car
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.common.swaglog import cloudlog, ForwardingHandler
from opendbc.car import DT_CTRL, structs
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
from opendbc.car.carlog import carlog
from opendbc.car.fw_versions import ObdCallback
from opendbc.car.car_helpers import get_car, interfaces
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
from openpilot.selfdrive.car.cruise import VCruiseHelper
from openpilot.selfdrive.car.helpers import convert_carControlSP, convert_to_capnp
@@ -123,9 +123,6 @@ class Car:
self.RI = RI
self.CP.alternativeExperience = 0
if self.params.get_bool("ToyotaAutoHold"):
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
# mads
set_alternative_experience(self.CP, self.CP_SP, self.params)
set_car_specific_params(self.CP, self.CP_SP, self.params)
+7 -64
View File
@@ -19,7 +19,6 @@ IMPERIAL_INCREMENT = round(CV.MPH_TO_KPH, 1) # round here to avoid rounding err
ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
CRUISE_LONG_PRESS = 50
TOYOTA_VIRTUAL_CRUISE_LONG_PRESS = 65
CRUISE_NEAREST_FUNC = {
ButtonType.accelCruise: math.ceil,
ButtonType.decelCruise: math.floor,
@@ -44,30 +43,6 @@ class VCruiseHelper(VCruiseHelperSP):
def v_cruise_initialized(self):
return self.v_cruise_kph != V_CRUISE_UNSET
@property
def software_pcm_cruise_speed(self) -> bool:
return self.CP.brand == "toyota" and self.CP.pcmCruise and self.CP.openpilotLongitudinalControl and not self.CP_SP.pcmCruiseSpeed
@property
def cruise_long_press_frames(self) -> int:
return TOYOTA_VIRTUAL_CRUISE_LONG_PRESS if self.software_pcm_cruise_speed else CRUISE_LONG_PRESS
@property
def software_pcm_cruise_initialized(self) -> bool:
return 0 < self.v_cruise_kph < V_CRUISE_UNSET and 0 < self.v_cruise_cluster_kph < V_CRUISE_UNSET
def _apply_software_pcm_cruise_delta(self, delta_kph: float, is_metric: bool) -> None:
"""Move Toyota's planner/display targets together while respecting both targets' bounds."""
cluster_min_kph = self.v_cruise_min if is_metric else self.v_cruise_min * CV.MPH_TO_KPH
min_delta = max(V_CRUISE_MIN - self.v_cruise_kph, cluster_min_kph - self.v_cruise_cluster_kph)
max_delta = min(V_CRUISE_MAX - self.v_cruise_kph, V_CRUISE_MAX - self.v_cruise_cluster_kph)
if delta_kph > 0:
applied_delta = min(delta_kph, max(0., max_delta))
else:
applied_delta = max(delta_kph, min(0., min_delta))
self.v_cruise_kph = round(self.v_cruise_kph + applied_delta, 1)
self.v_cruise_cluster_kph = round(self.v_cruise_cluster_kph + applied_delta, 1)
def update_v_cruise(self, CS, enabled, is_metric):
self.v_cruise_kph_last = self.v_cruise_kph
@@ -76,21 +51,11 @@ class VCruiseHelper(VCruiseHelperSP):
_enabled = self.update_enabled_state(CS, enabled)
if CS.cruiseState.available:
software_pcm_enabled = not self.CP_SP.pcmCruiseSpeed and _enabled
if self.software_pcm_cruise_speed:
software_pcm_enabled = software_pcm_enabled and self.software_pcm_cruise_initialized
if not self.CP.pcmCruise or software_pcm_enabled:
if not self.CP.pcmCruise or (not self.CP_SP.pcmCruiseSpeed and _enabled):
# if stock cruise is completely disabled, then we can use our own set speed logic
self._update_v_cruise_non_pcm(CS, _enabled, is_metric)
v_cruise_kph_before_sla = self.v_cruise_kph
self.update_speed_limit_assist_v_cruise_non_pcm()
if self.software_pcm_cruise_speed:
sla_delta_kph = self.v_cruise_kph - v_cruise_kph_before_sla
self.v_cruise_kph = v_cruise_kph_before_sla
self._apply_software_pcm_cruise_delta(sla_delta_kph, is_metric)
else:
self.v_cruise_cluster_kph = self.v_cruise_kph
self.v_cruise_cluster_kph = self.v_cruise_kph
else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
@@ -120,13 +85,13 @@ class VCruiseHelper(VCruiseHelperSP):
for b in CS.buttonEvents:
if b.type.raw in self.button_timers and not b.pressed:
if self.button_timers[b.type.raw] > self.cruise_long_press_frames:
if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS:
return # end long press
button_type = b.type.raw
break
else:
for k, timer in self.button_timers.items():
if timer and timer % self.cruise_long_press_frames == 0:
if timer and timer % CRUISE_LONG_PRESS == 0:
button_type = k
long_press = True
break
@@ -150,26 +115,10 @@ class VCruiseHelper(VCruiseHelperSP):
return
long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
# Toyota's canonical PCM set speed and displayed cluster set speed can differ. In
# software-owned PCM mode, round the value the driver sees and apply the same delta
# to both targets so the planner/cluster calibration offset remains intact.
v_cruise_reference = self.v_cruise_cluster_kph if self.software_pcm_cruise_speed else self.v_cruise_kph
if long_press and v_cruise_reference % v_cruise_delta != 0: # partial interval
v_cruise_reference_new = CRUISE_NEAREST_FUNC[button_type](v_cruise_reference / v_cruise_delta) * v_cruise_delta
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
else:
v_cruise_reference_new = v_cruise_reference + v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
if self.software_pcm_cruise_speed:
delta_kph = v_cruise_reference_new - v_cruise_reference
# If SET is pressed while overriding, do not lower the target below the current speed.
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
delta_kph = max(delta_kph, CS.vEgo * CV.MS_TO_KPH - self.v_cruise_kph)
self._apply_software_pcm_cruise_delta(delta_kph, is_metric)
return
self.v_cruise_kph += v_cruise_reference_new - v_cruise_reference
self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
# If set is pressed while overriding, clip cruise speed to minimum of vEgo
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
@@ -178,12 +127,6 @@ class VCruiseHelper(VCruiseHelperSP):
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), self.v_cruise_min, V_CRUISE_MAX)
def update_button_timers(self, CS, enabled):
if self.software_pcm_cruise_speed and (not enabled or not CS.cruiseState.available or not self.software_pcm_cruise_initialized):
for k in self.button_timers:
self.button_timers[k] = 0
self.button_change_states[k] = {"standstill": False, "enabled": False}
return
# increment timer for buttons still pressed
for k in self.button_timers:
if self.button_timers[k] > 0:
@@ -136,7 +136,6 @@ class Controls(ControlsExt):
# accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, self.CP_SP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits))
self.long_accel_sp(actuators, CS, long_plan, pid_accel_limits) # sunnypilot hook
# Steering PID loop and lateral MPC
# Reset desired curvature to current to avoid violating the limits on engage
@@ -35,12 +35,9 @@ def get_max_accel(v_ego):
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
def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt, accel_coast, allow_throttle,
max_accel_override=None):
if max_accel_override is not None:
max_accel = max_accel_override
else:
max_accel = ACCEL_MAX if e2e else get_max_accel(v_ego)
def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt, accel_coast, allow_throttle):
max_accel = ACCEL_MAX if e2e else get_max_accel(v_ego)
if not e2e:
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)
@@ -87,8 +84,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
v_ego = sm['carState'].vEgo
v_cruise_kph = min(sm['carState'].vCruise, V_CRUISE_MAX)
v_cruise = v_cruise_kph * CV.KPH_TO_MS
force_decel = sm['controlsState'].forceDecel
if force_decel:
if sm['controlsState'].forceDecel:
v_cruise = 0.0
long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
@@ -122,7 +118,6 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality)
self.mpc.set_cur_state(self.v_desired_filter.x, self.output_a_target)
self.mpc.update(sm['radarState'], personality=sm['selfdriveState'].personality)
self.update_dec(sm)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
@@ -145,17 +140,9 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
is_e2e = self.is_e2e(sm)
max_accel_override = self.get_max_accel_override(v_ego, sm['carStateSP'].engineOff)
v_cruise = self.get_cruise_target_override(v_ego, v_cruise, force_decel, accel_coast if accel_coast < 0.0 else None)
a_cruise_prev = self.a_cruise
gated_cruise = get_cruise_accel(is_e2e, v_cruise, v_ego, a_cruise_prev, steer_angle_without_offset,
self.CP, self.dt, accel_coast, self.allow_throttle, max_accel_override)
ungated_cruise = get_cruise_accel(is_e2e, v_cruise, v_ego, a_cruise_prev, steer_angle_without_offset,
self.CP, self.dt, accel_coast, True, max_accel_override)
self.a_cruise = self.arbitrate_cruise_candidate(
sm, gated_cruise, ungated_cruise, output_a_target_mpc, self.mpc.source,
allow_throttle=self.allow_throttle, e2e=is_e2e, force_decel=force_decel,
)
self.a_cruise = get_cruise_accel(is_e2e, v_cruise, v_ego,
self.a_cruise, steer_angle_without_offset, self.CP, self.dt,
accel_coast, self.allow_throttle)
cruise_should_stop = should_stop(v_ego, self.a_cruise)
candidates = [(output_a_target_mpc, self.mpc.source, output_should_stop_mpc),
@@ -166,7 +153,6 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
output_a_target, self.mpc.source, _ = min(candidates, key=lambda c: c[0])
self.output_should_stop = any(should_stop for _, _, should_stop in candidates)
self.output_a_target = np.clip(output_a_target, ACCEL_MIN, ACCEL_MAX)
self.accel_controller_active = self.is_accel_controller_active(force_decel)
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.output_a_target + a_prev) / 2.0
@@ -45,326 +45,326 @@ const static double MAHA_THRESH_31 = 3.8414588206941227;
* *
* This file is part of 'ekf' *
******************************************************************************/
void err_fun(double *nom_x, double *delta_x, double *out_5070392900769873758) {
out_5070392900769873758[0] = delta_x[0] + nom_x[0];
out_5070392900769873758[1] = delta_x[1] + nom_x[1];
out_5070392900769873758[2] = delta_x[2] + nom_x[2];
out_5070392900769873758[3] = delta_x[3] + nom_x[3];
out_5070392900769873758[4] = delta_x[4] + nom_x[4];
out_5070392900769873758[5] = delta_x[5] + nom_x[5];
out_5070392900769873758[6] = delta_x[6] + nom_x[6];
out_5070392900769873758[7] = delta_x[7] + nom_x[7];
out_5070392900769873758[8] = delta_x[8] + nom_x[8];
void err_fun(double *nom_x, double *delta_x, double *out_5744499419236567507) {
out_5744499419236567507[0] = delta_x[0] + nom_x[0];
out_5744499419236567507[1] = delta_x[1] + nom_x[1];
out_5744499419236567507[2] = delta_x[2] + nom_x[2];
out_5744499419236567507[3] = delta_x[3] + nom_x[3];
out_5744499419236567507[4] = delta_x[4] + nom_x[4];
out_5744499419236567507[5] = delta_x[5] + nom_x[5];
out_5744499419236567507[6] = delta_x[6] + nom_x[6];
out_5744499419236567507[7] = delta_x[7] + nom_x[7];
out_5744499419236567507[8] = delta_x[8] + nom_x[8];
}
void inv_err_fun(double *nom_x, double *true_x, double *out_3742760733154016051) {
out_3742760733154016051[0] = -nom_x[0] + true_x[0];
out_3742760733154016051[1] = -nom_x[1] + true_x[1];
out_3742760733154016051[2] = -nom_x[2] + true_x[2];
out_3742760733154016051[3] = -nom_x[3] + true_x[3];
out_3742760733154016051[4] = -nom_x[4] + true_x[4];
out_3742760733154016051[5] = -nom_x[5] + true_x[5];
out_3742760733154016051[6] = -nom_x[6] + true_x[6];
out_3742760733154016051[7] = -nom_x[7] + true_x[7];
out_3742760733154016051[8] = -nom_x[8] + true_x[8];
void inv_err_fun(double *nom_x, double *true_x, double *out_1184805137205037163) {
out_1184805137205037163[0] = -nom_x[0] + true_x[0];
out_1184805137205037163[1] = -nom_x[1] + true_x[1];
out_1184805137205037163[2] = -nom_x[2] + true_x[2];
out_1184805137205037163[3] = -nom_x[3] + true_x[3];
out_1184805137205037163[4] = -nom_x[4] + true_x[4];
out_1184805137205037163[5] = -nom_x[5] + true_x[5];
out_1184805137205037163[6] = -nom_x[6] + true_x[6];
out_1184805137205037163[7] = -nom_x[7] + true_x[7];
out_1184805137205037163[8] = -nom_x[8] + true_x[8];
}
void H_mod_fun(double *state, double *out_5758456641177717920) {
out_5758456641177717920[0] = 1.0;
out_5758456641177717920[1] = 0.0;
out_5758456641177717920[2] = 0.0;
out_5758456641177717920[3] = 0.0;
out_5758456641177717920[4] = 0.0;
out_5758456641177717920[5] = 0.0;
out_5758456641177717920[6] = 0.0;
out_5758456641177717920[7] = 0.0;
out_5758456641177717920[8] = 0.0;
out_5758456641177717920[9] = 0.0;
out_5758456641177717920[10] = 1.0;
out_5758456641177717920[11] = 0.0;
out_5758456641177717920[12] = 0.0;
out_5758456641177717920[13] = 0.0;
out_5758456641177717920[14] = 0.0;
out_5758456641177717920[15] = 0.0;
out_5758456641177717920[16] = 0.0;
out_5758456641177717920[17] = 0.0;
out_5758456641177717920[18] = 0.0;
out_5758456641177717920[19] = 0.0;
out_5758456641177717920[20] = 1.0;
out_5758456641177717920[21] = 0.0;
out_5758456641177717920[22] = 0.0;
out_5758456641177717920[23] = 0.0;
out_5758456641177717920[24] = 0.0;
out_5758456641177717920[25] = 0.0;
out_5758456641177717920[26] = 0.0;
out_5758456641177717920[27] = 0.0;
out_5758456641177717920[28] = 0.0;
out_5758456641177717920[29] = 0.0;
out_5758456641177717920[30] = 1.0;
out_5758456641177717920[31] = 0.0;
out_5758456641177717920[32] = 0.0;
out_5758456641177717920[33] = 0.0;
out_5758456641177717920[34] = 0.0;
out_5758456641177717920[35] = 0.0;
out_5758456641177717920[36] = 0.0;
out_5758456641177717920[37] = 0.0;
out_5758456641177717920[38] = 0.0;
out_5758456641177717920[39] = 0.0;
out_5758456641177717920[40] = 1.0;
out_5758456641177717920[41] = 0.0;
out_5758456641177717920[42] = 0.0;
out_5758456641177717920[43] = 0.0;
out_5758456641177717920[44] = 0.0;
out_5758456641177717920[45] = 0.0;
out_5758456641177717920[46] = 0.0;
out_5758456641177717920[47] = 0.0;
out_5758456641177717920[48] = 0.0;
out_5758456641177717920[49] = 0.0;
out_5758456641177717920[50] = 1.0;
out_5758456641177717920[51] = 0.0;
out_5758456641177717920[52] = 0.0;
out_5758456641177717920[53] = 0.0;
out_5758456641177717920[54] = 0.0;
out_5758456641177717920[55] = 0.0;
out_5758456641177717920[56] = 0.0;
out_5758456641177717920[57] = 0.0;
out_5758456641177717920[58] = 0.0;
out_5758456641177717920[59] = 0.0;
out_5758456641177717920[60] = 1.0;
out_5758456641177717920[61] = 0.0;
out_5758456641177717920[62] = 0.0;
out_5758456641177717920[63] = 0.0;
out_5758456641177717920[64] = 0.0;
out_5758456641177717920[65] = 0.0;
out_5758456641177717920[66] = 0.0;
out_5758456641177717920[67] = 0.0;
out_5758456641177717920[68] = 0.0;
out_5758456641177717920[69] = 0.0;
out_5758456641177717920[70] = 1.0;
out_5758456641177717920[71] = 0.0;
out_5758456641177717920[72] = 0.0;
out_5758456641177717920[73] = 0.0;
out_5758456641177717920[74] = 0.0;
out_5758456641177717920[75] = 0.0;
out_5758456641177717920[76] = 0.0;
out_5758456641177717920[77] = 0.0;
out_5758456641177717920[78] = 0.0;
out_5758456641177717920[79] = 0.0;
out_5758456641177717920[80] = 1.0;
void H_mod_fun(double *state, double *out_2752971634021862652) {
out_2752971634021862652[0] = 1.0;
out_2752971634021862652[1] = 0.0;
out_2752971634021862652[2] = 0.0;
out_2752971634021862652[3] = 0.0;
out_2752971634021862652[4] = 0.0;
out_2752971634021862652[5] = 0.0;
out_2752971634021862652[6] = 0.0;
out_2752971634021862652[7] = 0.0;
out_2752971634021862652[8] = 0.0;
out_2752971634021862652[9] = 0.0;
out_2752971634021862652[10] = 1.0;
out_2752971634021862652[11] = 0.0;
out_2752971634021862652[12] = 0.0;
out_2752971634021862652[13] = 0.0;
out_2752971634021862652[14] = 0.0;
out_2752971634021862652[15] = 0.0;
out_2752971634021862652[16] = 0.0;
out_2752971634021862652[17] = 0.0;
out_2752971634021862652[18] = 0.0;
out_2752971634021862652[19] = 0.0;
out_2752971634021862652[20] = 1.0;
out_2752971634021862652[21] = 0.0;
out_2752971634021862652[22] = 0.0;
out_2752971634021862652[23] = 0.0;
out_2752971634021862652[24] = 0.0;
out_2752971634021862652[25] = 0.0;
out_2752971634021862652[26] = 0.0;
out_2752971634021862652[27] = 0.0;
out_2752971634021862652[28] = 0.0;
out_2752971634021862652[29] = 0.0;
out_2752971634021862652[30] = 1.0;
out_2752971634021862652[31] = 0.0;
out_2752971634021862652[32] = 0.0;
out_2752971634021862652[33] = 0.0;
out_2752971634021862652[34] = 0.0;
out_2752971634021862652[35] = 0.0;
out_2752971634021862652[36] = 0.0;
out_2752971634021862652[37] = 0.0;
out_2752971634021862652[38] = 0.0;
out_2752971634021862652[39] = 0.0;
out_2752971634021862652[40] = 1.0;
out_2752971634021862652[41] = 0.0;
out_2752971634021862652[42] = 0.0;
out_2752971634021862652[43] = 0.0;
out_2752971634021862652[44] = 0.0;
out_2752971634021862652[45] = 0.0;
out_2752971634021862652[46] = 0.0;
out_2752971634021862652[47] = 0.0;
out_2752971634021862652[48] = 0.0;
out_2752971634021862652[49] = 0.0;
out_2752971634021862652[50] = 1.0;
out_2752971634021862652[51] = 0.0;
out_2752971634021862652[52] = 0.0;
out_2752971634021862652[53] = 0.0;
out_2752971634021862652[54] = 0.0;
out_2752971634021862652[55] = 0.0;
out_2752971634021862652[56] = 0.0;
out_2752971634021862652[57] = 0.0;
out_2752971634021862652[58] = 0.0;
out_2752971634021862652[59] = 0.0;
out_2752971634021862652[60] = 1.0;
out_2752971634021862652[61] = 0.0;
out_2752971634021862652[62] = 0.0;
out_2752971634021862652[63] = 0.0;
out_2752971634021862652[64] = 0.0;
out_2752971634021862652[65] = 0.0;
out_2752971634021862652[66] = 0.0;
out_2752971634021862652[67] = 0.0;
out_2752971634021862652[68] = 0.0;
out_2752971634021862652[69] = 0.0;
out_2752971634021862652[70] = 1.0;
out_2752971634021862652[71] = 0.0;
out_2752971634021862652[72] = 0.0;
out_2752971634021862652[73] = 0.0;
out_2752971634021862652[74] = 0.0;
out_2752971634021862652[75] = 0.0;
out_2752971634021862652[76] = 0.0;
out_2752971634021862652[77] = 0.0;
out_2752971634021862652[78] = 0.0;
out_2752971634021862652[79] = 0.0;
out_2752971634021862652[80] = 1.0;
}
void f_fun(double *state, double dt, double *out_730595102125265078) {
out_730595102125265078[0] = state[0];
out_730595102125265078[1] = state[1];
out_730595102125265078[2] = state[2];
out_730595102125265078[3] = state[3];
out_730595102125265078[4] = state[4];
out_730595102125265078[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_730595102125265078[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_730595102125265078[7] = state[7];
out_730595102125265078[8] = state[8];
void f_fun(double *state, double dt, double *out_7658700760911629364) {
out_7658700760911629364[0] = state[0];
out_7658700760911629364[1] = state[1];
out_7658700760911629364[2] = state[2];
out_7658700760911629364[3] = state[3];
out_7658700760911629364[4] = state[4];
out_7658700760911629364[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_7658700760911629364[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_7658700760911629364[7] = state[7];
out_7658700760911629364[8] = state[8];
}
void F_fun(double *state, double dt, double *out_7198115545780111359) {
out_7198115545780111359[0] = 1;
out_7198115545780111359[1] = 0;
out_7198115545780111359[2] = 0;
out_7198115545780111359[3] = 0;
out_7198115545780111359[4] = 0;
out_7198115545780111359[5] = 0;
out_7198115545780111359[6] = 0;
out_7198115545780111359[7] = 0;
out_7198115545780111359[8] = 0;
out_7198115545780111359[9] = 0;
out_7198115545780111359[10] = 1;
out_7198115545780111359[11] = 0;
out_7198115545780111359[12] = 0;
out_7198115545780111359[13] = 0;
out_7198115545780111359[14] = 0;
out_7198115545780111359[15] = 0;
out_7198115545780111359[16] = 0;
out_7198115545780111359[17] = 0;
out_7198115545780111359[18] = 0;
out_7198115545780111359[19] = 0;
out_7198115545780111359[20] = 1;
out_7198115545780111359[21] = 0;
out_7198115545780111359[22] = 0;
out_7198115545780111359[23] = 0;
out_7198115545780111359[24] = 0;
out_7198115545780111359[25] = 0;
out_7198115545780111359[26] = 0;
out_7198115545780111359[27] = 0;
out_7198115545780111359[28] = 0;
out_7198115545780111359[29] = 0;
out_7198115545780111359[30] = 1;
out_7198115545780111359[31] = 0;
out_7198115545780111359[32] = 0;
out_7198115545780111359[33] = 0;
out_7198115545780111359[34] = 0;
out_7198115545780111359[35] = 0;
out_7198115545780111359[36] = 0;
out_7198115545780111359[37] = 0;
out_7198115545780111359[38] = 0;
out_7198115545780111359[39] = 0;
out_7198115545780111359[40] = 1;
out_7198115545780111359[41] = 0;
out_7198115545780111359[42] = 0;
out_7198115545780111359[43] = 0;
out_7198115545780111359[44] = 0;
out_7198115545780111359[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_7198115545780111359[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_7198115545780111359[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_7198115545780111359[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_7198115545780111359[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_7198115545780111359[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_7198115545780111359[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_7198115545780111359[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_7198115545780111359[53] = -9.8100000000000005*dt;
out_7198115545780111359[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_7198115545780111359[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_7198115545780111359[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_7198115545780111359[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_7198115545780111359[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_7198115545780111359[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_7198115545780111359[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_7198115545780111359[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_7198115545780111359[62] = 0;
out_7198115545780111359[63] = 0;
out_7198115545780111359[64] = 0;
out_7198115545780111359[65] = 0;
out_7198115545780111359[66] = 0;
out_7198115545780111359[67] = 0;
out_7198115545780111359[68] = 0;
out_7198115545780111359[69] = 0;
out_7198115545780111359[70] = 1;
out_7198115545780111359[71] = 0;
out_7198115545780111359[72] = 0;
out_7198115545780111359[73] = 0;
out_7198115545780111359[74] = 0;
out_7198115545780111359[75] = 0;
out_7198115545780111359[76] = 0;
out_7198115545780111359[77] = 0;
out_7198115545780111359[78] = 0;
out_7198115545780111359[79] = 0;
out_7198115545780111359[80] = 1;
void F_fun(double *state, double dt, double *out_6351856658156694456) {
out_6351856658156694456[0] = 1;
out_6351856658156694456[1] = 0;
out_6351856658156694456[2] = 0;
out_6351856658156694456[3] = 0;
out_6351856658156694456[4] = 0;
out_6351856658156694456[5] = 0;
out_6351856658156694456[6] = 0;
out_6351856658156694456[7] = 0;
out_6351856658156694456[8] = 0;
out_6351856658156694456[9] = 0;
out_6351856658156694456[10] = 1;
out_6351856658156694456[11] = 0;
out_6351856658156694456[12] = 0;
out_6351856658156694456[13] = 0;
out_6351856658156694456[14] = 0;
out_6351856658156694456[15] = 0;
out_6351856658156694456[16] = 0;
out_6351856658156694456[17] = 0;
out_6351856658156694456[18] = 0;
out_6351856658156694456[19] = 0;
out_6351856658156694456[20] = 1;
out_6351856658156694456[21] = 0;
out_6351856658156694456[22] = 0;
out_6351856658156694456[23] = 0;
out_6351856658156694456[24] = 0;
out_6351856658156694456[25] = 0;
out_6351856658156694456[26] = 0;
out_6351856658156694456[27] = 0;
out_6351856658156694456[28] = 0;
out_6351856658156694456[29] = 0;
out_6351856658156694456[30] = 1;
out_6351856658156694456[31] = 0;
out_6351856658156694456[32] = 0;
out_6351856658156694456[33] = 0;
out_6351856658156694456[34] = 0;
out_6351856658156694456[35] = 0;
out_6351856658156694456[36] = 0;
out_6351856658156694456[37] = 0;
out_6351856658156694456[38] = 0;
out_6351856658156694456[39] = 0;
out_6351856658156694456[40] = 1;
out_6351856658156694456[41] = 0;
out_6351856658156694456[42] = 0;
out_6351856658156694456[43] = 0;
out_6351856658156694456[44] = 0;
out_6351856658156694456[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_6351856658156694456[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_6351856658156694456[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_6351856658156694456[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_6351856658156694456[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_6351856658156694456[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_6351856658156694456[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_6351856658156694456[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_6351856658156694456[53] = -9.8100000000000005*dt;
out_6351856658156694456[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_6351856658156694456[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_6351856658156694456[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_6351856658156694456[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_6351856658156694456[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_6351856658156694456[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_6351856658156694456[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_6351856658156694456[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_6351856658156694456[62] = 0;
out_6351856658156694456[63] = 0;
out_6351856658156694456[64] = 0;
out_6351856658156694456[65] = 0;
out_6351856658156694456[66] = 0;
out_6351856658156694456[67] = 0;
out_6351856658156694456[68] = 0;
out_6351856658156694456[69] = 0;
out_6351856658156694456[70] = 1;
out_6351856658156694456[71] = 0;
out_6351856658156694456[72] = 0;
out_6351856658156694456[73] = 0;
out_6351856658156694456[74] = 0;
out_6351856658156694456[75] = 0;
out_6351856658156694456[76] = 0;
out_6351856658156694456[77] = 0;
out_6351856658156694456[78] = 0;
out_6351856658156694456[79] = 0;
out_6351856658156694456[80] = 1;
}
void h_25(double *state, double *unused, double *out_5441109048213736058) {
out_5441109048213736058[0] = state[6];
void h_25(double *state, double *unused, double *out_2115125997200973707) {
out_2115125997200973707[0] = state[6];
}
void H_25(double *state, double *unused, double *out_4766949399557973891) {
out_4766949399557973891[0] = 0;
out_4766949399557973891[1] = 0;
out_4766949399557973891[2] = 0;
out_4766949399557973891[3] = 0;
out_4766949399557973891[4] = 0;
out_4766949399557973891[5] = 0;
out_4766949399557973891[6] = 1;
out_4766949399557973891[7] = 0;
out_4766949399557973891[8] = 0;
void H_25(double *state, double *unused, double *out_6275952284011354329) {
out_6275952284011354329[0] = 0;
out_6275952284011354329[1] = 0;
out_6275952284011354329[2] = 0;
out_6275952284011354329[3] = 0;
out_6275952284011354329[4] = 0;
out_6275952284011354329[5] = 0;
out_6275952284011354329[6] = 1;
out_6275952284011354329[7] = 0;
out_6275952284011354329[8] = 0;
}
void h_24(double *state, double *unused, double *out_2797667639827089179) {
out_2797667639827089179[0] = state[4];
out_2797667639827089179[1] = state[5];
void h_24(double *state, double *unused, double *out_7937179482135297626) {
out_7937179482135297626[0] = state[4];
out_7937179482135297626[1] = state[5];
}
void H_24(double *state, double *unused, double *out_4461115786511221334) {
out_4461115786511221334[0] = 0;
out_4461115786511221334[1] = 0;
out_4461115786511221334[2] = 0;
out_4461115786511221334[3] = 0;
out_4461115786511221334[4] = 1;
out_4461115786511221334[5] = 0;
out_4461115786511221334[6] = 0;
out_4461115786511221334[7] = 0;
out_4461115786511221334[8] = 0;
out_4461115786511221334[9] = 0;
out_4461115786511221334[10] = 0;
out_4461115786511221334[11] = 0;
out_4461115786511221334[12] = 0;
out_4461115786511221334[13] = 0;
out_4461115786511221334[14] = 1;
out_4461115786511221334[15] = 0;
out_4461115786511221334[16] = 0;
out_4461115786511221334[17] = 0;
void H_24(double *state, double *unused, double *out_8612866190895874151) {
out_8612866190895874151[0] = 0;
out_8612866190895874151[1] = 0;
out_8612866190895874151[2] = 0;
out_8612866190895874151[3] = 0;
out_8612866190895874151[4] = 1;
out_8612866190895874151[5] = 0;
out_8612866190895874151[6] = 0;
out_8612866190895874151[7] = 0;
out_8612866190895874151[8] = 0;
out_8612866190895874151[9] = 0;
out_8612866190895874151[10] = 0;
out_8612866190895874151[11] = 0;
out_8612866190895874151[12] = 0;
out_8612866190895874151[13] = 0;
out_8612866190895874151[14] = 1;
out_8612866190895874151[15] = 0;
out_8612866190895874151[16] = 0;
out_8612866190895874151[17] = 0;
}
void h_30(double *state, double *unused, double *out_1188271489450022901) {
out_1188271489450022901[0] = state[4];
void h_30(double *state, double *unused, double *out_8646825364085210282) {
out_8646825364085210282[0] = state[4];
}
void H_30(double *state, double *unused, double *out_9152098344023969527) {
out_9152098344023969527[0] = 0;
out_9152098344023969527[1] = 0;
out_9152098344023969527[2] = 0;
out_9152098344023969527[3] = 0;
out_9152098344023969527[4] = 1;
out_9152098344023969527[5] = 0;
out_9152098344023969527[6] = 0;
out_9152098344023969527[7] = 0;
out_9152098344023969527[8] = 0;
void H_30(double *state, double *unused, double *out_6146613336868114259) {
out_6146613336868114259[0] = 0;
out_6146613336868114259[1] = 0;
out_6146613336868114259[2] = 0;
out_6146613336868114259[3] = 0;
out_6146613336868114259[4] = 1;
out_6146613336868114259[5] = 0;
out_6146613336868114259[6] = 0;
out_6146613336868114259[7] = 0;
out_6146613336868114259[8] = 0;
}
void h_26(double *state, double *unused, double *out_6530138725336053464) {
out_6530138725336053464[0] = state[7];
void h_26(double *state, double *unused, double *out_7195513388815643683) {
out_7195513388815643683[0] = state[7];
}
void H_26(double *state, double *unused, double *out_8508452718432030115) {
out_8508452718432030115[0] = 0;
out_8508452718432030115[1] = 0;
out_8508452718432030115[2] = 0;
out_8508452718432030115[3] = 0;
out_8508452718432030115[4] = 0;
out_8508452718432030115[5] = 0;
out_8508452718432030115[6] = 0;
out_8508452718432030115[7] = 1;
out_8508452718432030115[8] = 0;
void H_26(double *state, double *unused, double *out_2534448965137298105) {
out_2534448965137298105[0] = 0;
out_2534448965137298105[1] = 0;
out_2534448965137298105[2] = 0;
out_2534448965137298105[3] = 0;
out_2534448965137298105[4] = 0;
out_2534448965137298105[5] = 0;
out_2534448965137298105[6] = 0;
out_2534448965137298105[7] = 1;
out_2534448965137298105[8] = 0;
}
void h_27(double *state, double *unused, double *out_8003961757834969913) {
out_8003961757834969913[0] = state[3];
void h_27(double *state, double *unused, double *out_3029764065914673830) {
out_3029764065914673830[0] = state[3];
}
void H_27(double *state, double *unused, double *out_6977335032223544616) {
out_6977335032223544616[0] = 0;
out_6977335032223544616[1] = 0;
out_6977335032223544616[2] = 0;
out_6977335032223544616[3] = 1;
out_6977335032223544616[4] = 0;
out_6977335032223544616[5] = 0;
out_6977335032223544616[6] = 0;
out_6977335032223544616[7] = 0;
out_6977335032223544616[8] = 0;
void H_27(double *state, double *unused, double *out_3971850025067689348) {
out_3971850025067689348[0] = 0;
out_3971850025067689348[1] = 0;
out_3971850025067689348[2] = 0;
out_3971850025067689348[3] = 1;
out_3971850025067689348[4] = 0;
out_3971850025067689348[5] = 0;
out_3971850025067689348[6] = 0;
out_3971850025067689348[7] = 0;
out_3971850025067689348[8] = 0;
}
void h_29(double *state, double *unused, double *out_1416133831358674750) {
out_1416133831358674750[0] = state[1];
void h_29(double *state, double *unused, double *out_4733501190905998611) {
out_4733501190905998611[0] = state[1];
}
void H_29(double *state, double *unused, double *out_8784414385371189905) {
out_8784414385371189905[0] = 0;
out_8784414385371189905[1] = 1;
out_8784414385371189905[2] = 0;
out_8784414385371189905[3] = 0;
out_8784414385371189905[4] = 0;
out_8784414385371189905[5] = 0;
out_8784414385371189905[6] = 0;
out_8784414385371189905[7] = 0;
out_8784414385371189905[8] = 0;
void H_29(double *state, double *unused, double *out_6656844681182506443) {
out_6656844681182506443[0] = 0;
out_6656844681182506443[1] = 1;
out_6656844681182506443[2] = 0;
out_6656844681182506443[3] = 0;
out_6656844681182506443[4] = 0;
out_6656844681182506443[5] = 0;
out_6656844681182506443[6] = 0;
out_6656844681182506443[7] = 0;
out_6656844681182506443[8] = 0;
}
void h_28(double *state, double *unused, double *out_7400645873158492396) {
out_7400645873158492396[0] = state[0];
void h_28(double *state, double *unused, double *out_6095421901546968827) {
out_6095421901546968827[0] = state[0];
}
void H_28(double *state, double *unused, double *out_6820784113805863654) {
out_6820784113805863654[0] = 1;
out_6820784113805863654[1] = 0;
out_6820784113805863654[2] = 0;
out_6820784113805863654[3] = 0;
out_6820784113805863654[4] = 0;
out_6820784113805863654[5] = 0;
out_6820784113805863654[6] = 0;
out_6820784113805863654[7] = 0;
out_6820784113805863654[8] = 0;
void H_28(double *state, double *unused, double *out_2823911718871392259) {
out_2823911718871392259[0] = 1;
out_2823911718871392259[1] = 0;
out_2823911718871392259[2] = 0;
out_2823911718871392259[3] = 0;
out_2823911718871392259[4] = 0;
out_2823911718871392259[5] = 0;
out_2823911718871392259[6] = 0;
out_2823911718871392259[7] = 0;
out_2823911718871392259[8] = 0;
}
void h_31(double *state, double *unused, double *out_1803659519959713708) {
out_1803659519959713708[0] = state[8];
void h_31(double *state, double *unused, double *out_6524751479517568177) {
out_6524751479517568177[0] = state[8];
}
void H_31(double *state, double *unused, double *out_9134660820665381591) {
out_9134660820665381591[0] = 0;
out_9134660820665381591[1] = 0;
out_9134660820665381591[2] = 0;
out_9134660820665381591[3] = 0;
out_9134660820665381591[4] = 0;
out_9134660820665381591[5] = 0;
out_9134660820665381591[6] = 0;
out_9134660820665381591[7] = 0;
out_9134660820665381591[8] = 1;
void H_31(double *state, double *unused, double *out_6306598245888314757) {
out_6306598245888314757[0] = 0;
out_6306598245888314757[1] = 0;
out_6306598245888314757[2] = 0;
out_6306598245888314757[3] = 0;
out_6306598245888314757[4] = 0;
out_6306598245888314757[5] = 0;
out_6306598245888314757[6] = 0;
out_6306598245888314757[7] = 0;
out_6306598245888314757[8] = 1;
}
#include <eigen3/Eigen/Dense>
#include <iostream>
@@ -518,68 +518,68 @@ void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_31, H_31, NULL, in_z, in_R, in_ea, MAHA_THRESH_31);
}
void car_err_fun(double *nom_x, double *delta_x, double *out_5070392900769873758) {
err_fun(nom_x, delta_x, out_5070392900769873758);
void car_err_fun(double *nom_x, double *delta_x, double *out_5744499419236567507) {
err_fun(nom_x, delta_x, out_5744499419236567507);
}
void car_inv_err_fun(double *nom_x, double *true_x, double *out_3742760733154016051) {
inv_err_fun(nom_x, true_x, out_3742760733154016051);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_1184805137205037163) {
inv_err_fun(nom_x, true_x, out_1184805137205037163);
}
void car_H_mod_fun(double *state, double *out_5758456641177717920) {
H_mod_fun(state, out_5758456641177717920);
void car_H_mod_fun(double *state, double *out_2752971634021862652) {
H_mod_fun(state, out_2752971634021862652);
}
void car_f_fun(double *state, double dt, double *out_730595102125265078) {
f_fun(state, dt, out_730595102125265078);
void car_f_fun(double *state, double dt, double *out_7658700760911629364) {
f_fun(state, dt, out_7658700760911629364);
}
void car_F_fun(double *state, double dt, double *out_7198115545780111359) {
F_fun(state, dt, out_7198115545780111359);
void car_F_fun(double *state, double dt, double *out_6351856658156694456) {
F_fun(state, dt, out_6351856658156694456);
}
void car_h_25(double *state, double *unused, double *out_5441109048213736058) {
h_25(state, unused, out_5441109048213736058);
void car_h_25(double *state, double *unused, double *out_2115125997200973707) {
h_25(state, unused, out_2115125997200973707);
}
void car_H_25(double *state, double *unused, double *out_4766949399557973891) {
H_25(state, unused, out_4766949399557973891);
void car_H_25(double *state, double *unused, double *out_6275952284011354329) {
H_25(state, unused, out_6275952284011354329);
}
void car_h_24(double *state, double *unused, double *out_2797667639827089179) {
h_24(state, unused, out_2797667639827089179);
void car_h_24(double *state, double *unused, double *out_7937179482135297626) {
h_24(state, unused, out_7937179482135297626);
}
void car_H_24(double *state, double *unused, double *out_4461115786511221334) {
H_24(state, unused, out_4461115786511221334);
void car_H_24(double *state, double *unused, double *out_8612866190895874151) {
H_24(state, unused, out_8612866190895874151);
}
void car_h_30(double *state, double *unused, double *out_1188271489450022901) {
h_30(state, unused, out_1188271489450022901);
void car_h_30(double *state, double *unused, double *out_8646825364085210282) {
h_30(state, unused, out_8646825364085210282);
}
void car_H_30(double *state, double *unused, double *out_9152098344023969527) {
H_30(state, unused, out_9152098344023969527);
void car_H_30(double *state, double *unused, double *out_6146613336868114259) {
H_30(state, unused, out_6146613336868114259);
}
void car_h_26(double *state, double *unused, double *out_6530138725336053464) {
h_26(state, unused, out_6530138725336053464);
void car_h_26(double *state, double *unused, double *out_7195513388815643683) {
h_26(state, unused, out_7195513388815643683);
}
void car_H_26(double *state, double *unused, double *out_8508452718432030115) {
H_26(state, unused, out_8508452718432030115);
void car_H_26(double *state, double *unused, double *out_2534448965137298105) {
H_26(state, unused, out_2534448965137298105);
}
void car_h_27(double *state, double *unused, double *out_8003961757834969913) {
h_27(state, unused, out_8003961757834969913);
void car_h_27(double *state, double *unused, double *out_3029764065914673830) {
h_27(state, unused, out_3029764065914673830);
}
void car_H_27(double *state, double *unused, double *out_6977335032223544616) {
H_27(state, unused, out_6977335032223544616);
void car_H_27(double *state, double *unused, double *out_3971850025067689348) {
H_27(state, unused, out_3971850025067689348);
}
void car_h_29(double *state, double *unused, double *out_1416133831358674750) {
h_29(state, unused, out_1416133831358674750);
void car_h_29(double *state, double *unused, double *out_4733501190905998611) {
h_29(state, unused, out_4733501190905998611);
}
void car_H_29(double *state, double *unused, double *out_8784414385371189905) {
H_29(state, unused, out_8784414385371189905);
void car_H_29(double *state, double *unused, double *out_6656844681182506443) {
H_29(state, unused, out_6656844681182506443);
}
void car_h_28(double *state, double *unused, double *out_7400645873158492396) {
h_28(state, unused, out_7400645873158492396);
void car_h_28(double *state, double *unused, double *out_6095421901546968827) {
h_28(state, unused, out_6095421901546968827);
}
void car_H_28(double *state, double *unused, double *out_6820784113805863654) {
H_28(state, unused, out_6820784113805863654);
void car_H_28(double *state, double *unused, double *out_2823911718871392259) {
H_28(state, unused, out_2823911718871392259);
}
void car_h_31(double *state, double *unused, double *out_1803659519959713708) {
h_31(state, unused, out_1803659519959713708);
void car_h_31(double *state, double *unused, double *out_6524751479517568177) {
h_31(state, unused, out_6524751479517568177);
}
void car_H_31(double *state, double *unused, double *out_9134660820665381591) {
H_31(state, unused, out_9134660820665381591);
void car_H_31(double *state, double *unused, double *out_6306598245888314757) {
H_31(state, unused, out_6306598245888314757);
}
void car_predict(double *in_x, double *in_P, double *in_Q, double dt) {
predict(in_x, in_P, in_Q, dt);
@@ -9,27 +9,27 @@ void car_update_27(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_err_fun(double *nom_x, double *delta_x, double *out_5070392900769873758);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_3742760733154016051);
void car_H_mod_fun(double *state, double *out_5758456641177717920);
void car_f_fun(double *state, double dt, double *out_730595102125265078);
void car_F_fun(double *state, double dt, double *out_7198115545780111359);
void car_h_25(double *state, double *unused, double *out_5441109048213736058);
void car_H_25(double *state, double *unused, double *out_4766949399557973891);
void car_h_24(double *state, double *unused, double *out_2797667639827089179);
void car_H_24(double *state, double *unused, double *out_4461115786511221334);
void car_h_30(double *state, double *unused, double *out_1188271489450022901);
void car_H_30(double *state, double *unused, double *out_9152098344023969527);
void car_h_26(double *state, double *unused, double *out_6530138725336053464);
void car_H_26(double *state, double *unused, double *out_8508452718432030115);
void car_h_27(double *state, double *unused, double *out_8003961757834969913);
void car_H_27(double *state, double *unused, double *out_6977335032223544616);
void car_h_29(double *state, double *unused, double *out_1416133831358674750);
void car_H_29(double *state, double *unused, double *out_8784414385371189905);
void car_h_28(double *state, double *unused, double *out_7400645873158492396);
void car_H_28(double *state, double *unused, double *out_6820784113805863654);
void car_h_31(double *state, double *unused, double *out_1803659519959713708);
void car_H_31(double *state, double *unused, double *out_9134660820665381591);
void car_err_fun(double *nom_x, double *delta_x, double *out_5744499419236567507);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_1184805137205037163);
void car_H_mod_fun(double *state, double *out_2752971634021862652);
void car_f_fun(double *state, double dt, double *out_7658700760911629364);
void car_F_fun(double *state, double dt, double *out_6351856658156694456);
void car_h_25(double *state, double *unused, double *out_2115125997200973707);
void car_H_25(double *state, double *unused, double *out_6275952284011354329);
void car_h_24(double *state, double *unused, double *out_7937179482135297626);
void car_H_24(double *state, double *unused, double *out_8612866190895874151);
void car_h_30(double *state, double *unused, double *out_8646825364085210282);
void car_H_30(double *state, double *unused, double *out_6146613336868114259);
void car_h_26(double *state, double *unused, double *out_7195513388815643683);
void car_H_26(double *state, double *unused, double *out_2534448965137298105);
void car_h_27(double *state, double *unused, double *out_3029764065914673830);
void car_H_27(double *state, double *unused, double *out_3971850025067689348);
void car_h_29(double *state, double *unused, double *out_4733501190905998611);
void car_H_29(double *state, double *unused, double *out_6656844681182506443);
void car_h_28(double *state, double *unused, double *out_6095421901546968827);
void car_H_28(double *state, double *unused, double *out_2823911718871392259);
void car_h_31(double *state, double *unused, double *out_6524751479517568177);
void car_H_31(double *state, double *unused, double *out_6306598245888314757);
void car_predict(double *in_x, double *in_P, double *in_Q, double dt);
void car_set_mass(double x);
void car_set_rotational_inertia(double x);
File diff suppressed because it is too large Load Diff
@@ -5,18 +5,18 @@ void pose_update_4(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void pose_update_10(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_13(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_14(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_err_fun(double *nom_x, double *delta_x, double *out_8299703332077589328);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_4608058237383429265);
void pose_H_mod_fun(double *state, double *out_7885453398379249088);
void pose_f_fun(double *state, double dt, double *out_1688144950946873165);
void pose_F_fun(double *state, double dt, double *out_4851283621730691038);
void pose_h_4(double *state, double *unused, double *out_2329133011647763937);
void pose_H_4(double *state, double *unused, double *out_8639614400433961695);
void pose_h_10(double *state, double *unused, double *out_3294648635274985008);
void pose_H_10(double *state, double *unused, double *out_3780608178453222524);
void pose_h_13(double *state, double *unused, double *out_8905113933939181200);
void pose_H_13(double *state, double *unused, double *out_6594855847943257120);
void pose_h_14(double *state, double *unused, double *out_8324071568749841126);
void pose_H_14(double *state, double *unused, double *out_5843888816936105392);
void pose_err_fun(double *nom_x, double *delta_x, double *out_7899872685628621531);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_1215289726053279423);
void pose_H_mod_fun(double *state, double *out_420741753375595218);
void pose_f_fun(double *state, double dt, double *out_5996963235034927992);
void pose_F_fun(double *state, double dt, double *out_78320000076568703);
void pose_h_4(double *state, double *unused, double *out_2403402202974529453);
void pose_H_4(double *state, double *unused, double *out_5573260138414675953);
void pose_h_10(double *state, double *unused, double *out_1415940429779603810);
void pose_H_10(double *state, double *unused, double *out_5540918162512642087);
void pose_h_13(double *state, double *unused, double *out_8116494657900557165);
void pose_H_13(double *state, double *unused, double *out_8785533963747008754);
void pose_h_14(double *state, double *unused, double *out_2556692697037776409);
void pose_H_14(double *state, double *unused, double *out_8910243078955391134);
void pose_predict(double *in_x, double *in_P, double *in_Q, double dt);
}
-1
View File
@@ -474,7 +474,6 @@ def main(demo=False):
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, left_edge, right_edge)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.valid = modelv2_send.valid
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
fill_driving_model_data(drivingdata_send, modelv2_send)
Binary file not shown.
+11
View File
@@ -29,6 +29,12 @@ enum SpiError {
const unsigned int SPI_ACK_TIMEOUT = 500; // milliseconds
const std::string SPI_DEVICE = "/dev/spidev0.0";
// TODO: fix SPI turnaround synchronization at the protocol level.
static uint64_t spi_last_bus_activity_ns = 0; // protected by hw_lock
static void wait_for_spi_turnaround(uint64_t start_ns) {
while ((nanos_since_boot() - start_ns) < 400000) {}
}
class LockEx {
public:
@@ -319,6 +325,8 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
assert(tx_len < SPI_BUF_SIZE);
assert(max_rx_len < SPI_BUF_SIZE);
wait_for_spi_turnaround(spi_last_bus_activity_ns);
xfer_count++;
header = {
.sync = SPI_SYNC,
@@ -347,6 +355,7 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
if (ret < 0) {
goto fail;
}
wait_for_spi_turnaround(nanos_since_boot());
// Send data
if (tx_data != NULL) {
@@ -389,6 +398,7 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
memcpy(rx_data, rx_buf + 3, rx_data_len);
}
spi_last_bus_activity_ns = nanos_since_boot();
return rx_data_len;
fail:
@@ -403,6 +413,7 @@ fail:
}
}
spi_last_bus_activity_ns = nanos_since_boot();
if (ret >= 0) ret = -1;
return ret;
}
@@ -11,15 +11,6 @@ from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPl
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
class PlannerSM(dict):
def __init__(self, radar_frame: int, services: dict):
super().__init__(services)
self.frame = radar_frame
self.logMonoTime = {"radarState": radar_frame}
self.valid = {"radarState": True}
self.alive = {"radarState": True}
class Plant:
messaging_initialized = False
@@ -141,7 +132,7 @@ class Plant:
car_control.carControl.orientationNED = [0., float(pitch), 0.]
# ******** get controlsState messages for plotting ***
sm = PlannerSM(self.rk.frame, {'radarState': radar.radarState,
sm = {'radarState': radar.radarState,
'carState': car_state.carState,
'carControl': car_control.carControl,
'controlsState': control.controlsState,
@@ -150,7 +141,7 @@ class Plant:
'modelV2': model.modelV2,
'carStateSP': car_state_sp.carStateSP,
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
'gpsLocation': gps_data.gpsLocation})
'gpsLocation': gps_data.gpsLocation}
self.planner.update(sm)
self.acceleration = self.planner.output_a_target
if self.planner.output_should_stop:
@@ -27,12 +27,6 @@ DESCRIPTIONS = {
"In relaxed mode sunnypilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " +
"your steering wheel distance button."
),
"AccelPersonalityEnabled": tr_noop(
"Lets you choose how sunnypilot starts, catches up, and settles at the cruise speed. Emergency braking and stopping are unchanged."
),
"AccelPersonality": tr_noop(
"Eco is gentlest, Normal balances a prompt start with smooth catch-up, and Sport is more responsive."
),
"IsLdwEnabled": tr_noop(
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " +
"without a turn signal activated while driving over 31 mph (50 km/h)."
@@ -112,24 +106,6 @@ class TogglesLayout(Widget):
icon="speed_limit.png"
)
self._accel_controller_enabled = toggle_item(
lambda: tr("Enable Accel Controller"),
lambda: tr(DESCRIPTIONS["AccelPersonalityEnabled"]),
self._params.get_bool("AccelPersonalityEnabled"),
callback=self._set_accel_controller_enabled,
icon="speed_limit.png",
)
self._accel_personality_setting = multiple_button_item(
lambda: tr("Acceleration Profile"),
lambda: tr(DESCRIPTIONS["AccelPersonality"]),
buttons=[lambda: tr("Eco"), lambda: tr("Normal"), lambda: tr("Sport")],
button_width=300,
callback=self._set_accel_personality,
selected_index=self._params.get("AccelPersonality", return_default=True),
icon="speed_limit.png"
)
self._toggles = {}
self._locked_toggles = set()
for param, (title, desc, icon, needs_restart) in self._toggle_defs.items():
@@ -159,11 +135,9 @@ class TogglesLayout(Widget):
self._toggles[param] = toggle
# insert longitudinal personality and Accel Controller settings after NDOG toggle
# insert longitudinal personality after NDOG toggle
if param == "DisengageOnAccelerator":
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
self._toggles["AccelPersonalityEnabled"] = self._accel_controller_enabled
self._toggles["AccelPersonality"] = self._accel_personality_setting
self._update_experimental_mode_icon()
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
@@ -184,7 +158,6 @@ class TogglesLayout(Widget):
def _update_toggles(self):
ui_state.update_params()
accel_controller_enabled = self._params.get_bool("AccelPersonalityEnabled")
e2e_description = tr(
"sunnypilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " +
@@ -203,15 +176,11 @@ class TogglesLayout(Widget):
self._toggles["ExperimentalMode"].action_item.set_enabled(True)
self._toggles["ExperimentalMode"].set_description(e2e_description)
self._long_personality_setting.action_item.set_enabled(True)
self._accel_controller_enabled.action_item.set_enabled(True)
self._accel_personality_setting.action_item.set_enabled(True)
else:
# no long for now
self._toggles["ExperimentalMode"].action_item.set_enabled(False)
self._toggles["ExperimentalMode"].action_item.set_state(False)
self._long_personality_setting.action_item.set_enabled(False)
self._accel_controller_enabled.action_item.set_enabled(False)
self._accel_personality_setting.action_item.set_enabled(False)
self._params.remove("ExperimentalMode")
unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.")
@@ -234,8 +203,6 @@ class TogglesLayout(Widget):
# refresh toggles from params to mirror external changes
for param in self._toggle_defs:
self._toggles[param].action_item.set_state(self._params.get_bool(param))
self._accel_controller_enabled.action_item.set_state(accel_controller_enabled)
self._accel_personality_setting.action_item.set_selected_button(self._params.get("AccelPersonality", return_default=True))
# these toggles need restart, block while engaged
for toggle_def in self._toggle_defs:
@@ -280,9 +247,3 @@ class TogglesLayout(Widget):
def _set_longitudinal_personality(self, button_index: int):
self._params.put("LongitudinalPersonality", button_index, block=True)
def _set_accel_personality(self, button_index: int):
self._params.put("AccelPersonality", button_index, block=True)
def _set_accel_controller_enabled(self, state: bool):
self._params.put_bool("AccelPersonalityEnabled", state, block=True)
+2 -8
View File
@@ -14,7 +14,6 @@ from openpilot.system.ui.lib.application import gui_app
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.home import MiciHomeLayoutSP as MiciHomeLayout
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad import OnroadViewContainerSP as AugmentedRoadView
ONROAD_DELAY = 2.5 # seconds
@@ -73,9 +72,6 @@ class MiciMainLayout(Scroller):
# For scroll_to
return self._body_onroad_layout if ui_state.is_body else self._car_onroad_layout
def _should_auto_scroll_to_onroad(self) -> bool:
return True
def _setup_callbacks(self):
self._home_layout.set_callbacks(
on_settings=lambda: gui_app.push_widget(self._settings_layout),
@@ -126,15 +122,13 @@ class MiciMainLayout(Scroller):
# FIXME: these two pops can interrupt user interacting in the settings
if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY:
if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad():
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
self._onroad_time_delay = None
# When car leaves standstill, pop nav stack and scroll to onroad
CS = ui_state.sm["carState"]
if not CS.standstill and self._prev_standstill:
if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad():
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout))
self._prev_standstill = CS.standstill
def _on_interactive_timeout(self):
@@ -42,8 +42,6 @@ class TogglesLayoutMici(NavScroller):
super().__init__()
self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"])
self._accel_controller_enabled = BigParamControl("enable accel controller", "AccelPersonalityEnabled")
self._accel_personality_toggle = BigMultiParamToggle("acceleration profile", "AccelPersonality", ["eco", "normal", "sport"])
self._experimental_btn = BigToggle("experimental mode", initial_state=ui_state.params.get_bool("ExperimentalMode"),
toggle_callback=self._on_experimental_mode)
is_metric_toggle = BigParamControl("use metric units", "IsMetric")
@@ -55,8 +53,6 @@ class TogglesLayoutMici(NavScroller):
self._scroller.add_widgets([
self._personality_toggle,
self._accel_controller_enabled,
self._accel_personality_toggle,
self._experimental_btn,
is_metric_toggle,
ldw_toggle,
@@ -69,7 +65,6 @@ class TogglesLayoutMici(NavScroller):
# Toggle lists
self._refresh_toggles = (
("ExperimentalMode", self._experimental_btn),
("AccelPersonalityEnabled", self._accel_controller_enabled),
("IsMetric", is_metric_toggle),
("IsLdwEnabled", ldw_toggle),
("AlwaysOnDM", always_on_dm_toggle),
@@ -109,23 +104,17 @@ class TogglesLayoutMici(NavScroller):
if ui_state.has_longitudinal_control:
self._experimental_btn.set_visible(True)
self._personality_toggle.set_visible(True)
self._accel_controller_enabled.set_visible(True)
self._accel_personality_toggle.set_visible(True)
else:
# no long for now
self._experimental_btn.set_visible(False)
self._experimental_btn.set_checked(False)
self._personality_toggle.set_visible(False)
self._accel_controller_enabled.set_visible(False)
self._accel_personality_toggle.set_visible(False)
ui_state.params.remove("ExperimentalMode")
# Refresh toggles from params to mirror external changes
for key, item in self._refresh_toggles:
item.set_checked(ui_state.params.get_bool(key))
self._accel_personality_toggle.refresh()
def _on_experimental_mode(self, state: bool):
if state and not ui_state.params.get_bool("ExperimentalModeConfirmed"):
# Don't show enabled state until confirm
@@ -154,8 +154,8 @@ class ModelRenderer(Widget, ModelRendererSP):
self._draw_lane_lines()
self._draw_path(sm)
if render_lead_indicator and radar_state:
self._draw_lead_indicator()
# if render_lead_indicator and radar_state:
# self._draw_lead_indicator()
def _update_raw_points(self, model):
"""Update raw 3D points from model data"""
@@ -383,18 +383,13 @@ class BigMultiParamToggle(BigMultiToggle):
self._load_value()
def _load_value(self):
value = self._params.get(self._param, return_default=True)
index = value if isinstance(value, int) else 0
self.set_value(self._options[max(0, min(index, len(self._options) - 1))])
self.set_value(self._options[self._params.get(self._param) or 0])
def _handle_mouse_release(self, mouse_pos: MousePos):
super()._handle_mouse_release(mouse_pos)
new_idx = self._options.index(self.value)
self._params.put(self._param, new_idx)
def refresh(self):
self._load_value()
class BigParamControl(BigToggle):
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
@@ -143,8 +143,7 @@ class CruiseLayout(Widget):
self.icbm_toggle.show_description(True)
if has_long or has_icbm:
software_cruise_speed = has_long and (not ui_state.CP.pcmCruise or not ui_state.CP_SP.pcmCruiseSpeed)
self.custom_acc_toggle.action_item.set_enabled((software_cruise_speed or has_icbm) and ui_state.is_offroad())
self.custom_acc_toggle.action_item.set_enabled(((has_long and not ui_state.CP.pcmCruise) or has_icbm) and ui_state.is_offroad())
self.dec_toggle.action_item.set_enabled(has_long)
self.scc_v_toggle.action_item.set_enabled(True)
self.scc_m_toggle.action_item.set_enabled(True)
@@ -170,7 +169,7 @@ class CruiseLayout(Widget):
show_custom_acc_desc = True
else:
if has_long or has_icbm:
if has_long and ui_state.CP.pcmCruise and ui_state.CP_SP.pcmCruiseSpeed:
if has_long and ui_state.CP.pcmCruise:
new_custom_acc_desc = tr(ACC_PCMCRUISE_DISABLED_DESCRIPTION)
show_custom_acc_desc = True
else:
@@ -4,6 +4,7 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import os
import re
import time
import pyray as rl
@@ -12,8 +13,7 @@ from openpilot.cereal import custom
from openpilot.sunnypilot.models.helpers import ACTIVE_BUNDLE_KEYS, get_selected_bundle, resolve_bundle_by_ref
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.selfdrive.ui.sunnypilot.model_info import (big_model_state, bundles_for_source, carrying_model, default_model_name,
model_cache_size_mb, queued_name, refresh_in_progress, refresh_model_list)
from openpilot.selfdrive.ui.sunnypilot.model_info import big_model_state, bundles_for_source, carrying_model, default_model_name, queued_name
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import DialogResult, Widget
@@ -21,6 +21,7 @@ from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDial
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets.toggle import ON_COLOR
from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH
from openpilot.system.ui.sunnypilot.lib.styles import style
from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction, ScrollingButtonAction
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp
@@ -39,9 +40,6 @@ class ModelsLayout(Widget):
self._selection_source = None
self._downloading = False
self._verifying = False
self._clearing = False
self._refreshing = False
self._refresh_start: float | None = None
self._last_note = None
self.last_cache_calc_time = 0
@@ -69,14 +67,15 @@ class ModelsLayout(Widget):
self.download_item = download_status_item(lambda: tr("Download") if self._downloading else tr("Model Status"))
self.refresh_item = button_item(tr("Refresh Model List"),
lambda: tr("FETCHING...") if self._refreshing else tr("REFRESH"), "",
self._refresh_models)
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "",
lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0),
ui_state.params.put("ModelManager_LastSyncTime_Chestnut", 0),
gui_app.push_widget(alert_dialog(tr("Fetching Latest Models")))))
self.clear_cache_item = ListItemSP(
title=tr("Clear Model Cache"),
description="",
action_item=NoElideButtonAction(lambda: tr("CLEARING...") if self._clearing else tr("CLEAR")),
action_item=NoElideButtonAction(tr("CLEAR")),
callback=self._clear_cache
)
@@ -116,38 +115,39 @@ class ModelsLayout(Widget):
if lagd_toggle:
desc += f"<br>{tr('Live Steer Delay:')} {ui_state.sm['lateralDelay'].lateralDelay:.3f} s"
elif ui_state.CP is not None:
sw = float(ui_state.params.get("LagdToggleDelay", return_default=True))
sw = float(ui_state.params.get("LagdToggleDelay", "0.2"))
cp = ui_state.CP.steerActuatorDelay
desc += f"<br>{tr('Actuator Delay:')} {cp:.2f} s + {tr('Software Delay:')} {sw:.2f} s = {tr('Total Delay:')} {cp + sw:.2f} s"
self.lagd_toggle.set_description(desc)
@staticmethod
def calculate_cache_size():
return model_cache_size_mb()
cache_size = 0.0
if os.path.exists(CUSTOM_MODEL_PATH):
for file in os.listdir(CUSTOM_MODEL_PATH):
try:
cache_size += os.path.getsize(os.path.join(CUSTOM_MODEL_PATH, file))
except OSError:
continue
return cache_size / (1024**2)
def _clear_cache(self):
def _callback(response):
if response == DialogResult.CONFIRM:
ui_state.params.put_bool("ModelManager_ClearCache", True)
self.clear_cache_item.action_item.set_value(f"{self.calculate_cache_size():.2f} MB")
dialog = ConfirmDialog(tr("This will delete ALL downloaded models from the cache except the currently active model. Are you sure?"),
tr("Clear Cache"), callback=_callback)
gui_app.push_widget(dialog)
def _refresh_models(self):
refresh_model_list()
self._refresh_start = time.monotonic()
def _handle_bundle_download_progress(self):
self.cancel_download_item.set_visible(False)
self._downloading = False
self._verifying = False
self.download_item.set_visible(True)
self._clearing = ui_state.params.get_bool("ModelManager_ClearCache")
if self._clearing:
self.last_cache_calc_time = 0.0 # refresh the size as soon as clearing finishes
elif (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5:
if (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5:
self.last_cache_calc_time = current_time
self.clear_cache_item.action_item.set_value(f"{self.calculate_cache_size():.2f} MB")
@@ -345,13 +345,6 @@ class ModelsLayout(Widget):
self.big_model_item.action_item.set_enabled(offroad)
self.small_model_item.set_description("" if offroad else tr("Only available when vehicle is off, or always offroad mode is on"))
# manager is offroad-only, so an onroad clear would never be serviced
self.clear_cache_item.action_item.set_enabled(offroad and not self._downloading and not self._clearing)
# manager is offroad-only, so a refresh queued onroad would never be serviced
self._refreshing = refresh_in_progress(self._refresh_start)
self.refresh_item.action_item.set_enabled(offroad and not self._downloading and not self._refreshing)
def _render(self, rect):
self._scroller.render(rect)
@@ -23,7 +23,7 @@ DESCRIPTIONS = {
'stop_and_go_hack': tr_noop(
'sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. ' +
'This feature is only applicable to certain models that are able to use longitudinal control. This is an alpha feature. Use at your own risk.'
),
)
}
@@ -1,19 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP
class MiciMainLayoutSP(MiciMainLayout):
def __init__(self):
super().__init__()
scroller = self._scroller
scroller.scroll_panel = GuiScrollPanel2SP(scroller._horizontal, handle_out_of_bounds=not scroller._snap_items)
def _should_auto_scroll_to_onroad(self) -> bool:
return not self._onroad_layout.is_on_info_panel()
@@ -4,18 +4,15 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import time
import pyray as rl
from openpilot.cereal import custom
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog, BigDialog
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog
from openpilot.sunnypilot.models.helpers import ACTIVE_BUNDLE_KEYS, get_selected_bundle
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.ui_state import ui_state, device
from openpilot.selfdrive.ui.sunnypilot.model_info import (active_source, big_model_state, bundles_for_source, carrying_model,
default_model_name, model_cache_size_mb, model_info, queued_name,
refresh_in_progress, refresh_model_list)
default_model_name, model_info, queued_name)
from openpilot.system.ui.lib.application import FontWeight, gui_app
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
@@ -84,18 +81,10 @@ class ModelsLayoutMici(NavScroller):
self.select_model_btn = BigButton(tr("select model"))
self.select_model_btn.set_click_callback(self._show_folders)
self.refresh_btn = BigButton(tr("refresh models"))
self.refresh_btn.set_click_callback(self._refresh_models)
self._refresh_start: float | None = None
self.cancel_download_btn = BigButton(tr("cancel download"))
self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadRef"))
self.clear_cache_btn = BigButton(tr("clear cache"), value=f"{model_cache_size_mb():.1f} MB")
self.clear_cache_btn.set_click_callback(self._confirm_clear_cache)
self._cache_size_time = 0.0
self.main_items = [self.current_model_info, self.select_model_btn, self.cancel_download_btn, self.refresh_btn, self.clear_cache_btn]
self.main_items = [self.current_model_info, self.select_model_btn, self.cancel_download_btn]
self._scroller.add_widgets(self.main_items)
@property
@@ -173,15 +162,6 @@ class ModelsLayoutMici(NavScroller):
ui_state.params.remove(ACTIVE_BUNDLE_KEYS[source])
self._pop_to_main()
def _confirm_clear_cache(self):
icon = gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64)
gui_app.push_widget(BigConfirmationDialog(f"{tr('slide to')}\n{tr('clear cache')}", icon,
lambda: ui_state.params.put_bool("ModelManager_ClearCache", True), red=True))
def _refresh_models(self):
refresh_model_list()
self._refresh_start = time.monotonic()
def _select_folder(self, folder_name):
source = self._selection_source
if source is None: # folders are only reachable after picking a hardware
@@ -225,21 +205,6 @@ class ModelsLayoutMici(NavScroller):
device.set_override_interactive_timeout(None)
self._was_downloading = is_downloading
# manager is offroad-only, so an onroad clear would never be serviced
clearing = ui_state.params.get_bool("ModelManager_ClearCache")
self.clear_cache_btn.set_enabled(ui_state.is_offroad() and not is_downloading and not clearing)
if clearing:
self.clear_cache_btn.set_value(tr("clearing..."))
self._cache_size_time = 0.0 # refresh the size as soon as clearing finishes
elif (now := time.monotonic()) - self._cache_size_time > 0.5:
self._cache_size_time = now
self.clear_cache_btn.set_value(f"{model_cache_size_mb():.1f} MB")
# manager is offroad-only, so a refresh queued onroad would never be serviced
refreshing = refresh_in_progress(self._refresh_start)
self.refresh_btn.set_enabled(ui_state.is_offroad() and not is_downloading and not refreshing)
self.refresh_btn.set_value(tr("fetching...") if refreshing else "")
self.current_model_info.current_model_header.set_text(tr("active model"))
active_text, info_header, info_text = _model_info()
self.current_model_info.current_model_text.set_text(active_text)
@@ -1,64 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from collections.abc import Callable
import pyray as rl
from openpilot.system.ui.lib.application import gui_app
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroller_sp import ScrollerSP
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.augmented_road_view import AugmentedRoadViewSP
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad_info_panel import OnroadInfoPanel
CONFIDENCE_BALL_VISIBLE_RATIO = 0.4
HORIZONTAL_SETTLE_PX = 5
HORIZONTAL_RESET_RATIO = 0.5
class OnroadViewContainerSP(ScrollerSP):
def __init__(self, bookmark_callback=None):
super().__init__(horizontal=False, snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False)
self.road_view = AugmentedRoadViewSP(bookmark_callback=bookmark_callback)
self.onroad_info_panel = OnroadInfoPanel(bookmark_callback=bookmark_callback)
self._scroller.add_widgets([
self.road_view,
self.onroad_info_panel,
])
self._scroller.set_reset_scroll_at_show(False)
self._scroller.set_scrolling_enabled(lambda: abs(self.rect.x) < HORIZONTAL_SETTLE_PX)
for child in (self.road_view, self.onroad_info_panel):
inner_touch_valid = child._touch_valid_callback
child.set_touch_valid_callback(
lambda inner=inner_touch_valid: self._touch_valid() and (inner() if inner else True)
)
def set_rect(self, rect: rl.Rectangle):
super().set_rect(rect)
self.road_view.set_rect(rect)
self.onroad_info_panel.set_rect(rect)
return self
def is_swiping_left(self) -> bool:
return self.road_view.is_swiping_left() or self.onroad_info_panel.is_swiping_left()
def set_click_callback(self, click_callback: Callable[[], None] | None) -> None:
self.road_view.set_click_callback(click_callback)
self.onroad_info_panel.set_click_callback(click_callback)
def is_on_info_panel(self) -> bool:
"""True when scrolled past halfway toward onroad_info_panel (used by main layout
to skip auto-pop-back-to-camera while user is reading the info panel)."""
return abs(self._scroller.scroll_panel.get_offset()) > self._rect.height / 2
def _render(self, rect: rl.Rectangle):
if abs(self.rect.x) > gui_app.width * HORIZONTAL_RESET_RATIO:
self._scroller.scroll_panel.set_offset(0)
vertical_offset = self._scroller.scroll_panel.get_offset()
show_ball = abs(vertical_offset) < rect.height * CONFIDENCE_BALL_VISIBLE_RATIO
self.road_view.set_show_confidence_ball(show_ball)
super()._render(rect)
@@ -1,403 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from dataclasses import dataclass, field
from openpilot.common.constants import CV
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.mici.onroad.alert_renderer import AlertRenderer
from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import BookmarkIcon
METER_TO_KM = 0.001
METER_TO_MILE = 0.000621371
CONTENT_MARGIN = 16
SPEED_LIMIT_SIGN_WIDTH = 146
VIENNA_SIGN_SIZE = 146
MUTCD_SIGN_HEIGHT = 178
OFFSET_BADGE_SIZE = 50
OFFSET_BADGE_PANEL_PADDING = 4
MUTCD_OFFSET_SIGN_Y_SHIFT = 6
VIENNA_BADGE_X_RATIO = 0.80
VIENNA_BADGE_UPCOMING_X_RATIO = 0.70
VIENNA_BADGE_Y_RATIO = -0.82
UPCOMING_SIGN_SIZE_RATIO = 0.76
UPCOMING_SIGN_OVERLAP_RATIO = 0.05
UNIT_FONT_SIZE = 40
SPEED_FONT_SIZE = 114
ROAD_FONT_SIZE = 32
SCC_TAG_WIDTH = 78
SCC_TAG_HEIGHT = 30
SCC_TAG_GAP = 5
COLUMN_GAP = 12
@dataclass(frozen=True)
class OnroadInfoPanelColors:
white: rl.Color = rl.WHITE
black: rl.Color = rl.BLACK
red: rl.Color = field(default_factory=lambda: rl.Color(255, 0, 0, 255))
green: rl.Color = field(default_factory=lambda: rl.Color(0, 255, 0, 255))
grey: rl.Color = field(default_factory=lambda: rl.Color(190, 195, 190, 255))
light_grey: rl.Color = field(default_factory=lambda: rl.Color(200, 200, 200, 255))
dark_grey: rl.Color = field(default_factory=lambda: rl.Color(100, 100, 100, 255))
bg_dark: rl.Color = field(default_factory=lambda: rl.Color(0, 0, 0, 255))
card_bg: rl.Color = field(default_factory=lambda: rl.Color(50, 50, 50, 200))
badge_bg: rl.Color = field(default_factory=lambda: rl.Color(60, 60, 60, 255))
COLORS = OnroadInfoPanelColors()
class OnroadInfoPanel(Widget):
def __init__(self, bookmark_callback=None):
super().__init__()
self.speed_limit: float = 0.0
self.speed_limit_valid: bool = False
self.speed_limit_offset: float = 0.0
self.next_speed_limit: float = 0.0
self.next_speed_limit_distance: float = 0.0
self.road_name: str = ""
self.current_speed: float = 0.0
self.set_speed: float = 0.0
self.cruise_enabled: bool = False
self._sign_slide: float = 0.0
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
self._marquee_offset: float = 0.0
self._marquee_direction: int = 1
self._marquee_pause_timer: float = 0.0
self._marquee_speed: float = 40.0
self._marquee_pause_duration: float = 1.5
self._alert_renderer = AlertRenderer()
self._alert_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps)
self._bookmark_icon = BookmarkIcon(bookmark_callback)
def is_swiping_left(self) -> bool:
return self._bookmark_icon.is_swiping_left()
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
# Mirror stock AugmentedRoadView: suppress click while bookmark gesture active
if not self._bookmark_icon.interacting():
super()._handle_mouse_release(mouse_pos)
def _update_state(self) -> None:
sm = ui_state.sm
speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
if sm.valid["longitudinalPlanSP"]:
lp_sp = sm["longitudinalPlanSP"]
resolver = lp_sp.speedLimit.resolver
self.speed_limit = resolver.speedLimit * speed_conv
self.speed_limit_valid = resolver.speedLimitValid
self.speed_limit_offset = resolver.speedLimitOffset * speed_conv
if sm.valid["liveMapDataSP"]:
lmd = sm["liveMapDataSP"]
self.next_speed_limit = lmd.speedLimitAhead * speed_conv
self.next_speed_limit_distance = lmd.speedLimitAheadDistance
self.road_name = lmd.roadName
if sm.updated["carState"]:
self.current_speed = sm["carState"].vEgo * speed_conv
if sm.valid["carState"] and sm.valid["controlsState"]:
self.cruise_enabled = sm["carState"].cruiseState.enabled
v_cruise_cluster = sm["carState"].vCruiseCluster
set_speed_kph = sm["controlsState"].vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster
self.set_speed = set_speed_kph * (METER_TO_MILE / METER_TO_KM) if not ui_state.is_metric else set_speed_kph
def _render(self, rect: rl.Rectangle) -> None:
self._update_state()
rl.draw_rectangle(int(rect.x), int(rect.y), int(rect.width), int(rect.height), COLORS.bg_dark)
left_x = rect.x + CONTENT_MARGIN
if self.cruise_enabled:
unit = tr("MAX")
display_speed = self.set_speed
else:
unit = tr("km/h") if ui_state.is_metric else tr("MPH")
display_speed = self.current_speed
display_speed_text = str(round(display_speed))
if self.speed_limit_valid and display_speed > self.speed_limit:
speed_color = COLORS.red
else:
speed_color = COLORS.white
sign_width = min(SPEED_LIMIT_SIGN_WIDTH, rect.width * 0.30)
sign_height = VIENNA_SIGN_SIZE if ui_state.is_metric else MUTCD_SIGN_HEIGHT
has_upcoming_limit = self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit
target_sign_slide = 1.0 if has_upcoming_limit else 0.0
slide_speed = 3.0 * rl.get_frame_time()
if self._sign_slide < target_sign_slide:
self._sign_slide = min(self._sign_slide + slide_speed, target_sign_slide)
elif self._sign_slide > target_sign_slide:
self._sign_slide = max(self._sign_slide - slide_speed, target_sign_slide)
upcoming_width = int(sign_width * UPCOMING_SIGN_SIZE_RATIO)
upcoming_height = int(sign_height * UPCOMING_SIGN_SIZE_RATIO)
upcoming_reserved_width = int(upcoming_width * 0.85) + 5
sign_x_without_upcoming = rect.x + rect.width - sign_width - CONTENT_MARGIN
sign_x_with_upcoming = rect.x + rect.width - sign_width - CONTENT_MARGIN - upcoming_reserved_width
sign_x = sign_x_without_upcoming + (sign_x_with_upcoming - sign_x_without_upcoming) * self._sign_slide
sign_y = rect.y + (rect.height - sign_height) / 2
if not ui_state.is_metric and self.speed_limit_offset != 0 and self.speed_limit_valid:
sign_y += MUTCD_OFFSET_SIGN_Y_SHIFT
readout_right = sign_x - COLUMN_GAP
readout_width = max(1, readout_right - left_x)
road_y = rect.y + rect.height - 44
unit_font_size = self._fit_font_size(self._font_semi_bold, unit, readout_width, 46, UNIT_FONT_SIZE, 28)
speed_font_size = self._fit_font_size(self._font_bold, display_speed_text, readout_width, road_y - (rect.y + 54) - 8,
SPEED_FONT_SIZE, 76)
speed_size = measure_text_cached(self._font_bold, display_speed_text, speed_font_size)
speed_y = min(rect.y + 54, road_y - speed_size.y - 8)
unit_y = max(rect.y + 14, speed_y - unit_font_size - 6)
rl.draw_text_ex(self._font_semi_bold, unit, rl.Vector2(left_x, unit_y), unit_font_size, 0, COLORS.grey)
rl.draw_text_ex(self._font_bold, display_speed_text, rl.Vector2(left_x, speed_y), speed_font_size, 0, speed_color)
self._draw_road_name(left_x, road_y, readout_width)
if has_upcoming_limit and self._sign_slide > 0.01:
upcoming_speed_text = str(round(self.next_speed_limit))
distance_text = self._format_distance(self.next_speed_limit_distance)
upcoming_x = sign_x + sign_width - int(upcoming_width * UPCOMING_SIGN_OVERLAP_RATIO)
upcoming_y = sign_y + (sign_height - upcoming_height) / 2
upcoming_speed_color = COLORS.black
if ui_state.is_metric:
self._draw_vienna_sign(upcoming_x, upcoming_y, upcoming_width, upcoming_height, upcoming_speed_text, upcoming_speed_color, is_upcoming=True)
else:
self._draw_mutcd_sign(upcoming_x, upcoming_y, upcoming_width, upcoming_height, upcoming_speed_text, upcoming_speed_color, is_upcoming=True)
distance_font_size = self._fit_font_size(self._font_medium, distance_text, upcoming_width, 30, 24, 16)
distance_size = measure_text_cached(self._font_medium, distance_text, distance_font_size)
rl.draw_text_ex(self._font_medium, distance_text, rl.Vector2(upcoming_x + upcoming_width / 2 - distance_size.x / 2, upcoming_y + upcoming_height),
distance_font_size, 0, COLORS.grey)
self._draw_speed_limit_sign(sign_x, sign_y, sign_width, sign_height)
if self.speed_limit_offset != 0 and self.speed_limit_valid:
offset_text = str(abs(round(self.speed_limit_offset)))
badge_size = OFFSET_BADGE_SIZE
badge_rect = self._offset_badge_rect(rect, sign_x, sign_y, sign_width, sign_height, badge_size, has_upcoming_limit)
if ui_state.is_metric:
badge_radius = badge_size / 2
badge_center_x = badge_rect.x + badge_radius
badge_center_y = badge_rect.y + badge_radius
rl.draw_circle(int(badge_center_x), int(badge_center_y), badge_radius + 2, COLORS.dark_grey)
rl.draw_circle(int(badge_center_x), int(badge_center_y), badge_radius, COLORS.badge_bg)
self._draw_text_centered_fit(self._font_bold, offset_text, 32, rl.Vector2(badge_center_x, badge_center_y), COLORS.white,
badge_size - 10, badge_size - 8, min_size=24)
else:
rl.draw_rectangle_rounded(badge_rect, 0.25, 10, COLORS.badge_bg)
rl.draw_rectangle_rounded_lines_ex(badge_rect, 0.25, 10, 2, COLORS.dark_grey)
self._draw_text_centered_fit(self._font_bold, offset_text, 32, rl.Vector2(badge_rect.x + badge_size / 2, badge_rect.y + badge_size / 2),
COLORS.white, badge_size - 10, badge_size - 8, min_size=24)
scc_tag_x = min(left_x + speed_size.x + COLUMN_GAP, readout_right - SCC_TAG_WIDTH)
scc_tag_y = speed_y + (speed_size.y - (SCC_TAG_HEIGHT * 2 + SCC_TAG_GAP)) / 2
if scc_tag_x >= left_x + speed_size.x + 8:
self._draw_scc_icons(scc_tag_x, scc_tag_y, readout_right)
self._bookmark_icon.render(rect)
if ui_state.started:
alert_obj, no_alert = self._alert_renderer.will_render()
self._alert_alpha_filter.update(0 if no_alert else 1)
alpha = self._alert_alpha_filter.x
if alpha > 0.01:
rl.draw_rectangle(int(rect.x), int(rect.y), int(rect.width), int(rect.height), rl.Color(0, 0, 0, int(150 * alpha)))
self._alert_renderer.render(rect)
def _draw_scc_icons(self, x: float, y: float, right_limit: float) -> None:
sm = ui_state.sm
if not sm.valid["longitudinalPlanSP"]:
return
scc = sm["longitudinalPlanSP"].smartCruiseControl
drawn = 0
for label, active in [("SCC-V", scc.vision.active), ("SCC-M", scc.map.active)]:
if not active:
continue
tag_x = x
if tag_x + SCC_TAG_WIDTH > right_limit:
return
tag_y = y + drawn * (SCC_TAG_HEIGHT + SCC_TAG_GAP)
rl.draw_rectangle_rounded(rl.Rectangle(tag_x, tag_y, SCC_TAG_WIDTH, SCC_TAG_HEIGHT), 0.3, 10, COLORS.green)
self._draw_text_centered_fit(self._font_bold, label, 18, rl.Vector2(tag_x + SCC_TAG_WIDTH / 2, tag_y + SCC_TAG_HEIGHT / 2), COLORS.black,
SCC_TAG_WIDTH - 10, SCC_TAG_HEIGHT - 4, min_size=14)
drawn += 1
def _draw_speed_limit_sign(self, x: float, y: float, sign_width: float, sign_height: float) -> None:
speed_str = str(round(self.speed_limit)) if self.speed_limit_valid and self.speed_limit > 0 else "--"
speed_color = COLORS.black if not self.speed_limit_valid or self.current_speed <= self.speed_limit else COLORS.red
if ui_state.is_metric:
self._draw_vienna_sign(x, y, sign_width, sign_height, speed_str, speed_color, is_upcoming=False)
else:
self._draw_mutcd_sign(x, y, sign_width, sign_height, speed_str, speed_color, is_upcoming=False)
def _draw_road_name(self, x: float, y: float, width: float) -> None:
if width <= 0:
return
road_display = self.road_name if self.road_name else "--"
font_size = self._fit_font_size(self._font_semi_bold, road_display, width, 38, ROAD_FONT_SIZE, 28)
road_size = measure_text_cached(self._font_semi_bold, road_display, font_size)
text_width = road_size.x
if text_width <= width:
self._marquee_offset = 0.0
self._marquee_direction = 1
self._marquee_pause_timer = 0.0
rl.draw_text_ex(self._font_semi_bold, road_display, rl.Vector2(x, y), font_size, 0, COLORS.white)
else:
overflow = text_width - width
dt = rl.get_frame_time()
if self._marquee_pause_timer > 0:
self._marquee_pause_timer -= dt
else:
self._marquee_offset += self._marquee_direction * self._marquee_speed * dt
if self._marquee_offset >= overflow:
self._marquee_offset = overflow
self._marquee_direction = -1
self._marquee_pause_timer = self._marquee_pause_duration
elif self._marquee_offset <= 0:
self._marquee_offset = 0
self._marquee_direction = 1
self._marquee_pause_timer = self._marquee_pause_duration
rl.begin_scissor_mode(int(x), int(y), int(width), int(road_size.y + 4))
text_pos = rl.Vector2(x - self._marquee_offset, y)
rl.draw_text_ex(self._font_semi_bold, road_display, text_pos, font_size, 0, COLORS.white)
rl.end_scissor_mode()
def _draw_vienna_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color, is_upcoming: bool = False) -> None:
center = rl.Vector2(x + width / 2, y + height / 2)
outer_radius = min(width, height) / 2
rl.draw_circle_v(center, outer_radius, COLORS.white)
ring_width = outer_radius * 0.18
rl.draw_ring(center, outer_radius - ring_width, outer_radius, 0, 360, 36, COLORS.red)
font_size = outer_radius * (0.7 if len(speed_str) >= 3 else 0.9)
self._draw_text_centered_fit(self._font_bold, speed_str, int(font_size), center, speed_color, width * 0.72, height * 0.50, min_size=24)
def _draw_mutcd_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color, is_upcoming: bool = False) -> None:
sign_rect = rl.Rectangle(x, y, width, height)
rl.draw_rectangle_rounded(sign_rect, 0.35, 10, COLORS.white)
inset = max(4, width * 0.05)
inner_rect = rl.Rectangle(x + inset, y + inset, width - inset * 2, height - inset * 2)
outer_radius = 0.35 * width / 2.0
inner_radius = outer_radius - inset
inner_roundness = inner_radius / (inner_rect.width / 2.0)
rl.draw_rectangle_rounded_lines_ex(inner_rect, inner_roundness, 10, 3, COLORS.black)
mid_x = x + width / 2
label_size = max(18, int(width * 0.26))
if is_upcoming:
self._draw_text_centered_fit(self._font_bold, tr("AHEAD"), int(width * 0.34), rl.Vector2(mid_x, y + height * 0.28), COLORS.black,
width * 0.94, height * 0.32, min_size=20)
else:
self._draw_text_centered_fit(self._font_bold, tr("SPEED"), label_size, rl.Vector2(mid_x, y + height * 0.20), COLORS.black,
width * 0.84, height * 0.24, min_size=16)
self._draw_text_centered_fit(self._font_bold, tr("LIMIT"), label_size, rl.Vector2(mid_x, y + height * 0.40), COLORS.black,
width * 0.84, height * 0.24, min_size=16)
speed_font_size = int(width * 0.60) if len(speed_str) >= 3 else int(width * 0.72)
self._draw_text_centered_fit(self._font_bold, speed_str, speed_font_size, rl.Vector2(mid_x, y + height * 0.72), speed_color,
width * 0.90, height * 0.52, min_size=32)
def _draw_text_centered(self, font, text, size, pos_center, color):
sz = measure_text_cached(font, text, size)
rl.draw_text_ex(font, text, rl.Vector2(pos_center.x - sz.x / 2, pos_center.y - sz.y / 2), size, 0, color)
def _draw_text_centered_fit(self, font, text, size, pos_center, color, max_width: float, max_height: float, min_size: int = 10):
size = self._fit_font_size(font, text, max_width, max_height, size, min_size)
self._draw_text_centered(font, text, size, pos_center, color)
def _fit_font_size(self, font, text: str, max_width: float, max_height: float, max_size: int | float, min_size: int) -> int:
size = int(max_size)
while size > min_size:
text_size = measure_text_cached(font, text, size)
if text_size.x <= max_width and text_size.y <= max_height:
return size
size -= 2
return min_size
def _offset_badge_rect(self, panel_rect: rl.Rectangle, sign_x: float, sign_y: float, sign_width: float, sign_height: float,
badge_size: float, has_upcoming_limit: bool) -> rl.Rectangle:
if ui_state.is_metric:
radius = min(sign_width, sign_height) / 2
center_x = sign_x + sign_width / 2
center_y = sign_y + sign_height / 2
badge_x_ratio = VIENNA_BADGE_UPCOMING_X_RATIO if has_upcoming_limit else VIENNA_BADGE_X_RATIO
badge_center_x = center_x + radius * badge_x_ratio
badge_center_y = center_y + radius * VIENNA_BADGE_Y_RATIO
badge_x = badge_center_x - badge_size / 2
badge_y = badge_center_y - badge_size / 2
else:
badge_x = sign_x + sign_width - badge_size * 0.45
badge_y = sign_y - badge_size * 0.75
return rl.Rectangle(
self._clamp(
badge_x,
panel_rect.x + OFFSET_BADGE_PANEL_PADDING,
panel_rect.x + panel_rect.width - badge_size - OFFSET_BADGE_PANEL_PADDING,
),
self._clamp(
badge_y,
panel_rect.y + OFFSET_BADGE_PANEL_PADDING,
panel_rect.y + panel_rect.height - badge_size - OFFSET_BADGE_PANEL_PADDING,
),
badge_size,
badge_size,
)
@staticmethod
def _clamp(value: float, min_value: float, max_value: float) -> float:
return max(min_value, min(max_value, value))
def _format_distance(self, distance: float) -> str:
if ui_state.is_metric:
if distance < 50:
return tr("Near")
if distance >= 1000:
return f"{distance * METER_TO_KM:.1f}" + tr("km")
if distance < 200:
rounded = max(10, int(distance / 10) * 10)
else:
rounded = int(distance / 100) * 100
return str(rounded) + tr("m")
else:
distance_mi = distance * METER_TO_MILE
if distance_mi < 0.1:
return tr("Near")
return f"{distance_mi:.1f}" + tr("mi")
@@ -1,29 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView
class _SuppressedConfidenceBall:
def render(self, *_):
pass
class AugmentedRoadViewSP(AugmentedRoadView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._show_confidence_ball: bool = True
self._real_confidence_ball = self._confidence_ball
self._confidence_ball = _SuppressedConfidenceBall()
def set_show_confidence_ball(self, show: bool) -> None:
self._show_confidence_ball = show
def _render(self, _) -> None:
super()._render(_)
if self._show_confidence_ball:
self._real_confidence_ball.render(self.rect)
@@ -1,83 +0,0 @@
import pyray as rl
from openpilot.common.test import OpenpilotTestCase
from openpilot.system.ui.lib.application import MouseEvent, MousePos, gui_app
from openpilot.system.ui.lib.scroll_panel2 import ScrollState
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets import scroller as scroller_mod
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP
class DummyScrollIndicator:
def update(self, *_) -> None:
pass
def render(self) -> None:
pass
class DummyWidget(Widget):
def __init__(self, rect: rl.Rectangle):
super().__init__()
self.set_rect(rect)
def _render(self, _) -> None:
pass
def _mouse_event(x: float, y: float, *, pressed: bool = False, released: bool = False,
down: bool = True, t: float = 0.0) -> MouseEvent:
return MouseEvent(MousePos(x, y), 0, pressed, released, down, t)
class TestScrollerSP(OpenpilotTestCase):
def test_vertical_snap_items_are_supported(self, monkeypatch):
monkeypatch.setattr(scroller_mod, "ScrollIndicator", DummyScrollIndicator)
scroller = scroller_mod._Scroller([], horizontal=False, snap_items=True, scroll_indicator=False)
scroller.set_rect(rl.Rectangle(0, 0, 100, 100))
scroller.scroll_panel.set_offset(-60)
captured_snap_target = None
def update(_, __, snap_target=None):
nonlocal captured_snap_target
captured_snap_target = snap_target
return scroller.scroll_panel.get_offset()
monkeypatch.setattr(scroller.scroll_panel, "update", update)
visible_items: list[Widget] = [
DummyWidget(rl.Rectangle(0, -60, 100, 100)),
DummyWidget(rl.Rectangle(0, 40, 100, 100)),
]
scroller._get_scroll(visible_items, 200)
assert captured_snap_target == -100
def test_scroll_panel_sp_rejects_orthogonal_drags(self, monkeypatch):
panel = GuiScrollPanel2SP(horizontal=True)
bounds = rl.Rectangle(0, 0, 100, 100)
monkeypatch.setattr(gui_app, "_mouse_events", [_mouse_event(10, 10, pressed=True, t=1.0)])
panel.update(bounds, 200)
assert panel.state == ScrollState.PRESSED
monkeypatch.setattr(gui_app, "_mouse_events", [_mouse_event(23, 60, t=1.1)])
panel.update(bounds, 200)
assert panel.state == ScrollState.STEADY
assert panel.get_offset() == 0
def test_scroll_panel_sp_can_disable_out_of_bounds_handling(self, monkeypatch):
panel = GuiScrollPanel2SP(horizontal=False, handle_out_of_bounds=False)
bounds = rl.Rectangle(0, 0, 100, 100)
monkeypatch.setattr(gui_app, "_mouse_events", [])
panel.set_offset(20)
panel.update(bounds, 200)
assert panel.get_offset() == 0
panel.set_offset(-150)
panel.update(bounds, 200)
assert panel.get_offset() == -100
@@ -1,33 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.system.ui.lib.application import MouseEvent
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState
class GuiScrollPanel2SP(GuiScrollPanel2):
"""Scroll panel behavior for nested Mici pagers."""
def __init__(self, horizontal: bool = True, handle_out_of_bounds: bool = True) -> None:
super().__init__(horizontal, handle_out_of_bounds=handle_out_of_bounds)
def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bounds_size: float,
content_size: float) -> None:
state_before_update = self._state
super()._handle_mouse_event(mouse_event, bounds, bounds_size, content_size)
if self._state == ScrollState.MANUAL_SCROLL and state_before_update == ScrollState.PRESSED and \
self._initial_click_event is not None:
drag_x = abs(mouse_event.pos.x - self._initial_click_event.pos.x)
drag_y = abs(mouse_event.pos.y - self._initial_click_event.pos.y)
primary_drag = drag_x if self._horizontal else drag_y
cross_drag = drag_y if self._horizontal else drag_x
if cross_drag > primary_drag:
self._state = ScrollState.STEADY
self._velocity = 0.0
self._velocity_buffer.clear()
@@ -1,16 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.system.ui.widgets.scroller import Scroller
from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP
class ScrollerSP(Scroller):
def __init__(self, **kwargs):
super().__init__(**kwargs)
inner = self._scroller
inner.scroll_panel = GuiScrollPanel2SP(inner._horizontal, handle_out_of_bounds=not inner._snap_items)
@@ -4,28 +4,12 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import contextlib
import os
import time
from openpilot.common.hardware.hw import Paths
from openpilot.selfdrive.ui.ui_state import ui_state, ChestnutState
from openpilot.sunnypilot.models.fetcher import get_cached_bundles
from openpilot.sunnypilot.models.helpers import get_active_source, get_selected_bundle, resolve_bundle_by_ref
from openpilot.sunnypilot.models.model_name import DEFAULT_BIG_MODEL, DEFAULT_MODEL
def model_cache_size_mb() -> float:
"""Bytes on disk under the model cache directory, in MB."""
model_root = Paths.model_root()
total = 0
if os.path.isdir(model_root):
for name in os.listdir(model_root):
with contextlib.suppress(OSError):
total += os.path.getsize(os.path.join(model_root, name))
return total / (1024 ** 2)
def active_source() -> str:
return get_active_source(chestnut=ui_state.chestnut_present,
chestnut_active=ui_state.chestnut_active, chestnut_loading=ui_state.chestnut_loading,
@@ -99,22 +83,3 @@ def model_info() -> tuple[str, str, str]:
active_name = active_bundle.displayName if active_bundle else default_model_name(source)
other_name = other_bundle.displayName if other_bundle else default_model_name(other)
return source, active_name, other_name
# mirrors the manager's ModelCache keys; the manager restamps them on a successful fetch
MODEL_SYNC_KEYS = ("ModelManager_LastSyncTime", "ModelManager_LastSyncTime_Chestnut")
MODEL_SYNC_TIMEOUT = 20.0
def refresh_model_list() -> None:
# zeroing the sync keys makes the manager refetch each manifest on its next tick
for key in MODEL_SYNC_KEYS:
ui_state.params.put(key, 0)
def refresh_in_progress(started_at: float | None) -> bool:
"""Whether a user refresh is still outstanding. A failed fetch never restamps the
sync keys, so the spinner is bounded by MODEL_SYNC_TIMEOUT rather than sticking."""
if started_at is None or time.monotonic() - started_at > MODEL_SYNC_TIMEOUT:
return False
return not all(ui_state.params.get(key) for key in MODEL_SYNC_KEYS)
-3
View File
@@ -10,9 +10,6 @@ from openpilot.selfdrive.ui.layouts.main import MainLayout
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout
from openpilot.selfdrive.ui.ui_state import ui_state
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.main import MiciMainLayoutSP as MiciMainLayout
BIG_UI = gui_app.big_ui()
+1 -1
View File
@@ -1 +1 @@
#define SUNNYPILOT_VERSION "2026.10.10-4923"
#define SUNNYPILOT_VERSION "2026.09.04-4849"
@@ -0,0 +1,5 @@
from pathlib import Path
MODEL_PATH = Path(__file__).parent / 'models/supercombo.onnx'
MODEL_PKL_PATH = Path(__file__).parent / 'models/supercombo_tinygrad.pkl'
METADATA_PATH = Path(__file__).parent / 'models/supercombo_metadata.pkl'
@@ -15,19 +15,11 @@ class CameraOffsetHelper:
self.actual_camera_offset = 0.0
@staticmethod
def get_v_horizon(intrinsics, rpy_calib):
def apply_camera_offset(model_transform, intrinsics, height, offset_param):
cy = intrinsics[1, 2]
if len(rpy_calib) == 3 and np.isfinite(rpy_calib).all():
fy = intrinsics[1, 1]
pitch = rpy_calib[1]
return float(cy - fy * np.tan(pitch))
return float(cy)
@staticmethod
def apply_camera_offset(model_transform, height, offset_param, v_horizon):
shear = np.eye(3, dtype=np.float32)
shear[0, 1] = offset_param / height
shear[0, 2] = -offset_param / height * v_horizon
shear[0, 2] = -offset_param / height * cy
model_transform = (shear @ model_transform).astype(np.float32)
return model_transform
@@ -38,13 +30,10 @@ class CameraOffsetHelper:
self.actual_camera_offset = (0.9 * self.actual_camera_offset) + (0.1 * self.camera_offset)
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['narrowRoadCameraState'].sensor))]
height = sm["extrinsicsCalibration"].height[0] if sm['extrinsicsCalibration'].height else 1.22
rpy_calib = sm['extrinsicsCalibration'].rpyCalib
intrinsics_main = dc.wide_road.intrinsics if main_wide_camera else dc.narrow_road.intrinsics
v_horizon_main = self.get_v_horizon(intrinsics_main, rpy_calib)
model_transform_main = self.apply_camera_offset(model_transform_main, height, self.actual_camera_offset, v_horizon_main)
model_transform_main = self.apply_camera_offset(model_transform_main, intrinsics_main, height, self.actual_camera_offset)
intrinsics_extra = dc.wide_road.intrinsics
v_horizon_extra = self.get_v_horizon(intrinsics_extra, rpy_calib)
model_transform_extra = self.apply_camera_offset(model_transform_extra, height, self.actual_camera_offset, v_horizon_extra)
model_transform_extra = self.apply_camera_offset(model_transform_extra, intrinsics_extra, height, self.actual_camera_offset)
return model_transform_main, model_transform_extra
@@ -32,7 +32,7 @@ def _patch_tinygrad_fetch_fw():
helpers.fetch_fw = fetch_fw
_patch_tinygrad_fetch_fw()
import openpilot.selfdrive.modeld.compile_modeld as stock
from openpilot.selfdrive.modeld.compile_modeld import NV12Frame, make_frame_prepare, sample_desire, sample_skip, shift_and_sample
from tinygrad import dtypes
from tinygrad.device import Device
from tinygrad.engine.jit import TinyJit
@@ -41,7 +41,8 @@ from tinygrad.tensor import Tensor
MODEL_TYPES = ('vision_policy', 'supercombo', 'vision_multi_policy')
WARP_INPUTS = ['tfm', 'big_tfm']
POLICY_INPUTS = ['img_q', 'big_img_q', 'feat_q', 'desire_q', 'packed_npy_inputs']
nv12_copy_size = stock.nv12_copy_size
WARP_DEV = os.getenv('WARP_DEV')
def _detect_desire_key(shapes: dict) -> str | None:
return next((key for key in shapes if key.startswith('desire')), None)
@@ -138,7 +139,7 @@ def make_supercombo_input_queues(input_shapes: dict, frame_skip: int,
return generate_queues_and_npy(input_shapes, frame_skip, device, is_supercombo=True)
def make_random_images(keys, shape, device, rng=None):
def make_random_images(keys, shape, device):
return {k: Tensor.randint(shape, low=0, high=256, dtype=dtypes.uint8, device=device).realize() for k in keys}
@@ -151,9 +152,24 @@ def make_warp_queues(device=Device.DEFAULT):
return queues, npy
def make_warp(nv12: NV12Frame, model_w: int, model_h: int):
frame_prepare = make_frame_prepare(nv12, model_w, model_h)
WARP_DEV = os.getenv('WARP_DEV', Device.DEFAULT)
def warp(tfm, big_tfm, frame, big_frame):
tfm = tfm.to(WARP_DEV)
big_tfm = big_tfm.to(WARP_DEV)
Tensor.realize(tfm, big_tfm)
warped_frame = frame_prepare(frame, tfm).unsqueeze(0)
warped_big_frame = frame_prepare(big_frame, big_tfm).unsqueeze(0)
return Tensor.cat(warped_frame, warped_big_frame)
return warp
def make_run_policy(vision_runner, policy_runners: list, features_slice: slice, frame_skip: int, input_shapes: dict):
sample_skip_fn = partial(stock.sample_skip, frame_skip=frame_skip)
sample_desire_fn = partial(stock.sample_desire, frame_skip=frame_skip)
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
desire_key = _detect_desire_key(input_shapes)
road_key, wide_key = _detect_vision_keys(input_shapes)
@@ -170,14 +186,14 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
warped_dev = warped.to(Device.DEFAULT)
Tensor.realize(packed_npy_inputs_dev, warped_dev)
img = stock.shift_and_sample(img_q, warped_dev[0:1], sample_skip_fn)
big_img = stock.shift_and_sample(big_img_q, warped_dev[1:2], sample_skip_fn)
img = shift_and_sample(img_q, warped_dev[0:1], sample_skip_fn)
big_img = shift_and_sample(big_img_q, warped_dev[1:2], sample_skip_fn)
unpacked_tensors = [tensor.reshape(shape) for tensor, shape in zip(packed_npy_inputs_dev.split(npy_sizes), npy_shapes.values(), strict=True)]
unpacked_dict = dict(zip(npy_shapes.keys(), unpacked_tensors, strict=True))
desire_dev = unpacked_dict['desire']
desire_buf = stock.shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn)
desire_buf = shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn)
inputs = {desire_key: desire_buf}
for key, tensor_val in unpacked_dict.items():
@@ -186,13 +202,13 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
if 'prev_feat' in unpacked_dict:
prev_feat_dev = unpacked_dict['prev_feat']
inputs['features_buffer'] = stock.shift_and_sample(feat_q, prev_feat_dev.reshape(1, 1, -1), sample_skip_fn).reshape(input_shapes['features_buffer'])
inputs['features_buffer'] = shift_and_sample(feat_q, prev_feat_dev.reshape(1, 1, -1), sample_skip_fn).reshape(input_shapes['features_buffer'])
if vision_runner:
vision_out_cast = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast('float32').realize()
if 'features_buffer' not in inputs:
new_feat = vision_out_cast[:, features_slice].reshape(1, -1).unsqueeze(0)
inputs['features_buffer'] = stock.shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
inputs['features_buffer'] = shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
policy_outs = [next(iter(pol_runner(inputs).values())).cast('float32').realize() for pol_runner in policy_runners]
return (vision_out_cast, *policy_outs) if len(policy_outs) > 1 else (vision_out_cast, policy_outs[0])
@@ -203,28 +219,27 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
policy_out = next(iter(policy_runners[0](inputs).values())).cast('float32').realize()
if 'features_buffer' not in inputs and features_slice is not None:
new_feat = policy_out[:, features_slice].reshape(1, -1).unsqueeze(0)
stock.shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
return policy_out
return run_policy
def compile_jit(jit, input_keys, make_queues, make_random_inputs=None, benchmark_runs: int = 1):
def compile_jit(jit, make_random_inputs, input_keys, make_queues):
SEED = 42
def random_inputs_run(fn, seed, n_runs, test_val=None, test_buffers=None, expect_match=True):
queues_res = make_queues(Device.DEFAULT)
input_queues, npy = queues_res[0], queues_res[1]
frame_views = queues_res[2] if len(queues_res) > 2 else {}
def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True):
input_queues, npy = make_queues(Device.DEFAULT)
rng = np.random.default_rng(seed)
Tensor.manual_seed(seed)
testing = test_val is not None or test_buffers is not None
n_runs = 1 if testing else 3
for i in range(n_runs):
for v in npy.values():
v[:] = rng.standard_normal(v.shape).astype(v.dtype)
for v in frame_views.values():
v[:] = rng.integers(0, 256, size=v.shape, dtype=np.uint8)
Device.default.synchronize()
random_inputs = make_random_inputs(rng=rng) if make_random_inputs is not None else {}
random_inputs = make_random_inputs()
st = time.perf_counter()
outs = fn(**{k: input_queues[k] for k in input_keys if k in input_queues}, **random_inputs)
mt = time.perf_counter()
@@ -245,15 +260,14 @@ def compile_jit(jit, input_keys, make_queues, make_random_inputs=None, benchmark
return val, buffers
print('capture + replay')
test_val, test_buffers = random_inputs_run(jit, SEED, 3)
print(f'pickle round trip ({benchmark_runs} runs per seed)')
test_val, test_buffers = random_inputs_run(jit, SEED)
print('pickle round trip')
with tempfile.TemporaryFile(dir=".") as f:
dump_oob(jit, f)
f.seek(0)
loaded_jit = load_oob(f)
random_inputs_run(loaded_jit, SEED, benchmark_runs, test_val, test_buffers, expect_match=True)
random_inputs_run(loaded_jit, SEED+1, benchmark_runs, test_val, test_buffers, expect_match=False)
return jit
deserialized_jit = load_oob(f)
random_inputs_run(deserialized_jit, SEED, test_val=test_val, test_buffers=test_buffers)
return deserialized_jit
def _parse_size(size_str: str) -> tuple[int, int]:
@@ -303,7 +317,6 @@ if __name__ == "__main__":
parser.add_argument('--model-size', type=_parse_size, required=True, help='model input WxH')
parser.add_argument('--camera-resolutions', type=_parse_size, nargs='+', required=True)
parser.add_argument('--frame-skip', type=int, default=None, help='frame skip value (auto-derived if not provided)')
parser.add_argument('--benchmark-runs', type=int, default=1, help='benchmark runs')
parser.add_argument('--output', required=True)
parser.add_argument('--vision-onnx', help='vision ONNX (for split models)')
@@ -322,64 +335,48 @@ if __name__ == "__main__":
args.on_policy_onnx = read_file_chunked_to_disk(args.on_policy_onnx)
args.supercombo_onnx = read_file_chunked_to_disk(args.supercombo_onnx)
if args.model_type == 'supercombo':
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
if args.model_type == 'vision_policy':
assert vision_runner and args.policy_onnx
policy_runners = [OnnxRunner(args.policy_onnx)]
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx), 'policy': make_metadata_dict(args.policy_onnx)}
elif args.model_type == 'supercombo':
assert args.supercombo_onnx
model_metadata = make_metadata_dict(args.supercombo_onnx)
output_data['metadata'] = {'model': model_metadata, **model_metadata}
output_data['input_devices'] = {'model': Device.DEFAULT}
output_data['run_model'] = {}
derived_frame_skip = args.frame_skip or derive_frame_skip({}, model_metadata['input_shapes'])
model_runner = OnnxRunner(args.supercombo_onnx)
run_policy = stock.make_run_policy(model_runner, model_metadata, derived_frame_skip)
for cam_w, cam_h in args.camera_resolutions:
print(f"Compiling unified run_model JIT for {cam_w}x{cam_h}...")
nv12 = stock.NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
frame_copy_size = stock.nv12_copy_size(nv12.stride, nv12.y_height, nv12.uv_height)
make_model_queues = partial(stock.make_input_queues, model_metadata['input_shapes'], derived_frame_skip,
frame_copy_size=frame_copy_size)
warp = stock.make_warp(nv12, model_w, model_h)
run_model_jit = TinyJit(stock.make_run_model(warp, run_policy, model_metadata, frame_copy_size), prune=True)
output_data['run_model'][(cam_w, cam_h)] = compile_jit(run_model_jit, stock.MODELD_INPUTS, make_model_queues, benchmark_runs=args.benchmark_runs)
else:
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
if args.model_type == 'vision_policy':
assert vision_runner and args.policy_onnx
policy_runners = [OnnxRunner(args.policy_onnx)]
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx), 'policy': make_metadata_dict(args.policy_onnx)}
elif args.model_type == 'vision_multi_policy':
assert vision_runner
policy_runners, policy_names = _load_policy_runners(args)
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx)}
for name in policy_names:
runner_arg = getattr(args, f"{name}_onnx")
output_data['metadata'][name] = make_metadata_dict(runner_arg)
policy_runners = [OnnxRunner(args.supercombo_onnx)]
output_data['metadata'] = {'model': make_metadata_dict(args.supercombo_onnx)}
elif args.model_type == 'vision_multi_policy':
assert vision_runner
policy_runners, policy_names = _load_policy_runners(args)
output_data['metadata'] = {'vision': make_metadata_dict(args.vision_onnx)}
for name in policy_names:
runner_arg = getattr(args, f"{name}_onnx")
output_data['metadata'][name] = make_metadata_dict(runner_arg)
policy_keys = [key for key in output_data['metadata'].keys() if key != 'vision']
first_policy_meta = output_data['metadata'][policy_keys[0]] if policy_keys else {}
vision_meta = output_data['metadata'].get('vision', {})
policy_keys = [key for key in output_data['metadata'].keys() if key != 'vision']
first_policy_meta = output_data['metadata'][policy_keys[0]] if policy_keys else {}
vision_meta = output_data['metadata'].get('vision', {})
derived_frame_skip = args.frame_skip or derive_frame_skip(vision_meta.get('input_shapes', {}), first_policy_meta.get('input_shapes', {}))
all_shapes = {key: value for meta in output_data['metadata'].values() for key, value in meta['input_shapes'].items()}
feat_meta = output_data['metadata'].get('vision') or output_data['metadata'].get('policy')
assert feat_meta is not None
features_slice = feat_meta['output_slices']['hidden_state']
derived_frame_skip = args.frame_skip or derive_frame_skip(vision_meta.get('input_shapes', {}), first_policy_meta.get('input_shapes', {}))
all_shapes = {key: value for meta in output_data['metadata'].values() for key, value in meta['input_shapes'].items()}
feat_meta = output_data['metadata'].get('vision') or output_data['metadata'].get('model') or output_data['metadata'].get('policy')
assert feat_meta is not None
features_slice = feat_meta['output_slices']['hidden_state']
is_supercombo = vision_runner is None
print(f"Compiling run_policy JIT (model_size={model_w}x{model_h}, frame_skip={derived_frame_skip})...")
run_policy_func = make_run_policy(vision_runner, policy_runners, features_slice, derived_frame_skip, all_shapes)
run_policy_jit = TinyJit(run_policy_func, prune=True)
make_policy_queues = partial(generate_queues_and_npy, all_shapes, derived_frame_skip, is_supercombo=False)
make_random_model_inputs = partial(make_random_images, keys=['warped'], shape=(2, 6, model_h // 2, model_w // 2), device=Device.DEFAULT)
output_data['run_policy'] = compile_jit(run_policy_jit, POLICY_INPUTS, make_policy_queues, make_random_inputs=make_random_model_inputs)
print(f"Compiling run_policy JIT (model_size={model_w}x{model_h}, frame_skip={derived_frame_skip})...")
run_policy_func = make_run_policy(vision_runner, policy_runners, features_slice, derived_frame_skip, all_shapes)
run_policy_jit = TinyJit(run_policy_func, prune=True)
make_policy_queues = partial(generate_queues_and_npy, all_shapes, derived_frame_skip, is_supercombo=is_supercombo)
make_random_model_inputs = partial(make_random_images, keys=['warped'], shape=(2, 6, model_h // 2, model_w // 2), device=WARP_DEV)
output_data['run_policy'] = compile_jit(run_policy_jit, make_random_model_inputs, POLICY_INPUTS, make_policy_queues)
for cam_w, cam_h in args.camera_resolutions:
print(f"Compiling warp JIT for {cam_w}x{cam_h}...")
nv12 = stock.NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
frame_copy_size = stock.nv12_copy_size(nv12.stride, nv12.y_height, nv12.uv_height)
make_random_warp_inputs = partial(make_random_images, keys=['frame', 'big_frame'], shape=frame_copy_size, device=Device.DEFAULT)
warp = TinyJit(stock.make_warp(nv12, model_w, model_h), prune=True)
output_data[(cam_w, cam_h)] = compile_jit(warp, WARP_INPUTS, make_warp_queues, make_random_inputs=make_random_warp_inputs)
output_data['metadata']['warp_dev'] = Device.DEFAULT
for cam_w, cam_h in args.camera_resolutions:
print(f"Compiling warp JIT for {cam_w}x{cam_h}...")
nv12 = NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
make_random_warp_inputs = partial(make_random_images, keys=['frame', 'big_frame'], shape=nv12.size, device=WARP_DEV)
warp = TinyJit(make_warp(nv12, model_w, model_h), prune=True)
output_data[(cam_w, cam_h)] = compile_jit(warp, make_random_warp_inputs, WARP_INPUTS, make_warp_queues)
with open(args.output, "wb") as file:
dump_oob(output_data, file)
@@ -14,8 +14,6 @@ class ModelConstants:
# model inputs constants
MODEL_FREQ = 20
MODEL_RUN_FREQ = 20
MODEL_CONTEXT_FREQ = 5
FEATURE_LEN = 512
FULL_HISTORY_BUFFER_LEN = 99
DESIRE_LEN = 8
@@ -37,7 +35,6 @@ class ModelConstants:
LANE_LINES_WIDTH = 2
ROAD_EDGES_WIDTH = 2
PLAN_WIDTH = 15
ACTION_WIDTH = 2
DESIRE_PRED_WIDTH = 8
LAT_PLANNER_SOLUTION_WIDTH = 4
DESIRED_CURV_WIDTH = 1
-100
View File
@@ -1,100 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import io
import struct
import pickle
import inspect
import importlib
import enum
def _pad_args(func, args, kwargs):
try:
sig = inspect.signature(func)
except Exception:
return args, kwargs
params = list(sig.parameters.values())
if inspect.isfunction(func) and params and params[0].name in ('cls', 'self'):
params = params[1:]
new_args = list(args)
has_varargs = any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params)
if len(new_args) > len(params) and not has_varargs:
new_args = new_args[:len(params)]
for i in range(len(new_args), len(params)):
param = params[i]
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
continue
val = param.default if param.default is not inspect.Parameter.empty else None
new_args.append(val)
return new_args, kwargs
def _enum_factory(enum_class):
def factory(*args, **kwargs):
try:
return enum_class(*args, **kwargs)
# OptOps and UOp objects in the .pkl are left over from the compilation phase,
# reassignment does nothing because they aren't tied to the execution graph
# It never executes or evaluates the UOp nodes again.
except ValueError:
return list(enum_class)[0]
factory.__name__ = enum_class.__name__
factory.__module__ = enum_class.__module__
return factory
def _dynamic_factory(real_class):
if isinstance(real_class, type) and issubclass(real_class, enum.Enum):
return _enum_factory(real_class)
def factory(*args, **kwargs):
try:
return real_class(*args, **kwargs)
except TypeError:
new_args, new_kwargs = _pad_args(real_class, args, kwargs)
return real_class(*new_args, **new_kwargs)
class DynamicMeta(type(real_class)):
def __call__(cls, *args, **kwargs):
return factory(*args, **kwargs)
class DynamicProxy(real_class, metaclass=DynamicMeta):
__slots__ = ()
def __new__(cls, *args, **kwargs):
return factory(*args, **kwargs)
DynamicProxy.__name__ = real_class.__name__
DynamicProxy.__module__ = real_class.__module__
return DynamicProxy
class DynamicTinygradUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "tinygrad.ops":
try:
importlib.import_module("tinygrad.uops")
module = "tinygrad.uops"
except ImportError:
pass
real_class = getattr(importlib.import_module(module), name)
if module.startswith("tinygrad"):
return _dynamic_factory(real_class)
return real_class
def load_oob(f):
opcodes = f.read(struct.unpack('<q', f.read(8))[0])
def buffers():
while (h := f.read(8)):
pb = pickle.PickleBuffer(bytearray(struct.unpack('<q', h)[0]))
f.readinto(pb)
yield pb
return DynamicTinygradUnpickler(io.BytesIO(opcodes), buffers=buffers()).load()
+18 -1
View File
@@ -1,9 +1,26 @@
from openpilot.sunnypilot.modeld_v2.constants import Meta
from openpilot.cereal import custom
from openpilot.sunnypilot.modeld_v2.meta_20hz import Meta20hz
from openpilot.sunnypilot.models.helpers import get_active_bundle
ModelBundle = custom.ModelManagerSP.ModelBundle
def load_meta_constants():
"""
Determines and loads the appropriate meta model class based on the metadata provided. The function checks
specific keys and conditions within the provided metadata dictionary to identify the corresponding meta
model class to return.
:param model_metadata: Dictionary containing metadata about the model. It includes
details such as input shapes, output slices, and other configurations for identifying
metadata-dependent meta model classes.
:type model_metadata: dict
:return: The appropriate meta model class (Meta, MetaSimPose, or MetaTombRaider)
based on the conditions and metadata provided.
:rtype: type
"""
if (bundle := get_active_bundle()) and bundle.is20hz:
return Meta20hz
return Meta
return Meta # Default
+91 -93
View File
@@ -6,7 +6,6 @@ This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from collections.abc import Callable
import os
os.environ['GMMU'] = '0'
import numpy as np
@@ -17,7 +16,7 @@ from tinygrad.tensor import Tensor
import openpilot.cereal.messaging as messaging
from openpilot.common.hardware import COMMA_HARDWARE
from openpilot.selfdrive.modeld.helpers import chestnut_present
from openpilot.selfdrive.modeld.helpers import chestnut_present, load_oob
from openpilot.cereal import log
from opendbc.car.structs import car
from openpilot.cereal.services import SERVICE_LIST
@@ -38,21 +37,13 @@ from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
from openpilot.selfdrive.modeld.modeld import ChestnutState
from openpilot.selfdrive.modeld.compile_modeld import (
MODELD_INPUTS,
make_input_queues as make_stock_input_queues,
)
from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState, get_curvature_from_output
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants, Plan
from openpilot.sunnypilot.modeld_v2.constants import Plan
from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
from openpilot.sunnypilot.modeld_v2.compile_modeld import (derive_frame_skip, make_split_input_queues,
make_supercombo_input_queues, nv12_copy_size,
WARP_INPUTS, POLICY_INPUTS)
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues, make_supercombo_input_queues, WARP_INPUTS, POLICY_INPUTS
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
from openpilot.sunnypilot.modeld_v2.helpers import load_oob
from openpilot.sunnypilot.models.helpers import get_active_bundle
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
@@ -119,40 +110,36 @@ class ModelState(ModelStateBase):
cloudlog.warning(f"loading combined pkl: {pkl_path}")
jits = load_oob(open_file_chunked(pkl_path))
metadata = jits['metadata']
self.WARP_DEV = metadata.get('warp_dev', 'QCOM') if COMMA_HARDWARE else 'CPU'
self.DEV = ('AMD' if self.chestnut else 'QCOM') if COMMA_HARDWARE else 'CPU'
self.WARP_DEV = 'QCOM' if COMMA_HARDWARE else 'CPU'
self.DEV = 'AMD' if self.chestnut else self.WARP_DEV
self.QUEUE_DEV = self.DEV
self.is_run_model = 'run_model' in jits
metadata = jits['metadata']
nv12_info = get_nv12_info(cam_w, cam_h)
self.frame_copy_size = nv12_copy_size(*nv12_info[:3])
self.full_frames: dict = {}
self._blob_cache: dict = {}
self.frame_buffers: dict = {}
self.is_legacy_model = 'run_policy' not in jits # remove after next recompile
if self.is_legacy_model:
self.warp = jits[(cam_w, cam_h)]['warp_enqueue']
self.run_policy = jits[(cam_w, cam_h)]['run_policy']
else:
self.run_policy = jits['run_policy']
self.warp = jits[(cam_w, cam_h)]
if self.is_run_model or 'model' in metadata:
model_metadata = metadata.get('model', metadata)
self.input_shapes = model_metadata['input_shapes']
if 'model' in metadata:
model_metadata = metadata['model']
self.vision_output_slices = model_metadata['output_slices']
self.policy_output_slices = {}
self._policy_slices_list = []
self._combined_model_type = 'supercombo'
self._vision_input_names = [key for key in self.input_shapes if 'img' in key]
self.frame_skip = derive_frame_skip({}, self.input_shapes)
if self.is_run_model:
self.input_queues, self.numpy_inputs, self.frame_buffers = make_stock_input_queues(
self.input_shapes, self.frame_skip, device=self.DEV, frame_copy_size=self.frame_copy_size)
self.frame_views, self.npy = self.frame_buffers, self.numpy_inputs
self.run_model, self.run_policy, self.warp = jits['run_model'][(cam_w, cam_h)], None, None
else:
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
self.run_model, self.run_policy, self.warp = None, jits['run_policy'], jits[(cam_w, cam_h)]
self._vision_input_names = [key for key in model_metadata['input_shapes'] if 'img' in key]
frame_skip = derive_frame_skip({}, model_metadata['input_shapes'])
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(model_metadata['input_shapes'],
frame_skip, device=self.QUEUE_DEV)
else:
self.run_model, self.run_policy, self.warp = None, jits['run_policy'], jits[(cam_w, cam_h)]
vision_metadata = metadata['vision']
policy_keys = [k for k in metadata if k not in ('vision', 'warp_dev')]
self._combined_model_type = 'split' if policy_keys == ['policy'] else 'multi_policy'
policy_keys = [k for k in metadata if k != 'vision']
if policy_keys == ['policy']:
self._combined_model_type = 'split'
else:
self._combined_model_type = 'multi_policy'
self.vision_output_slices = vision_metadata['output_slices']
self._policy_keys = policy_keys
self._policy_slices_list = [metadata[k]['output_slices'] for k in policy_keys]
@@ -168,39 +155,54 @@ class ModelState(ModelStateBase):
self._desire_key = next(key for key in self.numpy_inputs if key.startswith('desire'))
self._road_key = next(key for key in self._vision_input_names if 'big' not in key)
self._wide_key = next(key for key in self._vision_input_names if 'big' in key)
self.frame_buf_params = dict.fromkeys(self._vision_input_names, nv12_info)
is_20hz = bundle.is20hz if bundle else self._combined_model_type in ('split', 'multi_policy')
if is_20hz:
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
self.constants = SplitModelConstants()
else:
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
self.constants = ModelConstants()
self.parser = Parser()
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
if self._combined_model_type != 'supercombo':
from openpilot.sunnypilot.modeld_v2.parse_model_outputs_split import Parser as SplitParser
self.parser = SplitParser()
else:
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser as CombinedParser
self.parser = CombinedParser()
if self.warp is not None:
self.full_frames = {k: Tensor(np.zeros(nv12_info[3], dtype=np.uint8), device=self.WARP_DEV).contiguous().realize() for k in self._vision_input_names}
self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[self._road_key], big_frame=self.full_frames[self._wide_key])
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
self.full_frames: dict = {}
self._blob_cache: dict = {}
nv12_info = get_nv12_info(cam_w, cam_h)
self.frame_buf_params = dict.fromkeys(self._vision_input_names, nv12_info)
yuv_size = self.frame_buf_params[self._road_key][3]
frame_tensor = Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize()
big_frame_tensor = Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize()
if self.is_legacy_model: # Remove this conditional hack after recompile
self.warp(**self.input_queues, frame=frame_tensor, big_frame=big_frame_tensor)
else:
self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=frame_tensor, big_frame=big_frame_tensor)
def warmup(self) -> None:
dummy_size = self.frame_copy_size if self.is_run_model else self.frame_buf_params[self._road_key][3]
dummy_frames = {k: np.zeros(dummy_size, dtype=np.uint8) for k in self._vision_input_names}
dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self._vision_input_names}
transforms = {k: np.eye(3, dtype=np.float32) for k in [self._road_key, self._wide_key] if k}
dummy_inputs = {k: np.zeros(v.shape, dtype=v.dtype) for k, v in self.numpy_inputs.items() if k not in ['tfm', 'big_tfm', 'prev_feat']}
self.run(dummy_frames, transforms, dummy_inputs)
if self.is_run_model:
self.input_queues, self.numpy_inputs, self.frame_buffers = make_stock_input_queues(
self.input_shapes, self.frame_skip, device=self.DEV, frame_copy_size=self.frame_copy_size)
self.frame_views = self.frame_buffers
self.npy = self.numpy_inputs
else:
for v in self.numpy_inputs.values():
v[:] = 0
self.full_frames.clear()
self._blob_cache.clear()
dummy_inputs = {}
for k, v in self.numpy_inputs.items():
if k not in ['tfm', 'big_tfm', 'prev_feat']:
dummy_inputs[k] = np.zeros(v.shape, dtype=v.dtype)
self.run(dummy_frames, transforms, dummy_inputs, prepare_only=False)
for v in self.numpy_inputs.values():
v[:] = 0
self.prev_desire[:] = 0
self.full_frames.clear()
self._blob_cache.clear()
@property
def mlsim(self) -> bool:
@@ -215,50 +217,45 @@ class ModelState(ModelStateBase):
return self._desire_key
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
inputs: dict[str, np.ndarray],
after_enqueue: Callable[[], None] | None = None) -> dict[str, np.ndarray] | None:
if self.is_run_model:
for key, buf in bufs.items():
data = buf.data if hasattr(buf, 'data') else buf
np.copyto(self.frame_buffers[key], np.frombuffer(data, dtype=np.uint8, count=self.frame_copy_size))
else:
for key, buf in bufs.items():
ptr = np.frombuffer(buf.data, dtype=np.uint8).ctypes.data
cache_key = (key, ptr)
if cache_key not in self._blob_cache:
self._blob_cache[cache_key] = Tensor.from_blob(ptr, (self.frame_buf_params[key][3],), dtype='uint8', device=self.WARP_DEV)
self.full_frames[key] = self._blob_cache[cache_key]
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
for key in bufs.keys():
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data
yuv_size = self.frame_buf_params[key][3]
cache_key = (key, ptr)
if cache_key not in self._blob_cache:
self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8', device=self.WARP_DEV)
self.full_frames[key] = self._blob_cache[cache_key]
desire_key = self.desire_key
inputs[desire_key][0] = 0
self.numpy_inputs[desire_key][:] = np.where(inputs[desire_key] - self.prev_desire > .99, inputs[desire_key], 0)
self.prev_desire[:] = inputs[desire_key]
for key in ('traffic_convention', 'lateral_control_params', 'action_t'):
if key in self.numpy_inputs and key in inputs:
self.numpy_inputs[key][:] = inputs[key]
self.numpy_inputs['tfm'][:, :] = transforms[self._road_key].reshape(3, 3)
self.numpy_inputs['big_tfm'][:, :] = transforms[self._wide_key].reshape(3, 3)
road_key = self._road_key
wide_key = self._wide_key
self.numpy_inputs['tfm'][:, :] = transforms[road_key].reshape(3, 3)
self.numpy_inputs['big_tfm'][:, :] = transforms[wide_key].reshape(3, 3)
if self.run_model is not None:
outs, = self.run_model(**{k: self.input_queues[k] for k in MODELD_INPUTS})
raw_outputs = outs
if self.is_legacy_model: # remove after next recompile
if prepare_only:
self.warp(**self.input_queues, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
return None
raw_outputs = self.run_policy(**self.input_queues, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
else:
assert self.warp is not None and self.run_policy is not None
warped = self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[self._road_key], big_frame=self.full_frames[self._wide_key])
if prepare_only:
self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
return None
warped = self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=self.full_frames[road_key], big_frame=self.full_frames[wide_key])
raw_outputs = self.run_policy(**{k: self.input_queues[k] for k in POLICY_INPUTS if k in self.input_queues}, warped=warped)
if after_enqueue is not None:
after_enqueue()
if self._combined_model_type == 'supercombo':
model_output = raw_outputs.numpy().flatten()
if self.chestnut and not np.all(np.isfinite(model_output)):
raise RuntimeError("model output not finite")
sliced = {k: model_output[np.newaxis, v] for k, v in self.vision_output_slices.items()}
outputs = self.parser.parse_outputs(sliced)
if 'prev_feat' in self.numpy_inputs and 'hidden_state' in self.vision_output_slices:
if 'prev_feat' in self.numpy_inputs:
self.numpy_inputs['prev_feat'][:] = model_output[self.vision_output_slices['hidden_state']]
else:
vision_output = raw_outputs[0].numpy().flatten()
@@ -288,6 +285,9 @@ class ModelState(ModelStateBase):
buf[0, :-1] = buf[0, 1:]
buf[0, -1, :] = outputs['desired_curvature'][0, :] if not self.mlsim else 0
if self.chestnut and not np.all(np.isfinite(outputs.get('plan', np.array([0.])))):
raise RuntimeError("model output not finite")
return outputs
def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
@@ -373,11 +373,7 @@ def main(demo=False):
loader.start()
loader.join(BIG_MODEL_TIMEOUT)
model = big_model
if model is None:
params.put_bool("ChestnutModelError", True)
params.put_bool("ChestnutActive", model is not None)
if model is not None:
params.remove("ChestnutModelError")
small_model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, chestnut=False) if model is None or CHESTNUT else None
if model is None:
@@ -491,6 +487,9 @@ def main(demo=False):
run_count = run_count + 1
frame_drop_ratio = frames_dropped / (1 + frames_dropped)
prepare_only = vipc_dropped_frames > 0
if prepare_only:
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
@@ -513,14 +512,11 @@ def main(demo=False):
mt1 = time.perf_counter()
try:
send_chestnut = (chestnut_state is not None and
run_count % round(model.constants.MODEL_FREQ / SERVICE_LIST['chestnutState'].frequency) == 0)
model_output = model.run(bufs, transforms, inputs, chestnut_state.send if send_chestnut else None)
model_output = model.run(bufs, transforms, inputs, prepare_only)
except Exception:
if not params.get_bool("ChestnutActive"):
raise
cloudlog.exception("chestnut failed, falling back to small")
params.put_bool("ChestnutModelError", True)
params.put_bool("ChestnutActive", False)
assert small_model is not None
model = small_model
@@ -552,7 +548,6 @@ def main(demo=False):
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, left_edge, right_edge)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.valid = modelv2_send.valid
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
@@ -564,6 +559,9 @@ def main(demo=False):
pm.send('modelDataV2SP', mdv2sp_send)
last_vipc_frame_id = meta_main.frame_id
if chestnut_state is not None and run_count % round(model.constants.MODEL_FREQ / SERVICE_LIST['chestnutState'].frequency) == 0:
chestnut_state.send()
if __name__ == "__main__":
try:
import argparse
@@ -115,41 +115,22 @@ class Parser:
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
if 'plan' in outs:
self.parse_mdn('plan', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
if 'planplus' in outs:
self.parse_mdn('planplus', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
if 'lane_lines' in outs:
self.parse_mdn('lane_lines', outs, out_shape=(ModelConstants.NUM_LANE_LINES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
if 'road_edges' in outs:
self.parse_mdn('road_edges', outs, out_shape=(ModelConstants.NUM_ROAD_EDGES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
if 'pose' in outs:
self.parse_mdn('pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
if 'road_transform' in outs:
self.parse_mdn('road_transform', outs, out_shape=(ModelConstants.POSE_WIDTH,))
# supercombo (4955 / 102) and newer variants (e.g. 990 / 144).
self.parse_mdn('plan', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
self.parse_mdn('lane_lines', outs, out_shape=(ModelConstants.NUM_LANE_LINES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
self.parse_mdn('road_edges', outs, out_shape=(ModelConstants.NUM_ROAD_EDGES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
self.parse_mdn('pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
self.parse_mdn('road_transform', outs, out_shape=(ModelConstants.POSE_WIDTH,))
if 'sim_pose' in outs:
self.parse_mdn('sim_pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
if 'wide_from_device_euler' in outs:
self.parse_mdn('wide_from_device_euler', outs, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
if 'lead' in outs:
self.parse_mdn('lead', outs, out_shape=(ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH))
self.parse_mdn('wide_from_device_euler', outs, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
self.parse_mdn('lead', outs, out_shape=(ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH))
if 'lat_planner_solution' in outs:
self.parse_mdn('lat_planner_solution', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
if 'desired_curvature' in outs:
self.parse_mdn('desired_curvature', outs, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
if 'action' in outs:
self.parse_mdn('action', outs, out_shape=(ModelConstants.ACTION_WIDTH,))
for k in ['lead_prob', 'lane_lines_prob', 'meta']:
if k in outs:
self.parse_binary_crossentropy(k, outs)
if 'desire_state' in outs:
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
if 'desire_pred' in outs:
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN, ModelConstants.DESIRE_PRED_WIDTH))
self.parse_binary_crossentropy(k, outs)
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN, ModelConstants.DESIRE_PRED_WIDTH))
return outs
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
return self.parse_outputs(outs)
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
return self.parse_outputs(outs)
@@ -0,0 +1,159 @@
import numpy as np
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
def safe_exp(x, out=None):
# -11 is around 10**14, more causes float16 overflow
return np.exp(np.clip(x, -np.inf, 11), out=out)
def sigmoid(x):
return 1. / (1. + safe_exp(-x))
def softmax(x, axis=-1):
x -= np.max(x, axis=axis, keepdims=True)
if x.dtype == np.float32 or x.dtype == np.float64:
safe_exp(x, out=x)
else:
x = safe_exp(x)
x /= np.sum(x, axis=axis, keepdims=True)
return x
class Parser:
def __init__(self, ignore_missing=False):
self.ignore_missing = ignore_missing
def check_missing(self, outs, name):
if name not in outs and not self.ignore_missing:
raise ValueError(f"Missing output {name}")
return name not in outs
def parse_categorical_crossentropy(self, name, outs, out_shape=None):
if self.check_missing(outs, name):
return
raw = outs[name]
if out_shape is not None:
raw = raw.reshape((raw.shape[0],) + out_shape)
outs[name] = softmax(raw, axis=-1)
def parse_binary_crossentropy(self, name, outs):
if self.check_missing(outs, name):
return
raw = outs[name]
outs[name] = sigmoid(raw)
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None):
if self.check_missing(outs, name):
return
raw = outs[name]
raw = raw.reshape((raw.shape[0], max(in_N, 1), -1))
n_values = (raw.shape[2] - out_N)//2
pred_mu = raw[:,:,:n_values]
pred_std = safe_exp(raw[:,:,n_values: 2*n_values])
if in_N > 1:
weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype)
for i in range(out_N):
weights[:,:,i - out_N] = softmax(raw[:,:,i - out_N], axis=-1)
if out_N == 1:
for fidx in range(weights.shape[0]):
idxs = np.argsort(weights[fidx][:,0])[::-1]
weights[fidx] = weights[fidx][idxs]
pred_mu[fidx] = pred_mu[fidx][idxs]
pred_std[fidx] = pred_std[fidx][idxs]
assert out_shape is not None
full_shape = tuple([raw.shape[0], in_N] + list(out_shape))
outs[name + '_weights'] = weights
outs[name + '_hypotheses'] = pred_mu.reshape(full_shape)
outs[name + '_stds_hypotheses'] = pred_std.reshape(full_shape)
pred_mu_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype)
pred_std_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype)
for fidx in range(weights.shape[0]):
for hidx in range(out_N):
idxs = np.argsort(weights[fidx,:,hidx])[::-1]
pred_mu_final[fidx, hidx] = pred_mu[fidx, idxs[0]]
pred_std_final[fidx, hidx] = pred_std[fidx, idxs[0]]
else:
pred_mu_final = pred_mu
pred_std_final = pred_std
if out_N > 1:
assert out_shape is not None
final_shape = tuple([raw.shape[0], out_N] + list(out_shape))
else:
assert out_shape is not None
final_shape = tuple([raw.shape[0],] + list(out_shape))
outs[name] = pred_mu_final.reshape(final_shape)
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
def is_mhp(self, outs, name, shape):
if self.check_missing(outs, name):
return False
if outs[name].shape[1] == 2 * shape:
return False
return True
def parse_dynamic_outputs(self, outs: dict[str, np.ndarray]) -> None:
if 'lead' in outs:
lead_mhp = self.is_mhp(outs, 'lead',
SplitModelConstants.LEAD_MHP_SELECTION * SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH)
lead_in_N, lead_out_N = (SplitModelConstants.LEAD_MHP_N, SplitModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0)
lead_out_shape = (SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH) if lead_mhp else \
(SplitModelConstants.LEAD_MHP_SELECTION, SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH)
self.parse_mdn('lead', outs, in_N=lead_in_N, out_N=lead_out_N, out_shape=lead_out_shape)
if 'plan' in outs:
plan_mhp = self.is_mhp(outs, 'plan', SplitModelConstants.IDX_N * SplitModelConstants.PLAN_WIDTH)
plan_in_N, plan_out_N = (SplitModelConstants.PLAN_MHP_N, SplitModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0)
self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N,
out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
if 'planplus' in outs:
self.parse_mdn('planplus', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
def split_outputs(self, outs: dict[str, np.ndarray]) -> None:
if 'desired_curvature' in outs:
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.DESIRED_CURV_WIDTH,))
if 'desire_pred' in outs:
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(SplitModelConstants.DESIRE_PRED_LEN,SplitModelConstants.DESIRE_PRED_WIDTH))
if 'desire_state' in outs:
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,))
if 'lane_lines' in outs:
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
if 'lane_lines_prob' in outs:
self.parse_binary_crossentropy('lane_lines_prob', outs)
if 'lead_prob' in outs:
self.parse_binary_crossentropy('lead_prob', outs)
if 'lat_planner_solution' in outs:
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
if 'meta' in outs:
self.parse_binary_crossentropy('meta', outs)
if 'road_edges' in outs:
self.parse_mdn('road_edges', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
if 'sim_pose' in outs:
self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
if 'action' in outs:
self.parse_mdn('action', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.ACTION_WIDTH,))
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.WIDE_FROM_DEVICE_WIDTH,))
self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
self.parse_dynamic_outputs(outs)
self.split_outputs(outs)
return outs
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
self.parse_dynamic_outputs(outs)
self.split_outputs(outs)
return outs
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
outs = self.parse_vision_outputs(outs)
outs = self.parse_policy_outputs(outs)
return outs
@@ -117,7 +117,7 @@ ARCHETYPES = {
is_20hz=True,
expected_model_type='split',
expected_constants_class=SplitModelConstants,
expected_parser_module='parse_model_outputs',
expected_parser_module='parse_model_outputs_split',
expected_desire_key='desire',
),
'vision_multi_policy': Archetype(
@@ -130,7 +130,7 @@ ARCHETYPES = {
is_20hz=True,
expected_model_type='multi_policy',
expected_constants_class=SplitModelConstants,
expected_parser_module='parse_model_outputs',
expected_parser_module='parse_model_outputs_split',
expected_desire_key='desire',
),
'tri_policy': Archetype(
@@ -144,7 +144,7 @@ ARCHETYPES = {
is_20hz=True,
expected_model_type='multi_policy',
expected_constants_class=SplitModelConstants,
expected_parser_module='parse_model_outputs',
expected_parser_module='parse_model_outputs_split',
expected_desire_key='desire',
),
'supercombo_non20hz': Archetype(
@@ -6,9 +6,8 @@ See the LICENSE.md file in the root directory for more details.
"""
import numpy as np
from openpilot.common.transformations.camera import DEVICE_CAMERAS, view_frame_from_device_frame
from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.common.transformations.model import get_warp_matrix
from openpilot.common.transformations.orientation import rot_from_euler
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
from openpilot.common.test import OpenpilotTestCase
@@ -47,50 +46,29 @@ class TestCameraOffset(OpenpilotTestCase):
self.camera_offset.update(main_transform, extra_transform, sm, False)
np.testing.assert_almost_equal(self.camera_offset.actual_camera_offset, 0.038)
def test_apply_camera_offset(self):
def test_camera_offset_(self):
intrinsics = self.dc.narrow_road.intrinsics
v_horizon = CameraOffsetHelper.get_v_horizon(intrinsics, []) # pitch = 0 fallback: v_horizon == cy
transform = np.eye(3, dtype=np.float32)
height = 1.22
offset = 0.1
cy = intrinsics[1, 2]
expected_shear = np.eye(3, dtype=np.float32)
expected_shear[0, 1] = offset / height
expected_shear[0, 2] = -offset / height * v_horizon
expected_shear[0, 2] = -offset / height * cy
result = CameraOffsetHelper.apply_camera_offset(transform, height, offset, v_horizon)
result = CameraOffsetHelper.apply_camera_offset(transform, intrinsics, height, offset)
np.testing.assert_array_almost_equal(result, expected_shear)
def test_v_horizon_empty_rpy(self):
intrinsics = self.dc.narrow_road.intrinsics
v_horizon = CameraOffsetHelper.get_v_horizon(intrinsics, [])
np.testing.assert_almost_equal(v_horizon, intrinsics[1, 2])
def test_v_horizon_projection(self):
intrinsics = self.dc.narrow_road.intrinsics
f, cy = intrinsics[1, 1], intrinsics[1, 2]
for pitch_deg in [6.0, -6.0, 0.0]:
rpy = [0.0, np.radians(pitch_deg), 0.0]
d_dev = rot_from_euler(rpy) @ np.array([1.0, 0.0, 0.0])
view = view_frame_from_device_frame @ d_dev
expected = cy + f * view[1] / view[2]
v_horizon = CameraOffsetHelper.get_v_horizon(intrinsics, rpy)
np.testing.assert_almost_equal(v_horizon, expected, decimal=4)
def test_update(self):
height = 1.2
pitch = np.radians(-8.0)
sm = MockStruct(
deviceState=MockStruct(deviceType='mici'),
narrowRoadCameraState=MockStruct(sensor='os04c10'),
extrinsicsCalibration=MockStruct(rpyCalib=[0.0, pitch, 0.0], height=[height])
extrinsicsCalibration=MockStruct(rpyCalib=[0.0, 0.0, 0.0], height=[1.22])
)
intrinsics_main = self.dc.narrow_road.intrinsics
intrinsics_extra = self.dc.wide_road.intrinsics
device_from_calib_euler = np.array(sm['extrinsicsCalibration'].rpyCalib, dtype=np.float32)
device_from_calib_euler = np.array([0.0, 0.0, 0.0], dtype=np.float32)
main_transform = get_warp_matrix(device_from_calib_euler, intrinsics_main, False).astype(np.float32)
extra_transform = get_warp_matrix(device_from_calib_euler, intrinsics_extra, True).astype(np.float32)
@@ -103,13 +81,5 @@ class TestCameraOffset(OpenpilotTestCase):
main_out, extra_out = self.camera_offset.update(main_transform, extra_transform, sm, False)
assert not np.array_equal(main_out, main_transform)
assert not np.array_equal(extra_out, extra_transform)
# settle the low-pass filter
for _ in range(100):
main_out, extra_out = self.camera_offset.update(main_transform, extra_transform, sm, False)
# undo main_transform dot product to get shear matrix
shear = main_out @ np.linalg.inv(main_transform)
expected_v_horizon = intrinsics_main[1, 2] - intrinsics_main[1, 1] * np.tan(pitch)
np.testing.assert_almost_equal(shear[0, 1], self.camera_offset.actual_camera_offset / height, decimal=4)
np.testing.assert_almost_equal(shear[0, 2], -self.camera_offset.actual_camera_offset / height * expected_v_horizon, decimal=4)
assert main_out[0, 1] != 0.0
assert main_out[0, 2] != 0.0
@@ -103,23 +103,6 @@ class TestStockEquivalence(OpenpilotTestCase):
assert state.vision_output_slices == arch.metadata_structure['vision']['output_slices']
assert state.policy_output_slices == arch.metadata_structure['policy']['output_slices']
def test_unified_run_model(self, tmp_path, monkeypatch, patch_modeld):
from openpilot.common.hardware import hw
from openpilot.selfdrive.modeld.helpers import dump_oob
shapes = {'img': (1, 12, 128, 256), 'big_img': (1, 12, 128, 256), 'features_buffer': (1, 24, 32, 512),
'desire_pulse': (1, 25, 8), 'traffic_convention': (1, 2), 'action_t': (1, 2)}
pkl_data = {'metadata': {'model': {'input_shapes': shapes, 'output_slices': {}}},
'run_model': {(CAM_W, CAM_H): tests_helpers._noop_jit}}
with open(tmp_path / 'driving_test_tinygrad.pkl', 'wb') as f:
dump_oob(pkl_data, f)
bundle = DummyBundle(models=[DummyModel('supercombo', 'driving_test_tinygrad.pkl')])
patch_modeld(bundle)
monkeypatch.setattr(hw.Paths, 'model_root', staticmethod(lambda: str(tmp_path)))
state = ModelState(cam_w=CAM_W, cam_h=CAM_H)
assert state.is_run_model and state.run_model is not None
assert state.run_policy is None and state.warp is None
assert 'img' in state.frame_views and 'big_img' in state.frame_views
ARCHETYPE_NAMES = list(ARCHETYPES.keys())
@@ -1,43 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import os
import unittest
from unittest.mock import patch
from openpilot.common.file_chunker import open_file_chunked
from openpilot.sunnypilot.modeld_v2.helpers import load_oob
from tinygrad.device import Device
class TestLegacyModels(unittest.TestCase):
def test_legacy_model_load(self):
base_name = os.environ.get("MODEL_BASE_NAME")
if not base_name:
raise unittest.SkipTest("MODEL_BASE_NAME env var not set, skipping integration test.")
chunk_dir = os.environ.get("MODEL_CHUNK_DIR", "/tmp/model_chunks")
base_path = os.path.join(chunk_dir, base_name)
try:
f = open_file_chunked(base_path)
except Exception as error:
self.fail(f"Failed to open chunked file {base_path}: {error}")
self.addCleanup(f.close)
real_getitem = Device.__class__.__getitem__
def safe_getitem(device_self, ix):
if ix == "QCOM" and not os.path.exists("/dev/kgsl-3d0"):
return real_getitem(device_self, "CPU")
if ix == "AMD" and not os.path.exists("/dev/kfd"):
return real_getitem(device_self, "CPU")
return real_getitem(device_self, ix)
with patch.object(Device.__class__, "__getitem__", safe_getitem):
obj = load_oob(f)
assert isinstance(obj, dict), "Parsed object is not a dictionary"
assert "metadata" in obj, "Metadata key is missing"
@@ -1,81 +0,0 @@
import numpy as np
from openpilot.common.test import OpenpilotTestCase
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser, _infer_mhp, sigmoid, softmax
class TestParseModelOutputs(OpenpilotTestCase):
def test_infer_mhp_lead(self):
in_hypotheses, out_selections = _infer_mhp(102, 24)
assert in_hypotheses == 2
assert out_selections == 3
def test_infer_mhp_plan(self):
in_hypotheses, out_selections = _infer_mhp(4955, 495)
assert in_hypotheses == 5
assert out_selections == 1
def test_infer_mhp_non_mdn(self):
in_hypotheses, out_selections = _infer_mhp(48, 24)
assert in_hypotheses == 1
assert out_selections == 0
def test_check_missing_raises(self):
parser = Parser(ignore_missing=False)
with self.assertRaises(ValueError):
parser.check_missing({}, "missing_key")
def test_check_missing_ignored(self):
parser = Parser(ignore_missing=True)
assert parser.check_missing({}, "missing_key") is True
def test_binary_crossentropy(self):
parser = Parser()
raw_logits = np.array([[-10.0, 0.0, 10.0]], dtype=np.float32)
outs = {"meta": raw_logits.copy()}
parser.parse_binary_crossentropy("meta", outs)
expected_probabilities = sigmoid(raw_logits)
np.testing.assert_allclose(outs["meta"], expected_probabilities, rtol=1e-5, atol=1e-6)
def test_categorical_crossentropy(self):
parser = Parser()
raw_logits = np.array([[1.0, 2.0, 3.0]], dtype=np.float32)
outs = {"desire_state": raw_logits.copy()}
parser.parse_categorical_crossentropy("desire_state", outs)
expected_probabilities = softmax(raw_logits)
np.testing.assert_allclose(outs["desire_state"], expected_probabilities, rtol=1e-5, atol=1e-6)
def test_parse_vision_outputs(self):
parser = Parser()
pose_raw = np.zeros((1, ModelConstants.POSE_WIDTH * 2), dtype=np.float32)
road_transform_raw = np.zeros((1, ModelConstants.POSE_WIDTH * 2), dtype=np.float32)
lead_raw = np.zeros((1, 102), dtype=np.float32)
meta_raw = np.zeros((1, 55), dtype=np.float32)
vision_outputs = {"pose": pose_raw, "road_transform": road_transform_raw, "lead": lead_raw, "meta": meta_raw}
parsed = parser.parse_vision_outputs(vision_outputs)
assert "pose" in parsed
assert "road_transform" in parsed
assert "lead" in parsed
assert "meta" in parsed
assert parsed["pose"].shape == (1, ModelConstants.POSE_WIDTH)
assert parsed["lead"].shape == (1, ModelConstants.LEAD_MHP_SELECTION, ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH)
def test_parse_policy_outputs(self):
parser = Parser()
plan_raw = np.zeros((1, 4955), dtype=np.float32)
desire_state_raw = np.zeros((1, ModelConstants.DESIRE_PRED_WIDTH), dtype=np.float32)
action_raw = np.zeros((1, ModelConstants.ACTION_WIDTH * 2), dtype=np.float32)
policy_outputs = {"plan": plan_raw, "desire_state": desire_state_raw, "action": action_raw}
parsed = parser.parse_policy_outputs(policy_outputs)
assert parsed["plan"].shape == (1, ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)
assert parsed["action"].shape == (1, ModelConstants.ACTION_WIDTH)
assert parsed["desire_state"].shape == (1, ModelConstants.DESIRE_PRED_WIDTH)
def test_parse_outputs_combined(self):
parser = Parser()
outputs = {"plan": np.zeros((1, 4955), dtype=np.float32), "pose": np.zeros((1, ModelConstants.POSE_WIDTH * 2),
dtype=np.float32), "meta": np.zeros((1, 55), dtype=np.float32)}
parsed = parser.parse_outputs(outputs)
assert parsed["plan"].shape == (1, ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)
assert parsed["pose"].shape == (1, ModelConstants.POSE_WIDTH)
assert parsed["meta"].shape == (1, 55)
+121
View File
@@ -0,0 +1,121 @@
import numpy as np
def index_function(idx, max_val=192, max_idx=32):
return max_val * ((idx/max_idx)**2)
class ModelConstants:
# time and distance indices
IDX_N = 33
T_IDXS = [index_function(idx, max_val=10.0) for idx in range(IDX_N)]
X_IDXS = [index_function(idx, max_val=192.0) for idx in range(IDX_N)]
LEAD_T_IDXS = [0., 2., 4., 6., 8., 10.]
LEAD_T_OFFSETS = [0., 2., 4.]
META_T_IDXS = [2., 4., 6., 8., 10.]
# model inputs constants
MODEL_FREQ = 20
FEATURE_LEN = 512
HISTORY_BUFFER_LEN = 99
DESIRE_LEN = 8
TRAFFIC_CONVENTION_LEN = 2
NAV_FEATURE_LEN = 256
NAV_INSTRUCTION_LEN = 150
LAT_PLANNER_STATE_LEN = 4
LATERAL_CONTROL_PARAMS_LEN = 2
PREV_DESIRED_CURV_LEN = 1
# model outputs constants
FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32)
FCW_THRESHOLDS_3MS2 = np.array([.7, .7], dtype=np.float32)
FCW_5MS2_PROBS_WIDTH = 5
FCW_3MS2_PROBS_WIDTH = 2
DISENGAGE_WIDTH = 5
POSE_WIDTH = 6
WIDE_FROM_DEVICE_WIDTH = 3
SIM_POSE_WIDTH = 6
LEAD_WIDTH = 4
LANE_LINES_WIDTH = 2
ROAD_EDGES_WIDTH = 2
PLAN_WIDTH = 15
DESIRE_PRED_WIDTH = 8
LAT_PLANNER_SOLUTION_WIDTH = 4
DESIRED_CURV_WIDTH = 1
NUM_LANE_LINES = 4
NUM_ROAD_EDGES = 2
LEAD_TRAJ_LEN = 6
DESIRE_PRED_LEN = 4
PLAN_MHP_N = 5
LEAD_MHP_N = 2
PLAN_MHP_SELECTION = 1
LEAD_MHP_SELECTION = 3
FCW_THRESHOLD_5MS2_HIGH = 0.15
FCW_THRESHOLD_5MS2_LOW = 0.05
FCW_THRESHOLD_3MS2 = 0.7
CONFIDENCE_BUFFER_LEN = 5
RYG_GREEN = 0.01165
RYG_YELLOW = 0.06157
POLY_PATH_DEGREE = 4
# model outputs slices
class Plan:
POSITION = slice(0, 3)
VELOCITY = slice(3, 6)
ACCELERATION = slice(6, 9)
T_FROM_CURRENT_EULER = slice(9, 12)
ORIENTATION_RATE = slice(12, 15)
class Meta:
ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 31, 6)
BRAKE_DISENGAGE = slice(2, 31, 6)
STEER_OVERRIDE = slice(3, 31, 6)
HARD_BRAKE_3 = slice(4, 31, 6)
HARD_BRAKE_4 = slice(5, 31, 6)
HARD_BRAKE_5 = slice(6, 31, 6)
# next 0, 2, 4, 6, 8, 10 seconds
GAS_PRESS = slice(31, 55, 4)
BRAKE_PRESS = slice(32, 55, 4)
LEFT_BLINKER = slice(33, 55, 4)
RIGHT_BLINKER = slice(34, 55, 4)
class MetaTombRaider:
ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 41, 8)
BRAKE_DISENGAGE = slice(2, 41, 8)
STEER_OVERRIDE = slice(3, 41, 8)
HARD_BRAKE_3 = slice(4, 41, 8)
HARD_BRAKE_4 = slice(5, 41, 8)
HARD_BRAKE_5 = slice(6, 41, 8)
GAS_PRESS = slice(7, 41, 8)
BRAKE_PRESS = slice(8, 41, 8)
# next 0, 2, 4, 6, 8, 10 seconds
LEFT_BLINKER = slice(41, 53, 2)
RIGHT_BLINKER = slice(42, 53, 2)
class MetaSimPose:
ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 36, 7)
BRAKE_DISENGAGE = slice(2, 36, 7)
STEER_OVERRIDE = slice(3, 36, 7)
HARD_BRAKE_3 = slice(4, 36, 7)
HARD_BRAKE_4 = slice(5, 36, 7)
HARD_BRAKE_5 = slice(6, 36, 7)
GAS_PRESS = slice(7, 36, 7)
# next 0, 2, 4, 6, 8, 10 seconds
LEFT_BLINKER = slice(36, 48, 2)
RIGHT_BLINKER = slice(37, 48, 2)
+1 -1
View File
@@ -139,7 +139,7 @@ class ModelCache:
class ModelFetcher:
"""Handles fetching and caching of model data from remote source"""
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v22.json"
MODEL_URL_CHESTNUT = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_chestnut_v25.json"
MODEL_URL_CHESTNUT = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_chestnut_v23.json"
MODEL_SOURCES = {
"qcom": (MODEL_URL, ""),
+31
View File
@@ -7,11 +7,14 @@ See the LICENSE.md file in the root directory for more details.
import hashlib
import os
import pickle
from pathlib import Path
import numpy as np
from openpilot.cereal import custom
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.sunnypilot.models.constants import Meta, MetaSimPose, MetaTombRaider
from openpilot.common.hardware.hw import Paths
from openpilot.selfdrive.modeld.helpers import chestnut_present
@@ -19,6 +22,7 @@ from openpilot.selfdrive.modeld.helpers import chestnut_present
REQUIRED_JSON_VERSION = 19
CUSTOM_MODEL_PATH = Paths.model_root()
METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl'
ModelManager = custom.ModelManagerSP
ACTIVE_BUNDLE_KEYS = {
@@ -197,6 +201,33 @@ def _get_model():
return None
def load_metadata():
metadata_path = METADATA_PATH
with open(metadata_path, 'rb') as f:
return pickle.load(f)
def prepare_inputs(model_metadata: dict) -> dict[str, np.ndarray]:
return {
key: np.zeros(shape, dtype=np.float32).flatten()
for key, shape in model_metadata['input_shapes'].items()
if 'img' not in key
}
def load_meta_constants(model_metadata: dict):
""" Loads the appropriate meta model class based on key shapes"""
if 'sim_pose' in model_metadata['input_shapes']:
return MetaSimPose
meta_slice = model_metadata['output_slices']['meta']
if (meta_slice.start, meta_slice.stop, meta_slice.step) == (5868, 5921, None):
return MetaTombRaider
return Meta
# The following method(s) are modeld helper methods
def plan_x_idxs_helper(constants, plan, model_output) -> list[float]:
# times at X_IDXS according to plan.
@@ -0,0 +1,24 @@
import requests
from openpilot.sunnypilot.models.tinygrad_ref import get_tinygrad_ref
from openpilot.sunnypilot.models.fetcher import ModelFetcher
from openpilot.common.test import OpenpilotTestCase
def fetch_tinygrad_ref():
response = requests.get(ModelFetcher.MODEL_URL, timeout=10)
response.raise_for_status()
json_data = response.json()
return json_data.get("tinygrad_ref")
class TestTinygradRef(OpenpilotTestCase):
def test_tinygrad_ref(self):
current_ref = get_tinygrad_ref()
remote_ref = fetch_tinygrad_ref()
assert remote_ref == current_ref, (
f"""tinygrad_repo ref does not match remote tinygrad_ref of current compiled driving models json.
Current: {current_ref}
Remote: {remote_ref}
Please run build-all workflow to update models."""
)
print("tinygrad_repo ref matches current compiled driving models json ref.")
@@ -115,7 +115,7 @@ class IntelligentCruiseButtonManagement:
self.is_ready = ready and not button_pressed
def run(self, CS: car.CarState, CC: car.CarControl, LP_SP: custom.LongitudinalPlanSP, is_metric: bool) -> None:
if self.CP_SP.pcmCruiseSpeed or not self.CP_SP.intelligentCruiseButtonManagementAvailable:
if self.CP_SP.pcmCruiseSpeed:
return
self.is_metric = is_metric
@@ -136,10 +136,6 @@ def initialize_params(params) -> list[dict[str, Any]]:
keys.extend([
"ToyotaEnforceStockLongitudinal",
"ToyotaStopAndGoHack",
"ToyotaTSS2Long",
"ToyotaEnhancedBsm",
"ToyotaAutoHold",
"ToyotaBrakeOnset",
])
return [{k: params.get(k, return_default=True)} for k in keys]
@@ -1,26 +1,14 @@
from opendbc.can.parser import CANParser
from opendbc.car import create_button_events
from opendbc.car.structs import car
from opendbc.car.toyota.carstate import get_virtual_cruise_button, VIRTUAL_CRUISE_BUTTONS
from openpilot.cereal import custom
from openpilot.common.constants import CV
from openpilot.common.parameterized import parameterized, parameterized_class
from openpilot.common.params import Params
from openpilot.common.test import OpenpilotTestCase
from openpilot.selfdrive.car.cruise import TOYOTA_VIRTUAL_CRUISE_LONG_PRESS, VCruiseHelper, V_CRUISE_INITIAL, V_CRUISE_UNSET
from openpilot.selfdrive.car.cruise import V_CRUISE_INITIAL
from openpilot.selfdrive.car.tests.test_cruise_speed import TestVCruiseHelper
from openpilot.sunnypilot.selfdrive.car.interfaces import initialize_params
ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
class TestToyotaParamsHandoff(OpenpilotTestCase):
def test_tss2_long_tuning_param_is_forwarded_to_opendbc(self):
keys = {next(iter(entry)) for entry in initialize_params(Params())}
assert "ToyotaTSS2Long" in keys
# TODO: test pcmCruise and pcmCruiseSpeed
@parameterized_class(('pcm_cruise', 'pcm_cruise_speed'), [(False, True)])
class TestCustomAccIncrements(TestVCruiseHelper):
@@ -126,8 +114,8 @@ class TestCustomAccIncrements(TestVCruiseHelper):
def test_rounding_behavior(self):
"""Test rounding behavior for 5 and 10 increments"""
test_cases = [
(47, 5, 50), # 47 -> 50 (round up to next 5)
(45, 5, 50), # 45 -> 50 (already at 5, increment by 5)
(47, 5, 50), # 47 -> 50 (round up to next 5)
(45, 5, 50), # 45 -> 50 (already at 5, increment by 5)
(43, 10, 50), # 43 -> 50 (round up to next 10)
(40, 10, 50), # 40 -> 50 (already at 10, increment by 10)
]
@@ -158,302 +146,3 @@ class TestCustomAccIncrements(TestVCruiseHelper):
initial_speed = self.v_cruise_helper.v_cruise_kph
self.press_button_long(ButtonType.accelCruise)
assert self.v_cruise_helper.v_cruise_kph == initial_speed + 10 # Should fallback to 10
class TestToyotaVirtualCruiseSpeed(OpenpilotTestCase):
def setup_method(self):
self.params = Params()
self.params.put_bool("CustomAccIncrementsEnabled", True, block=True)
self.params.put("CustomAccShortPressIncrement", 5, block=True)
self.params.put("CustomAccLongPressIncrement", 5, block=True)
CP = car.CarParams(brand="toyota", pcmCruise=True, openpilotLongitudinalControl=True)
CP_SP = custom.CarParamsSP(pcmCruiseSpeed=False)
self.v_cruise_helper = VCruiseHelper(CP, CP_SP)
self.v_cruise_helper.read_custom_set_speed_params()
self.route_parser = CANParser("toyota_nodsu_pt_generated", [("CLUTCH", 16)], 0)
self.route_button = 0
@staticmethod
def car_state(canonical_kph, cluster_kph, *, available=True, standstill=False, gas_pressed=False, v_ego_kph=0.0, button_events=None):
CS = car.CarState(
gasPressed=gas_pressed,
vEgo=v_ego_kph * CV.KPH_TO_MS,
cruiseState={
"available": available,
"speed": canonical_kph * CV.KPH_TO_MS,
"speedCluster": cluster_kph * CV.KPH_TO_MS,
"standstill": standstill,
},
)
CS.buttonEvents = button_events or []
return CS
def seed_enabled(self, canonical_kph, cluster_kph, *, is_metric=True):
CS = self.car_state(canonical_kph, cluster_kph)
self.v_cruise_helper.update_v_cruise(CS, enabled=False, is_metric=is_metric)
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=is_metric)
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=is_metric)
assert self.v_cruise_helper.v_cruise_kph == canonical_kph
assert self.v_cruise_helper.v_cruise_cluster_kph == cluster_kph
def press(self, button_type, canonical_kph, cluster_kph, hold_frames=0, *, standstill=False, gas_pressed=False, v_ego_kph=0.0, is_metric=True):
pressed = [ButtonEvent(type=button_type, pressed=True)]
self.v_cruise_helper.update_v_cruise(
self.car_state(canonical_kph, cluster_kph, standstill=standstill, gas_pressed=gas_pressed, v_ego_kph=v_ego_kph, button_events=pressed),
enabled=True,
is_metric=is_metric,
)
for _ in range(hold_frames):
self.v_cruise_helper.update_v_cruise(
self.car_state(canonical_kph, cluster_kph, standstill=standstill, gas_pressed=gas_pressed, v_ego_kph=v_ego_kph),
enabled=True,
is_metric=is_metric,
)
released = [ButtonEvent(type=button_type, pressed=False)]
self.v_cruise_helper.update_v_cruise(
self.car_state(canonical_kph, cluster_kph, standstill=standstill, gas_pressed=gas_pressed, v_ego_kph=v_ego_kph, button_events=released),
enabled=True,
is_metric=is_metric,
)
def set_increments(self, short_increment, long_increment):
self.params.put("CustomAccShortPressIncrement", short_increment, block=True)
self.params.put("CustomAccLongPressIncrement", long_increment, block=True)
self.v_cruise_helper.read_custom_set_speed_params()
def assert_kph_almost_equal(self, actual, expected):
self.assertAlmostEqual(actual, expected, delta=abs(expected) * 1e-6)
def route_button_events(self, payload):
self.route_parser.update((1, [(0x361, bytes.fromhex(payload), 0)]))
current = get_virtual_cruise_button(
self.route_parser.vl["CLUTCH"]["CRUISE_RES"],
self.route_parser.vl["CLUTCH"]["CRUISE_SET"],
)
events = create_button_events(current, self.route_button, VIRTUAL_CRUISE_BUTTONS)
self.route_button = current
return events
def test_short_press_rounds_display_target_and_preserves_offset(self):
self.seed_enabled(27, 31)
self.press(ButtonType.accelCruise, 28, 32)
assert self.v_cruise_helper.v_cruise_kph == 31
assert self.v_cruise_helper.v_cruise_cluster_kph == 35
def test_decel_at_display_minimum_does_not_increase_target(self):
self.seed_enabled(26, 30)
self.press(ButtonType.decelCruise, 25, 29)
assert self.v_cruise_helper.v_cruise_kph == 26
assert self.v_cruise_helper.v_cruise_cluster_kph == 30
@parameterized.expand((52, TOYOTA_VIRTUAL_CRUISE_LONG_PRESS - 1))
def test_route_length_short_press_is_not_a_long_press(self, hold_frames):
self.set_increments(short_increment=2, long_increment=5)
self.seed_enabled(27, 31)
self.press(ButtonType.accelCruise, 28, 32, hold_frames=hold_frames)
assert self.v_cruise_helper.v_cruise_kph == 29
assert self.v_cruise_helper.v_cruise_cluster_kph == 33
def test_toyota_long_press_uses_route_validated_cadence_and_suppresses_release(self):
self.set_increments(short_increment=2, long_increment=5)
self.seed_enabled(27, 31)
pressed = [ButtonEvent(type=ButtonType.accelCruise, pressed=True)]
self.v_cruise_helper.update_v_cruise(self.car_state(31, 35, button_events=pressed), enabled=True, is_metric=True)
for _ in range(TOYOTA_VIRTUAL_CRUISE_LONG_PRESS):
self.v_cruise_helper.update_v_cruise(self.car_state(31, 35), enabled=True, is_metric=True)
assert self.v_cruise_helper.v_cruise_kph == 31
assert self.v_cruise_helper.v_cruise_cluster_kph == 35
released = [ButtonEvent(type=ButtonType.accelCruise, pressed=False)]
self.v_cruise_helper.update_v_cruise(self.car_state(31, 35, button_events=released), enabled=True, is_metric=True)
assert self.v_cruise_helper.v_cruise_kph == 31
assert self.v_cruise_helper.v_cruise_cluster_kph == 35
def test_route_4_32_second_hold_repeats_six_times(self):
self.seed_enabled(26, 30)
self.press(ButtonType.accelCruise, 30, 34, hold_frames=432)
assert self.v_cruise_helper.v_cruise_kph == 56
assert self.v_cruise_helper.v_cruise_cluster_kph == 60
def test_maximum_boundary_caps_pair_and_preserves_offset(self):
self.seed_enabled(141, 145)
self.press(ButtonType.accelCruise, 142, 146)
assert self.v_cruise_helper.v_cruise_kph == 141
assert self.v_cruise_helper.v_cruise_cluster_kph == 145
self.press(ButtonType.accelCruise, 143, 147)
assert self.v_cruise_helper.v_cruise_kph == 141
assert self.v_cruise_helper.v_cruise_cluster_kph == 145
@parameterized.expand(
(
(25, 29, ButtonType.decelCruise),
(141, 147, ButtonType.accelCruise),
)
)
def test_out_of_range_raw_pair_is_not_moved_in_opposite_direction(self, canonical_kph, cluster_kph, button_type):
self.seed_enabled(canonical_kph, cluster_kph)
self.press(button_type, canonical_kph, cluster_kph)
assert self.v_cruise_helper.v_cruise_kph == canonical_kph
assert self.v_cruise_helper.v_cruise_cluster_kph == cluster_kph
def test_imperial_increment_preserves_canonical_cluster_pair(self):
self.seed_enabled(45, 50, is_metric=False)
self.press(ButtonType.accelCruise, 46, 51, is_metric=False)
assert self.v_cruise_helper.v_cruise_kph == 51
assert self.v_cruise_helper.v_cruise_cluster_kph == 56
def test_engagement_button_held_does_not_change_target(self):
initial = self.car_state(27, 31)
self.v_cruise_helper.update_v_cruise(initial, enabled=False, is_metric=True)
pressed = [ButtonEvent(type=ButtonType.decelCruise, pressed=True)]
self.v_cruise_helper.update_v_cruise(self.car_state(27, 31, button_events=pressed), enabled=False, is_metric=True)
for _ in range(TOYOTA_VIRTUAL_CRUISE_LONG_PRESS + 10):
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=True, is_metric=True)
released = [ButtonEvent(type=ButtonType.decelCruise, pressed=False)]
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32, button_events=released), enabled=True, is_metric=True)
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=True, is_metric=True)
assert self.v_cruise_helper.v_cruise_kph == 28
assert self.v_cruise_helper.v_cruise_cluster_kph == 32
def test_delayed_pcm_target_seeds_before_software_ownership(self):
invalid = self.car_state(0, 0)
self.v_cruise_helper.update_v_cruise(invalid, enabled=False, is_metric=True)
release = [ButtonEvent(type=ButtonType.decelCruise, pressed=False)]
for _ in range(4):
self.v_cruise_helper.update_v_cruise(self.car_state(0, 0, button_events=release), enabled=True, is_metric=True)
assert self.v_cruise_helper.v_cruise_kph == V_CRUISE_UNSET
assert self.v_cruise_helper.v_cruise_cluster_kph == V_CRUISE_UNSET
self.v_cruise_helper.update_v_cruise(self.car_state(27, 31), enabled=True, is_metric=True)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_kph, 27)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_cluster_kph, 31)
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=True, is_metric=True)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_kph, 27)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_cluster_kph, 31)
def test_route_payload_short_press_drives_virtual_target(self):
self.seed_enabled(27, 31)
pressed = self.route_button_events("a61a0000561a1a81")
self.v_cruise_helper.update_v_cruise(self.car_state(27, 31, button_events=pressed), enabled=True, is_metric=True)
for _ in range(52):
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=True, is_metric=True)
released = self.route_button_events("861a0000561b1a81")
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32, button_events=released), enabled=True, is_metric=True)
assert self.v_cruise_helper.v_cruise_kph == 31
assert self.v_cruise_helper.v_cruise_cluster_kph == 35
def test_prius_route_payload_short_set_drives_virtual_target(self):
self.seed_enabled(31, 35)
pressed = self.route_button_events("965f000056666585")
self.v_cruise_helper.update_v_cruise(self.car_state(31, 35, button_events=pressed), enabled=True, is_metric=True)
for _ in range(45):
self.v_cruise_helper.update_v_cruise(self.car_state(30, 34), enabled=True, is_metric=True)
released = self.route_button_events("865f000056666585")
self.v_cruise_helper.update_v_cruise(self.car_state(30, 34, button_events=released), enabled=True, is_metric=True)
assert self.v_cruise_helper.v_cruise_kph == 26
assert self.v_cruise_helper.v_cruise_cluster_kph == 30
def test_prius_route_payload_standstill_res_does_not_change_target(self):
self.seed_enabled(27, 31)
pressed = self.route_button_events("a61b0000561c1c80")
self.v_cruise_helper.update_v_cruise(
self.car_state(27, 31, standstill=True, button_events=pressed),
enabled=True,
is_metric=True,
)
for _ in range(TOYOTA_VIRTUAL_CRUISE_LONG_PRESS):
self.v_cruise_helper.update_v_cruise(self.car_state(27, 31, standstill=True), enabled=True, is_metric=True)
released = self.route_button_events("865f000056666585")
self.v_cruise_helper.update_v_cruise(
self.car_state(27, 31, standstill=True, button_events=released),
enabled=True,
is_metric=True,
)
assert self.v_cruise_helper.v_cruise_kph == 27
assert self.v_cruise_helper.v_cruise_cluster_kph == 31
def test_route_payload_disengage_mid_hold_clears_pending_action(self):
self.seed_enabled(27, 31)
pressed = self.route_button_events("a61a0000561a1a81")
self.v_cruise_helper.update_v_cruise(self.car_state(27, 31, button_events=pressed), enabled=True, is_metric=True)
for _ in range(30):
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=True, is_metric=True)
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=False, is_metric=True)
released = self.route_button_events("861a0000561b1a81")
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32, available=False, button_events=released), enabled=False, is_metric=True)
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=False, is_metric=True)
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=True, is_metric=True)
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=True, is_metric=True)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_kph, 28)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_cluster_kph, 32)
def test_standstill_resume_does_not_change_target(self):
self.seed_enabled(27, 31)
self.press(ButtonType.accelCruise, 27, 31, standstill=True)
assert self.v_cruise_helper.v_cruise_kph == 27
assert self.v_cruise_helper.v_cruise_cluster_kph == 31
def test_disengagement_discards_virtual_target_and_reseeds_raw_pair(self):
self.seed_enabled(27, 31)
self.press(ButtonType.accelCruise, 28, 32)
assert self.v_cruise_helper.v_cruise_kph == 31
raw = self.car_state(28, 32)
self.v_cruise_helper.update_v_cruise(raw, enabled=False, is_metric=True)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_kph, 28)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_cluster_kph, 32)
self.v_cruise_helper.update_v_cruise(raw, enabled=True, is_metric=True)
self.v_cruise_helper.update_v_cruise(raw, enabled=True, is_metric=True)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_kph, 28)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_cluster_kph, 32)
def test_unavailable_and_mads_handback_discard_virtual_target(self):
self.seed_enabled(27, 31)
self.press(ButtonType.accelCruise, 28, 32)
assert self.v_cruise_helper.v_cruise_kph == 31
self.v_cruise_helper.update_v_cruise(self.car_state(28, 32), enabled=False, is_metric=True)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_kph, 28)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_cluster_kph, 32)
self.v_cruise_helper.update_v_cruise(self.car_state(0, 0, available=False), enabled=False, is_metric=True)
assert self.v_cruise_helper.v_cruise_kph == V_CRUISE_UNSET
assert self.v_cruise_helper.v_cruise_cluster_kph == V_CRUISE_UNSET
self.v_cruise_helper.update_v_cruise(self.car_state(29, 33), enabled=False, is_metric=True)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_kph, 29)
self.assert_kph_almost_equal(self.v_cruise_helper.v_cruise_cluster_kph, 33)
def test_set_during_gas_override_clips_target_to_ego_speed(self):
self.seed_enabled(27, 31)
self.press(ButtonType.decelCruise, 26, 30, gas_pressed=True, v_ego_kph=50)
assert self.v_cruise_helper.v_cruise_kph == 50
assert self.v_cruise_helper.v_cruise_cluster_kph == 54
@@ -12,13 +12,11 @@ from openpilot.cereal import log, custom
from opendbc.car import structs
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral
from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_v0 import LatControlTorque as LatControlTorqueV0
from openpilot.sunnypilot.selfdrive.controls.lib.stopping_controller import StoppingController
class ControlsExt(ModelStateBase):
@@ -33,11 +31,6 @@ class ControlsExt(ModelStateBase):
self.CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP)
cloudlog.info("controlsd_ext got CarParamsSP")
self.stopping_controller = StoppingController(self.CP.stopAccel) if params.get_bool("SunnypilotStoppingController") else None
# the stock LongControl.update() has already run by the time long_accel_sp() sees it, so carry the pair it was
# handed last frame; a fresh LongControl starts at (off, 0.0)
self._stopping_prev = (LongCtrlState.off, 0.0)
self.sm_services_ext = ['radarState', 'selfdriveStateSP']
self.pm_services_ext = ['carControlSP']
@@ -54,27 +47,6 @@ class ControlsExt(ModelStateBase):
else:
return lac
def long_accel_sp(self, actuators, CS, long_plan, accel_limits: tuple[float, float]) -> None:
"""sunnypilot: terminal-stop policy applied after the stock LongControl.update() (see stopping_controller.py)."""
stock_state = self.LoC.long_control_state
stock_accel = actuators.accel
prev_state, prev_accel = self._stopping_prev
self._stopping_prev = (stock_state, stock_accel)
if self.stopping_controller is None:
return
state, accel = self.stopping_controller.update(
prev_state, stock_state, CS, long_plan.aTarget, prev_accel, stock_accel, accel_limits, long_plan.hasLead,
pitch=self.calibrated_pose.orientation.pitch if self.calibrated_pose is not None else None,
a_long=self.calibrated_pose.acceleration.x if self.calibrated_pose is not None else None)
if state != stock_state:
self.LoC.reset()
self.LoC.long_control_state = state
self.LoC.last_output_accel = accel
self._stopping_prev = (state, accel)
actuators.accel = float(accel)
def get_params_sp(self, sm: messaging.SubMaster) -> None:
if time.monotonic() - self._param_update_time > PARAMS_UPDATE_PERIOD:
self.blinker_pause_lateral.get_params()
@@ -1,128 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import numpy as np
from openpilot.cereal import custom
from openpilot.common.params import Params
from openpilot.sunnypilot import get_sanitize_int_param
AccelProfile = custom.LongitudinalPlanSP.AccelController.Profile
MAX_ACCEL_BREAKPOINTS = [0., 3., 12, 24., 36.] # m/s
MAX_ACCEL_PROFILES = {
AccelProfile.eco: [1.75, 1.50, 0.40, 0.15, 0.12],
AccelProfile.normal: [1.95, 1.80, 0.70, 0.36, 0.25],
AccelProfile.sport: [2.00, 2.00, 1.20, 0.70, 0.50],
}
# Eco while the hybrid's engine is OFF (battery-first mode, ported from tncr18): keep wheel power m*a*v + road load under
# the ~11-14 kW at which a Corolla hybrid starts its engine (30 Corolla Cross rlogs, threshold not SOC dependent):
# 60 km/h 0.25, 70 km/h 0.16, 80 km/h 0.12. Launch values are the Altis eco ones. Once the engine runs there is nothing
# to save, so the normal eco line takes over until it stops again.
ECO_ENGINE_OFF_BP = [0., 3., 12., 16.67, 19.44, 22.22, 24., 36.] # m/s
# The first two rows follow the eco line above: battery-first may never launch harder than eco. They still held the
# pre-retune eco values (1.85 / 1.55) in the source this was ported from, which put engine-off above eco at 0-3 m/s.
ECO_ENGINE_OFF_MAX_ACCEL = [1.75, 1.50, 0.35, 0.25, 0.16, 0.12, 0.08, 0.06]
# Cruise deceleration to a lowered set speed (no lead). Normal/sport: one CONSTANT decel latched when the gap opens
# (gap / response time, never gentler than CRUISE_DECEL_ACCEL) and held, so the car slows on a straight line instead of
# the proportional gap/time law, which braked hardest at the start and dragged a long tail; more presses = firmer.
# Eco (driver request 2026-09-25/26): never hurry to the new set speed however far it is or however many presses.
# Above 70 km/h: pure coasting - the target decel IS the planner's pitch-aware coast accel (get_coast_accel), so the
# PCM neither adds throttle nor regen; the speed table above 70 is only the fallback when no coast value is given
# (road-load estimate of a 1500 kg Corolla Cross: -0.22 at 70, -0.30 at 90, -0.40 at 120). Below 60 km/h -0.40
# (regen), blended into coasting between 60 and 70 as the speed falls. Whether -0.40 is too firm is for the road test.
CRUISE_DECEL_RESPONSE_TIME = { # seconds to close the gap (normal/sport)
AccelProfile.normal: 3.5,
AccelProfile.sport: 3.0,
}
CRUISE_DECEL_ACCEL = { # m/s^2; gentlest constant decel (normal/sport)
AccelProfile.normal: -0.50,
AccelProfile.sport: -0.65,
}
ECO_CRUISE_DECEL_BP = [0., 16.67, 19.44, 25.0, 33.3] # m/s (0, 60, 70, 90, 120 km/h)
ECO_CRUISE_DECEL_V = [-0.40, -0.40, -0.22, -0.30, -0.40] # m/s^2; the >= 70 km/h part is only the FALLBACK coast estimate
ECO_COAST_BLEND_BP = [16.67, 19.44] # m/s: -0.40 at 60 km/h blends into pure coasting by 70 km/h
ECO_COAST_MIN, ECO_COAST_MAX = -1.2, -0.05 # m/s^2; sanity clip on the planner's pitch-aware coast accel
# Eco lead pull-away boost (driver 2026-10-01 night): below 80 km/h, when a detected lead is pulling away (gap opening),
# eco may accelerate a little harder for a while so the car keeps up, eased in and out; with no lead nothing changes.
# The boost scales with how fast the gap opens, fades out from 70 to 80 km/h, and never lifts eco above the normal line.
ECO_BOOST_DV_BP = [0.3, 2.5] # m/s, lead speed minus ego speed
ECO_BOOST_V = [0.0, 0.35] # m/s^2 added to the eco limit
ECO_BOOST_FADE_BP = [19.44, 22.22] # m/s (70 -> 80 km/h): full -> none
ECO_BOOST_D_MAX = 80.0 # m, only a lead this close counts
ECO_BOOST_PROB_MIN = 0.5
ECO_BOOST_RISE = 0.25 # m/s^3, how fast the boost builds
ECO_BOOST_FALL = 0.5 # m/s^3, how fast it fades
ECO_BOOST_DT = 0.05 # s, planner step
CRUISE_DECEL_TAPER_TIME = 1.0 # s; inside |decel| * this of the target the decel eases off proportionally (no overshoot)
class AccelController:
def __init__(self):
self.params = Params()
self._cruise_decel: float | None = None
self._cruise_decel_target: float | None = None
self._boost = 0.0
self.update()
def update(self) -> None:
self._profile = get_sanitize_int_param("AccelPersonality", AccelProfile.eco, AccelProfile.sport, self.params)
self._enabled = self.params.get_bool("AccelPersonalityEnabled")
@property
def profile(self) -> int:
return self._profile
def is_enabled(self) -> bool:
return self._enabled
def get_max_accel(self, v_ego: float, engine_off: bool = False, lead=None) -> float:
if engine_off and self._profile == AccelProfile.eco:
base = float(np.interp(max(0.0, v_ego), ECO_ENGINE_OFF_BP, ECO_ENGINE_OFF_MAX_ACCEL))
else:
base = float(np.interp(max(0.0, v_ego), MAX_ACCEL_BREAKPOINTS, MAX_ACCEL_PROFILES[self._profile]))
if self._profile != AccelProfile.eco:
self._boost = 0.0
return base
return base + self._eco_lead_boost(v_ego, base, lead)
def _eco_lead_boost(self, v_ego: float, base: float, lead) -> float:
target = 0.0
if lead is not None and lead.present and lead.modelProb > ECO_BOOST_PROB_MIN and lead.dRel < ECO_BOOST_D_MAX:
target = float(np.interp(lead.vLead - v_ego, ECO_BOOST_DV_BP, ECO_BOOST_V))
target *= float(np.interp(v_ego, ECO_BOOST_FADE_BP, [1.0, 0.0]))
normal = float(np.interp(max(0.0, v_ego), MAX_ACCEL_BREAKPOINTS, MAX_ACCEL_PROFILES[AccelProfile.normal]))
target = float(np.clip(target, 0.0, max(normal - base, 0.0)))
step = (ECO_BOOST_RISE if target > self._boost else ECO_BOOST_FALL) * ECO_BOOST_DT
self._boost = float(np.clip(target, self._boost - step, self._boost + step))
return self._boost
def get_cruise_target(self, v_ego: float, v_target: float, accel_coast: float | None = None) -> float:
if not np.isfinite(v_target) or v_target <= 0.0 or v_target >= v_ego:
self._cruise_decel = None
self._cruise_decel_target = None
return v_target
target_delta = v_target - v_ego
if self._profile == AccelProfile.eco:
# speed-scheduled, independent of the gap and of how many presses opened it
decel = float(np.interp(v_ego, ECO_CRUISE_DECEL_BP, ECO_CRUISE_DECEL_V))
if accel_coast is not None and np.isfinite(accel_coast) and accel_coast < 0.0:
# >= 70 km/h: coast exactly (pitch-aware); 60-70 km/h: blend from the low-speed value into coasting
coast = float(np.clip(accel_coast, ECO_COAST_MIN, ECO_COAST_MAX))
w = float(np.interp(v_ego, ECO_COAST_BLEND_BP, [0.0, 1.0]))
decel = (1.0 - w) * ECO_CRUISE_DECEL_V[1] + w * coast
else:
if self._cruise_decel is None or self._cruise_decel_target != v_target:
# a new (or changed) gap: latch one constant decel for this episode
self._cruise_decel = min(CRUISE_DECEL_ACCEL[self._profile], target_delta / CRUISE_DECEL_RESPONSE_TIME[self._profile])
self._cruise_decel_target = v_target
decel = self._cruise_decel
# taper only once the target is close (both terms are negative; max = the gentler one) so the speed lands cleanly
decel = max(decel, target_delta / CRUISE_DECEL_TAPER_TIME)
return float(v_ego + decel)
@@ -1,142 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from types import SimpleNamespace
import numpy as np
from opendbc.car.interfaces import ACCEL_MAX
from openpilot.common.params import Params
from openpilot.common.test import OpenpilotTestCase
from openpilot.sunnypilot.selfdrive.controls.lib.accel_controller.accel_controller import (
AccelController, AccelProfile, CRUISE_DECEL_ACCEL, CRUISE_DECEL_RESPONSE_TIME, ECO_CRUISE_DECEL_BP,
ECO_CRUISE_DECEL_V, ECO_ENGINE_OFF_MAX_ACCEL, MAX_ACCEL_BREAKPOINTS, MAX_ACCEL_PROFILES,
)
class TestAccelController(OpenpilotTestCase):
def setUp(self):
self.params = Params()
self.params.put_bool("AccelPersonalityEnabled", True, block=True)
self.params.put("AccelPersonality", AccelProfile.normal, block=True)
def set_profile(self, profile: int) -> AccelController:
self.params.put("AccelPersonality", profile, block=True)
return AccelController()
def test_table_breakpoints(self):
for profile, values in MAX_ACCEL_PROFILES.items():
controller = self.set_profile(profile)
for speed, expected in zip(MAX_ACCEL_BREAKPOINTS, values, strict=True):
assert controller.get_max_accel(speed) == expected
def test_profiles_are_ordered_monotonic_and_bounded(self):
controllers = {
profile: self.set_profile(profile)
for profile in (AccelProfile.eco, AccelProfile.normal, AccelProfile.sport)
}
previous = {profile: float("inf") for profile in controllers}
for speed in np.linspace(0.0, 55.0, 551):
values = {profile: controller.get_max_accel(speed) for profile, controller in controllers.items()}
assert 0.0 <= values[AccelProfile.eco] <= values[AccelProfile.normal] <= values[AccelProfile.sport] <= ACCEL_MAX
for profile, value in values.items():
assert value <= previous[profile]
previous[profile] = value
def test_engine_off_line_only_lowers_eco(self):
speeds = np.linspace(0.0, 40.0, 401)
eco = self.set_profile(AccelProfile.eco)
for speed in speeds:
assert eco.get_max_accel(speed, engine_off=True) <= eco.get_max_accel(speed) + 1e-12
for speed, expected in zip(MAX_ACCEL_BREAKPOINTS, ECO_ENGINE_OFF_MAX_ACCEL, strict=True):
assert eco.get_max_accel(speed, engine_off=True) == expected
for profile in (AccelProfile.normal, AccelProfile.sport):
controller = self.set_profile(profile)
for speed in speeds:
assert controller.get_max_accel(speed, engine_off=True) == controller.get_max_accel(speed)
def test_engine_off_eco_line_respects_hybrid_power_budget(self):
controller = self.set_profile(AccelProfile.eco)
for kph, budget_kw in ((60, 11.5), (70, 11.5), (80, 13.5)):
speed = kph / 3.6
road_load = (0.010 * 1500 * 9.81 + 0.5 * 1.2 * 0.82 * speed * speed) * speed
wheel_power = 1500 * controller.get_max_accel(speed, engine_off=True) * speed + road_load
assert wheel_power <= budget_kw * 1e3, kph
def test_lead_boost_is_eco_only_and_absent_without_a_lead(self):
# the eco lead pull-away boost: no lead is the plain table lookup, and normal/sport ignore the lead entirely
eco = self.set_profile(AccelProfile.eco)
assert eco.get_max_accel(1.0, lead=None) == eco.get_max_accel(1.0)
assert eco.get_max_accel(20.0, lead=None) == eco.get_max_accel(20.0)
normal = self.set_profile(AccelProfile.normal)
lead = SimpleNamespace(present=True, modelProb=0.9, dRel=30.0, vLead=23.0)
assert normal.get_max_accel(20.0, lead=lead) == normal.get_max_accel(20.0)
def test_sport_uses_openpilot_accel_max_at_launch(self):
controller = self.set_profile(AccelProfile.sport)
assert controller.get_max_accel(0.0) == ACCEL_MAX
def test_negative_speed_uses_standstill_value(self):
controller = self.set_profile(AccelProfile.sport)
assert controller.get_max_accel(-1.0) == MAX_ACCEL_PROFILES[AccelProfile.sport][0]
def test_eco_cruise_decel_is_speed_scheduled_not_gap_scheduled(self):
controller = self.set_profile(AccelProfile.eco)
a_small = controller.get_cruise_target(22.0, 20.0) - 22.0
a_big = controller.get_cruise_target(22.0, 12.0) - 22.0
assert np.isclose(a_small, a_big)
assert np.isclose(controller.get_cruise_target(19.44, 10.0) - 19.44, -0.22)
assert np.isclose(controller.get_cruise_target(15.0, 10.0) - 15.0, -0.40)
assert -0.40 < controller.get_cruise_target(18.0, 10.0) - 18.0 < -0.22
# with the planner's coast accel available, >= 70 km/h coasts exactly (pitch-aware), 60-70 blends into it
assert np.isclose(controller.get_cruise_target(22.0, 10.0, accel_coast=-0.27) - 22.0, -0.27)
assert np.isclose(controller.get_cruise_target(15.0, 10.0, accel_coast=-0.27) - 15.0, -0.40)
assert -0.40 < controller.get_cruise_target(18.0, 10.0, accel_coast=-0.27) - 18.0 < -0.27
# normal ignores the coast value
normal = self.set_profile(AccelProfile.normal)
assert normal.get_cruise_target(22.0, 20.0, accel_coast=-0.27) == normal.get_cruise_target(22.0, 20.0)
def test_normal_and_sport_cruise_decel_latch_then_taper(self):
for profile in (AccelProfile.normal, AccelProfile.sport):
controller = self.set_profile(profile)
v_target = 10.0
a1 = controller.get_cruise_target(14.0, v_target) - 14.0
a2 = controller.get_cruise_target(13.0, v_target) - 13.0
expected = min(CRUISE_DECEL_ACCEL[profile], (v_target - 14.0) / CRUISE_DECEL_RESPONSE_TIME[profile])
assert a1 < 0 and np.isclose(a1, a2)
assert np.isclose(a1, expected)
a_close = controller.get_cruise_target(10.2, v_target) - 10.2
assert a1 < a_close < 0
assert controller.get_cruise_target(10.0, v_target) == v_target
# A new, lower target re-latches a firmer deceleration.
a_new_target = controller.get_cruise_target(14.0, v_target - 2.0) - 14.0
assert a_new_target <= a1
assert CRUISE_DECEL_RESPONSE_TIME[profile] >= 3.0
def test_non_decel_cruise_requests_clear_latched_deceleration(self):
controller = self.set_profile(AccelProfile.normal)
controller.get_cruise_target(14.0, 10.0)
assert controller.get_cruise_target(10.0, 10.0) == 10.0
assert np.isclose(controller.get_cruise_target(14.0, 10.0) - 14.0,
(10.0 - 14.0) / CRUISE_DECEL_RESPONSE_TIME[AccelProfile.normal])
def test_eco_decel_table_is_valid(self):
assert len(ECO_CRUISE_DECEL_BP) == len(ECO_CRUISE_DECEL_V)
assert all(decel < 0.0 for decel in ECO_CRUISE_DECEL_V)
def test_params_refresh(self):
controller = self.set_profile(AccelProfile.normal)
self.params.put("AccelPersonality", AccelProfile.sport, block=True)
controller.update()
assert controller.profile == AccelProfile.sport
self.params.put_bool("AccelPersonalityEnabled", False, block=True)
controller.update()
assert not controller.is_enabled()

Some files were not shown because too many files have changed in this diff Show More