IQ.Pilot Prebuilt Release @ 67fd9c2

This commit is contained in:
IQ.Lvbs CI [bot]
2026-09-01 20:15:16 -05:00
commit 13523543ee
2549 changed files with 678222 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
import numpy as np
from iqdbc.can import CANPacker
from iqdbc.car import Bus, make_tester_present_msg
from iqdbc.car.lateral import apply_driver_steer_torque_limits, common_fault_avoidance
from iqdbc.car.interfaces import CarControllerBase
from iqdbc.car.subaru import subarucan
from iqdbc.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags
from iqdbc.lvbs.car.subaru.creep_assist import IQStopAndGoController
# FIXME: These limits aren't exact. The real limit is more than likely over a larger time period and
# involves the total steering angle change rather than rate, but these limits work well for now
MAX_STEER_RATE = 25 # deg/s
MAX_STEER_RATE_FRAMES = 7 # tx control frames needed before torque can be cut
class CarController(CarControllerBase, IQStopAndGoController):
def __init__(self, dbc_names, CP, CP_IQ):
CarControllerBase.__init__(self, dbc_names, CP, CP_IQ)
IQStopAndGoController.__init__(self, CP, CP_IQ)
self.apply_torque_last = 0
self.cruise_button_prev = 0
self.steer_rate_counter = 0
self.p = CarControllerParams(CP)
self.packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
def update(self, CC, CC_IQ, CS, now_nanos):
actuators = CC.actuators
hud_control = CC.hudControl
pcm_cancel_cmd = CC.cruiseControl.cancel
can_sends = []
# *** steering ***
if (self.frame % self.p.STEER_STEP) == 0:
apply_torque = int(round(actuators.torque * self.p.STEER_MAX))
# limits due to driver torque
new_torque = int(round(apply_torque))
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.p)
if not CC.latActive:
apply_torque = 0
if self.CP.flags & SubaruFlags.PREGLOBAL:
can_sends.append(subarucan.create_preglobal_steering_control(self.packer, self.frame // self.p.STEER_STEP, apply_torque, CC.latActive))
else:
apply_steer_req = CC.latActive
if self.CP.flags & SubaruFlags.STEER_RATE_LIMITED:
# Steering rate fault prevention
self.steer_rate_counter, apply_steer_req = \
common_fault_avoidance(abs(CS.out.steeringRateDeg) > MAX_STEER_RATE, apply_steer_req,
self.steer_rate_counter, MAX_STEER_RATE_FRAMES)
can_sends.append(subarucan.create_steering_control(self.packer, apply_torque, apply_steer_req))
self.apply_torque_last = apply_torque
# *** longitudinal ***
if CC.longActive:
apply_throttle = int(round(np.interp(actuators.accel, CarControllerParams.THROTTLE_LOOKUP_BP, CarControllerParams.THROTTLE_LOOKUP_V)))
apply_rpm = int(round(np.interp(actuators.accel, CarControllerParams.RPM_LOOKUP_BP, CarControllerParams.RPM_LOOKUP_V)))
apply_brake = int(round(np.interp(actuators.accel, CarControllerParams.BRAKE_LOOKUP_BP, CarControllerParams.BRAKE_LOOKUP_V)))
# limit min and max values
cruise_throttle = np.clip(apply_throttle, CarControllerParams.THROTTLE_MIN, CarControllerParams.THROTTLE_MAX)
cruise_rpm = np.clip(apply_rpm, CarControllerParams.RPM_MIN, CarControllerParams.RPM_MAX)
cruise_brake = np.clip(apply_brake, CarControllerParams.BRAKE_MIN, CarControllerParams.BRAKE_MAX)
else:
cruise_throttle = CarControllerParams.THROTTLE_INACTIVE
cruise_rpm = CarControllerParams.RPM_MIN
cruise_brake = CarControllerParams.BRAKE_MIN
# *** alerts and pcm cancel ***
if self.CP.flags & SubaruFlags.PREGLOBAL:
if self.frame % 5 == 0:
# 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep
# disengage ACC when OP is disengaged
if pcm_cancel_cmd:
cruise_button = 1
# turn main on if off and past start-up state
elif not CS.out.cruiseState.available and CS.ready:
cruise_button = 1
else:
cruise_button = CS.cruise_button
# unstick previous mocked button press
if cruise_button == 1 and self.cruise_button_prev == 1:
cruise_button = 0
self.cruise_button_prev = cruise_button
can_sends.append(subarucan.create_preglobal_es_distance(self.packer, cruise_button, CS.es_distance_msg))
else:
if self.frame % 10 == 0:
can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled,
self.CP.openpilotLongitudinalControl, CC.longActive, hud_control.leadVisible))
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled, hud_control.visualAlert,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
hud_control.leftLaneDepart, hud_control.rightLaneDepart))
if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT:
can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg, hud_control.visualAlert))
if self.CP.openpilotLongitudinalControl:
if self.frame % 5 == 0:
can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg,
self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm))
can_sends.append(subarucan.create_es_brake(self.packer, self.frame // 5, CS.es_brake_msg,
self.CP.openpilotLongitudinalControl, CC.longActive, cruise_brake))
can_sends.append(subarucan.create_es_distance(self.packer, self.frame // 5, CS.es_distance_msg, 0, pcm_cancel_cmd,
self.CP.openpilotLongitudinalControl, cruise_brake > 0, cruise_throttle))
else:
if pcm_cancel_cmd:
if not (self.CP.flags & SubaruFlags.HYBRID):
bus = CanBus.alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else CanBus.main
can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg["COUNTER"] + 1, CS.es_distance_msg, bus, pcm_cancel_cmd))
if self.CP.flags & SubaruFlags.DISABLE_EYESIGHT:
# Tester present (keeps eyesight disabled)
if self.frame % 100 == 0:
can_sends.append(make_tester_present_msg(GLOBAL_ES_ADDR, CanBus.camera, suppress_response=True))
# Create all of the other eyesight messages to keep the rest of the car happy when eyesight is disabled
if self.frame % 5 == 0:
can_sends.append(subarucan.create_es_highbeamassist(self.packer))
if self.frame % 10 == 0:
can_sends.append(subarucan.create_es_static_1(self.packer))
if self.frame % 2 == 0:
can_sends.append(subarucan.create_es_static_2(self.packer))
can_sends.extend(IQStopAndGoController.create_creep_assist(self, self.packer, CC, CS, self.frame))
new_actuators = actuators.as_builder()
new_actuators.torque = self.apply_torque_last / self.p.STEER_MAX
new_actuators.torqueOutputCan = self.apply_torque_last
self.frame += 1
return new_actuators, can_sends

View File

@@ -0,0 +1,157 @@
import copy
from iqdbc.can import CANDefine, CANParser
from iqdbc.car import Bus, structs
from iqdbc.car.common.conversions import Conversions as CV
from iqdbc.car.interfaces import CarStateBase
from iqdbc.car.subaru.values import DBC, CanBus, SubaruFlags
from iqdbc.car import CanSignalRateCalculator
from iqdbc.lvbs.car.subaru.aol import AolCarState
from iqdbc.lvbs.car.subaru.creep_assist import IQStopAndGoState
class CarState(CarStateBase, AolCarState, IQStopAndGoState):
def __init__(self, CP, CP_IQ):
CarStateBase.__init__(self, CP, CP_IQ)
AolCarState.__init__(self, CP, CP_IQ)
IQStopAndGoState.__init__(self, CP, CP_IQ)
can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt])
self.shifter_values = can_define.dv["Transmission"]["Gear"]
self.angle_rate_calulator = CanSignalRateCalculator(50)
def update(self, can_parsers) -> tuple[structs.CarState, structs.IQCarState]:
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
cp_alt = can_parsers[Bus.alt]
ret = structs.CarState()
ret_iq = structs.IQCarState()
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_alt.vl["Throttle_Hybrid"]
ret.gasPressed = throttle_msg["Throttle_Pedal"] > 1e-5
if self.CP.flags & SubaruFlags.PREGLOBAL:
ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 0
else:
cp_brakes = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1
cp_es_distance = cp_alt if self.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID) else cp_cam
if not (self.CP.flags & SubaruFlags.HYBRID):
eyesight_fault = bool(cp_es_distance.vl["ES_Distance"]["Cruise_Fault"])
# if openpilot is controlling long, an eyesight fault is a non-critical fault. otherwise it's an ACC fault
if self.CP.openpilotLongitudinalControl:
ret.carFaultedNonCritical = eyesight_fault
else:
ret.accFaulted = eyesight_fault
cp_wheels = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
self.parse_wheel_speeds(ret,
cp_wheels.vl["Wheel_Speeds"]["FL"],
cp_wheels.vl["Wheel_Speeds"]["FR"],
cp_wheels.vl["Wheel_Speeds"]["RL"],
cp_wheels.vl["Wheel_Speeds"]["RR"],
)
ret.standstill = ret.vEgoRaw == 0
# continuous blinker signals for assisted lane change
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["Dashlights"]["LEFT_BLINKER"],
cp.vl["Dashlights"]["RIGHT_BLINKER"])
if self.CP.enableBsm:
ret.leftBlindspot = (cp.vl["BSD_RCTA"]["L_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["L_APPROACHING"] == 1)
ret.rightBlindspot = (cp.vl["BSD_RCTA"]["R_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["R_APPROACHING"] == 1)
cp_transmission = cp_alt if self.CP.flags & SubaruFlags.HYBRID else cp
can_gear = int(cp_transmission.vl["Transmission"]["Gear"])
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None))
if not (self.CP.flags & SubaruFlags.LKAS_ANGLE):
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]
steering_updated = len(cp.vl_all["Steering_Torque"]["Steering_Angle"]) > 0
else:
# Steering_Torque->Steering_Angle exists on SUBARU_FORESTER_2022, SUBARU_OUTBACK_2023, SUBARU_ASCENT_2023 where
# it is identical to Steering_2's signal. However, it is always zero on newer LKAS_ANGLE cars
# such as 2024+ Crosstrek, 2023+ Ascent, etc. Use a universal signal for LKAS_ANGLE cars.
ret.steeringAngleDeg = cp.vl["Steering_2"]["Steering_Angle"]
steering_updated = len(cp.vl_all["Steering_2"]["Steering_Angle"]) > 0
if not (self.CP.flags & SubaruFlags.PREGLOBAL):
# ideally we get this from the car, but unclear if it exists. diagnostic software doesn't even have it
ret.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, steering_updated)
ret.steeringTorque = cp.vl["Steering_Torque"]["Steer_Torque_Sensor"]
ret.steeringTorqueEps = cp.vl["Steering_Torque"]["Steer_Torque_Output"]
steer_threshold = 75 if self.CP.flags & SubaruFlags.PREGLOBAL else 80
ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold
cp_cruise = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
cp_es_brake = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp_cam
if self.CP.flags & (SubaruFlags.HYBRID | SubaruFlags.LKAS_ANGLE):
# ES_DashStatus->Cruise_Activated_Dash is likely intended for the dash display only, as it falls
# during user gas override and at standstill. ES_Status is missing on hybrid, so we use ES_Brake instead
# TODO: ES_Brake->Cruise_Activated has been seen staying high when Crosstrek 2025 angle LKAS user pressed
# brake while engaged at a stop. ES_Status and ES_DashStatus->Signal7 correctly fell, but is either missing or
# always zero on hybrids. Probably need to split angle & hybrid. 0x27 and 0x225 on hybrids may work for them.
ret.cruiseState.enabled = cp_es_brake.vl["ES_Brake"]['Cruise_Activated'] != 0
ret.cruiseState.available = cp_cam.vl["ES_DashStatus"]['Cruise_On'] != 0
else:
ret.cruiseState.enabled = cp_cruise.vl["CruiseControl"]["Cruise_Activated"] != 0
ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0
ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS
if (self.CP.flags & SubaruFlags.PREGLOBAL and cp.vl["Dash_State2"]["UNITS"] == 1) or \
(not (self.CP.flags & SubaruFlags.PREGLOBAL) and cp.vl["Dashlights"]["UNITS"] == 1):
ret.cruiseState.speed *= CV.MPH_TO_KPH
ret.seatbeltUnlatched = cp.vl["Dashlights"]["SEATBELT_FL"] == 1
ret.doorOpen = any([cp.vl["BodyInfo"]["DOOR_OPEN_RR"],
cp.vl["BodyInfo"]["DOOR_OPEN_RL"],
cp.vl["BodyInfo"]["DOOR_OPEN_FR"],
cp.vl["BodyInfo"]["DOOR_OPEN_FL"]])
ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1
if self.CP.flags & SubaruFlags.PREGLOBAL:
self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"]
self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"]
else:
ret.steerFaultTemporary = cp.vl["Steering_Torque"]["Steer_Warning"] == 1
ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1
ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3
ret.stockFcw = (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 1) or \
(cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2)
self.es_lkas_state_msg = copy.copy(cp_cam.vl["ES_LKAS_State"])
self.es_brake_msg = copy.copy(cp_es_brake.vl["ES_Brake"])
# TODO: Hybrid cars don't have ES_Distance, need a replacement
if not (self.CP.flags & SubaruFlags.HYBRID):
# 8 is known AEB, there are a few other values related to AEB we ignore
ret.stockAeb = (cp_es_distance.vl["ES_Brake"]["AEB_Status"] == 8) and \
(cp_es_distance.vl["ES_Brake"]["Brake_Pressure"] != 0)
self.es_status_msg = copy.copy(cp_es_brake.vl["ES_Status"])
self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"])
if not (self.CP.flags & SubaruFlags.HYBRID):
self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"])
if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT:
self.es_infotainment_msg = copy.copy(cp_cam.vl["ES_Infotainment"])
AolCarState.update_aol(self, ret, can_parsers)
IQStopAndGoState.update(self, ret, can_parsers)
return ret, ret_iq
@staticmethod
def get_can_parsers(CP, CP_IQ):
return {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.main),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.camera),
Bus.alt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.alt)
}

View File

@@ -0,0 +1,586 @@
""" AUTO-FORMATTED USING iqdbc/car/debug/format_fingerprints.py, EDIT STRUCTURE THERE."""
from iqdbc.car.structs import CarParams
from iqdbc.car.subaru.values import CAR
Ecu = CarParams.Ecu
FW_VERSIONS = {
CAR.SUBARU_ASCENT: {
(Ecu.abs, 0x7b0, None): [
b'\xa5 \x19\x02\x00',
b'\xa5 !\x02\x00',
],
(Ecu.eps, 0x746, None): [
b'\x05\xc0\xd0\x00',
b'\x85\xc0\xd0\x00',
b'\x95\xc0\xd0\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00d\xb9\x00\x00\x00\x00',
b'\x00\x00d\xb9\x1f@ \x10',
b'\x00\x00e@\x00\x00\x00\x00',
b'\x00\x00e@\x1f@ $',
b"\x00\x00e~\x1f@ '",
],
(Ecu.engine, 0x7e0, None): [
b'\xbb,\xa0t\x07',
b'\xd1,\xa0q\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\x00>\xf0\x00\x00',
b'\x00\xfe\xf7\x00\x00',
b'\x01\xfe\xf7\x00\x00',
b'\x01\xfe\xf9\x00\x00',
b'\x01\xfe\xfa\x00\x00',
],
},
CAR.SUBARU_ASCENT_2023: {
(Ecu.abs, 0x7b0, None): [
b'\xa5 #\x03\x00',
],
(Ecu.eps, 0x746, None): [
b'%\xc0\xd0\x11',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x05!\x08\x1dK\x05!\x08\x01/',
],
(Ecu.engine, 0x7a2, None): [
b'\xe5,\xa0P\x07',
],
(Ecu.transmission, 0x7a3, None): [
b'\x04\xfe\xf3\x00\x00',
],
},
CAR.SUBARU_LEGACY: {
(Ecu.abs, 0x7b0, None): [
b'\xa1 \x02\x01',
b'\xa1 \x02\x02',
b'\xa1 \x03\x02',
b'\xa1 \x03\x03',
b'\xa1 \x04\x01',
b'\xa1 \x04\x02',
],
(Ecu.eps, 0x746, None): [
b'\x9b\xc0\x11\x00',
b'\x9b\xc0\x11\x02',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00e\x80\x00\x1f@ \x19\x00',
b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'\xde"a0\x07',
b'\xde,\xa0@\x07',
b'\xe2"a0\x07',
b'\xe2"ap\x07',
b'\xe2"aq\x07',
b'\xe2,\xa0@\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xa5\xf6\x05@\x00',
b'\xa5\xfe\xc7@\x00',
b'\xa7\xf6\x04@\x00',
b'\xa7\xfe\xc4@\x00',
],
},
CAR.SUBARU_IMPREZA: {
(Ecu.abs, 0x7b0, None): [
b'z\x84\x19\x90\x00',
b'z\x94\x08\x90\x00',
b'z\x94\x08\x90\x01',
b'z\x94\x0c\x90\x00',
b'z\x94\x0c\x90\x01',
b'z\x94.\x90\x00',
b'z\x94?\x90\x00',
b'z\x9c\x19\x80\x01',
b'\xa2 \x185\x00',
b'\xa2 \x193\x00',
b'\xa2 \x194\x00',
b'\xa2 \x19`\x00',
b'\xa2 5\x00',
],
(Ecu.eps, 0x746, None): [
b'z\xc0\x00\x00',
b'z\xc0\x04\x00',
b'z\xc0\x08\x00',
b'z\xc0\n\x00',
b'z\xc0\x0c\x00',
b'\x8a\xc0\x00\x00',
b'\x8a\xc0\x10\x00',
b'\x9a\xc0\x08\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00c\xf4\x00\x00\x00\x00',
b'\x00\x00c\xf4\x1f@ \x07',
b'\x00\x00d)\x00\x00\x00\x00',
b'\x00\x00d)\x1f@ \x07',
b'\x00\x00dd\x00\x00\x00\x00',
b'\x00\x00dd\x1f@ \x0e',
b'\x00\x00d\xb5\x1f@ \x0e',
b'\x00\x00d\xdc\x00\x00\x00\x00',
b'\x00\x00d\xdc\x1f@ \x0e',
b'\x00\x00e\x02\x1f@ \x14',
b'\x00\x00e\x1c\x00\x00\x00\x00',
b'\x00\x00e\x1c\x1f@ \x14',
b'\x00\x00e+\x00\x00\x00\x00',
b'\x00\x00e+\x1f@ \x14',
b'\x00\x00eq\x1f@ "',
],
(Ecu.engine, 0x7e0, None): [
b'\xaa\x00Bu\x07',
b'\xaa\x01bt\x07',
b'\xaa\x01fr\x07',
b'\xaa!`u\x07',
b'\xaa!au\x07',
b'\xaa!av\x07',
b'\xaa!aw\x07',
b'\xaa!dq\x07',
b'\xaa!ds\x07',
b'\xaa!dt\x07',
b'\xaaafs\x07',
b'\xbe!as\x07',
b'\xbe!at\x07',
b'\xbeacr\x07',
b'\xc5!`r\x07',
b'\xc5!`s\x07',
b'\xc5!ap\x07',
b'\xc5!ar\x07',
b'\xc5!as\x07',
b'\xc5!au\x07',
b'\xc5!dr\x07',
b'\xc5!ds\x07',
b'\xca\x01b0\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xe3\xd0\x081\x00',
b'\xe3\xd5\x161\x00',
b'\xe3\xd5F1\x00',
b'\xe3\xe5F1\x00',
b'\xe3\xf5\x06\x00\x00',
b'\xe3\xf5\x07\x00\x00',
b'\xe3\xf5C\x00\x00',
b'\xe3\xf5F\x00\x00',
b'\xe3\xf5G\x00\x00',
b'\xe4\xe5\x021\x00',
b'\xe4\xe5\x061\x00',
b'\xe4\xf5\x02\x00\x00',
b'\xe4\xf5\x07\x00\x00',
b'\xe5\xf5\x04\x00\x00',
b'\xe5\xf5$\x00\x00',
b'\xe5\xf5B\x00\x00',
b'\xe6\xd5\x041\x00',
],
},
CAR.SUBARU_IMPREZA_2020: {
(Ecu.abs, 0x7b0, None): [
b'\xa2 \x193\x00',
b'\xa2 \x194\x00',
b'\xa2 `\x00',
b'\xa2 !3\x00',
b'\xa2 !6\x00',
b'\xa2 !`\x00',
b'\xa2 !i\x00',
],
(Ecu.eps, 0x746, None): [
b'\n\xc0\x04\x00',
b'\n\xc0\x04\x01',
b'\x9a\xc0\x00\x00',
b'\x9a\xc0\x04\x00',
b'\x9a\xc0\n\x01',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00eb\x1f@ "',
b'\x00\x00eq\x00\x00\x00\x00',
b'\x00\x00eq\x1f@ "',
b'\x00\x00e\x8f\x00\x00\x00\x00',
b'\x00\x00e\x8f\x1f@ )',
b'\x00\x00e\x92\x00\x00\x00\x00',
b'\x00\x00e\xa4\x00\x00\x00\x00',
b'\x00\x00e\xa4\x1f@ (',
],
(Ecu.engine, 0x7e0, None): [
b'\xca!`0\x07',
b'\xca!`p\x07',
b'\xca!`t\x07',
b'\xca!a0\x07',
b'\xca!ap\x07',
b'\xca!f@\x07',
b'\xca!fp\x07',
b'\xcaacp\x07',
b'\xcc!`p\x07',
b'\xcc!fp\x07',
b'\xcc"f0\x07',
b'\xe6!`@\x07',
b'\xe6!fp\x07',
b'\xe6"f0\x07',
b'\xe6"fp\x07',
b'\xf3"f@\x07',
b'\xf3"fp\x07',
b'\xf3"fr\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xe6\x15\x042\x00',
b'\xe6\xf5\x04\x00\x00',
b'\xe6\xf5$\x00\x00',
b'\xe6\xf5D0\x00',
b'\xe7\xf5\x04\x00\x00',
b'\xe7\xf5D0\x00',
b'\xe7\xf6B0\x00',
b'\xe9\xf5"\x00\x00',
b'\xe9\xf5B0\x00',
b'\xe9\xf6B0\x00',
b'\xe9\xf6F0\x00',
],
},
CAR.SUBARU_CROSSTREK_HYBRID: {
(Ecu.abs, 0x7b0, None): [
b'\xa2 \x19e\x01',
b'\xa2 !e\x01',
],
(Ecu.eps, 0x746, None): [
b'\n\xc2\x01\x00',
b'\x9a\xc2\x01\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00el\x1f@ #',
],
(Ecu.engine, 0x7e0, None): [
b'\xd7!`@\x07',
b'\xd7!`p\x07',
b'\xf4!`0\x07',
],
},
CAR.SUBARU_FORESTER: {
(Ecu.abs, 0x7b0, None): [
b'\xa3 \x18\x14\x00',
b'\xa3 \x18&\x00',
b'\xa3 \x19\x14\x00',
b'\xa3 \x19&\x00',
b'\xa3 \x19h\x00',
b'\xa3 \x14\x00',
b'\xa3 \x14\x01',
],
(Ecu.eps, 0x746, None): [
b'\x8d\xc0\x00\x00',
b'\x8d\xc0\x04\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00e!\x00\x00\x00\x00',
b'\x00\x00e!\x1f@ \x11',
b'\x00\x00e^\x00\x00\x00\x00',
b'\x00\x00e^\x1f@ !',
b'\x00\x00e`\x00\x00\x00\x00',
b'\x00\x00e`\x1f@ ',
b'\x00\x00e\x97\x00\x00\x00\x00',
b'\x00\x00e\x97\x1f@ 0',
],
(Ecu.engine, 0x7e0, None): [
b'\xb6"`A\x07',
b'\xb6\xa2`A\x07',
b'\xcb"`@\x07',
b'\xcb"`p\x07',
b'\xcf"`0\x07',
b'\xcf"`p\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\x1a\xe6B1\x00',
b'\x1a\xe6F1\x00',
b'\x1a\xf6B0\x00',
b'\x1a\xf6B`\x00',
b'\x1a\xf6F`\x00',
b'\x1a\xf6b0\x00',
b'\x1a\xf6b`\x00',
b'\x1a\xf6f`\x00',
],
},
CAR.SUBARU_FORESTER_HYBRID: {
(Ecu.abs, 0x7b0, None): [
b'\xa3 \x19T\x00',
],
(Ecu.eps, 0x746, None): [
b'\x8d\xc2\x00\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00eY\x1f@ !',
],
(Ecu.engine, 0x7e0, None): [
b'\xd2\xa1`r\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\x1b\xa7@a\x00',
],
},
CAR.SUBARU_FORESTER_PREGLOBAL: {
(Ecu.abs, 0x7b0, None): [
b'm\x97\x14@',
b'}\x97\x14@',
],
(Ecu.eps, 0x746, None): [
b'm\xc0\x10\x00',
b'}\xc0\x10\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00c\xe9\x00\x00\x00\x00',
b'\x00\x00c\xe9\x1f@ \x03',
b'\x00\x00d5\x1f@ \t',
b'\x00\x00d\xd3\x1f@ \t',
],
(Ecu.engine, 0x7e0, None): [
b'\xa7"@0\x07',
b'\xa7"@p\x07',
b'\xa7)\xa0q\x07',
b'\xba"@@\x07',
b'\xba"@p\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\x1a\xf6F`\x00',
b'\xda\xf2`p\x00',
b'\xda\xf2`\x80\x00',
b'\xda\xfd\xe0\x80\x00',
b'\xdc\xf2@`\x00',
b'\xdc\xf2``\x00',
b'\xdc\xf2`\x80\x00',
b'\xdc\xf2`\x81\x00',
],
},
CAR.SUBARU_LEGACY_PREGLOBAL: {
(Ecu.abs, 0x7b0, None): [
b'[\x97D\x00',
b'[\xba\xc4\x03',
b'k\x97D\x00',
b'k\x9aD\x00',
b'{\x97D\x00',
],
(Ecu.eps, 0x746, None): [
b'K\xb0\x00\x01',
b'[\xb0\x00\x01',
b'k\xb0\x00\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00c\x94\x1f@\x10\x08',
b'\x00\x00c\xb7\x1f@\x10\x16',
b'\x00\x00c\xec\x1f@ \x04',
],
(Ecu.engine, 0x7e0, None): [
b'\xa0"@q\x07',
b'\xa0+@p\x07',
b'\xab*@r\x07',
b'\xab+@p\x07',
b'\xb4"@0\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xbd\xf2\x00`\x00',
b'\xbe\xf2\x00p\x00',
b'\xbe\xfb\xc0p\x00',
b'\xbf\xf2\x00\x80\x00',
b'\xbf\xfb\xc0\x80\x00',
],
},
CAR.SUBARU_OUTBACK_PREGLOBAL: {
(Ecu.abs, 0x7b0, None): [
b'[\xba\xac\x03',
b'[\xf7\xac\x00',
b'[\xf7\xac\x03',
b'[\xf7\xbc\x03',
b'k\x97\xac\x00',
b'k\x9a\xac\x00',
b'{\x97\xac\x00',
b'{\x9a\xac\x00',
],
(Ecu.eps, 0x746, None): [
b'K\xb0\x00\x00',
b'K\xb0\x00\x02',
b'[\xb0\x00\x00',
b'k\xb0\x00\x00',
b'{\xb0\x00\x01',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00c\x90\x1f@\x10\x0e',
b'\x00\x00c\x94\x00\x00\x00\x00',
b'\x00\x00c\x94\x1f@\x10\x08',
b'\x00\x00c\xb7\x1f@\x10\x16',
b'\x00\x00c\xd1\x1f@\x10\x17',
b'\x00\x00c\xec\x1f@ \x04',
],
(Ecu.engine, 0x7e0, None): [
b'\xa0"@\x80\x07',
b'\xa0*@q\x07',
b'\xa0*@u\x07',
b'\xa0+@@\x07',
b'\xa0bAq\x07',
b'\xab"@@\x07',
b'\xab"@s\x07',
b'\xab*@@\x07',
b'\xab+@@\x07',
b'\xb4"@0\x07',
b'\xb4"@p\x07',
b'\xb4"@r\x07',
b'\xb4+@p\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xbd\xf2@`\x00',
b'\xbd\xf2@\x81\x00',
b'\xbd\xfb\xe0\x80\x00',
b'\xbe\xf2@p\x00',
b'\xbe\xf2@\x80\x00',
b'\xbe\xfb\xe0p\x00',
b'\xbf\xe2@\x80\x00',
b'\xbf\xf2@\x80\x00',
b'\xbf\xfb\xe0b\x00',
],
},
CAR.SUBARU_OUTBACK_PREGLOBAL_2018: {
(Ecu.abs, 0x7b0, None): [
b'\x8b\x97\xac\x00',
b'\x8b\x97\xbc\x00',
b'\x8b\x99\xac\x00',
b'\x8b\x9a\xac\x00',
b'\x9b\x97\xac\x00',
b'\x9b\x97\xbe\x10',
b'\x9b\x9a\xac\x00',
],
(Ecu.eps, 0x746, None): [
b'{\xb0\x00\x00',
b'{\xb0\x00\x01',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00df\x1f@ \n',
b'\x00\x00d\x95\x00\x00\x00\x00',
b'\x00\x00d\x95\x1f@ \x0f',
b'\x00\x00d\xfe\x00\x00\x00\x00',
b'\x00\x00d\xfe\x1f@ \x15',
b'\x00\x00e\x19\x1f@ \x15',
],
(Ecu.engine, 0x7e0, None): [
b'\xb5"@P\x07',
b'\xb5"@p\x07',
b'\xb5+@@\x07',
b'\xb5b@1\x07',
b'\xb5q\xe0@\x07',
b'\xc4"@0\x07',
b'\xc4+@0\x07',
b'\xc4b@p\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xbb\xf2@`\x00',
b'\xbb\xfb\xe0`\x00',
b'\xbc\xaf\xe0`\x00',
b'\xbc\xe2@\x80\x00',
b'\xbc\xf2@\x80\x00',
b'\xbc\xf2@\x81\x00',
b'\xbc\xfb\xe0`\x00',
b'\xbc\xfb\xe0\x80\x00',
],
},
CAR.SUBARU_OUTBACK: {
(Ecu.abs, 0x7b0, None): [
b'\xa1 \x06\x00',
b'\xa1 \x06\x01',
b'\xa1 \x06\x02',
b'\xa1 \x06\x03',
b'\xa1 \x07\x00',
b'\xa1 \x07\x01',
b'\xa1 \x07\x02',
b'\xa1 \x07\x03',
b'\xa1 \x08\x00',
b'\xa1 \x08\x01',
b'\xa1 \x08\x02',
b'\xa1 "\t\x00',
b'\xa1 "\t\x01',
],
(Ecu.eps, 0x746, None): [
b'\x1b\xc0\x10\x00',
b'\x9b\xc0\x10\x00',
b'\x9b\xc0\x10\x02',
b'\x9b\xc0 \x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00eJ\x00\x00\x00\x00\x00\x00',
b'\x00\x00eJ\x00\x1f@ \x19\x00',
b'\x00\x00e\x80\x00\x1f@ \x19\x00',
b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00',
b'\x00\x00e\x9a\x00\x1f@ 1\x00',
],
(Ecu.engine, 0x7e0, None): [
b'\xbc"`@\x07',
b'\xbc"`q\x07',
b'\xbc,\xa0q\x07',
b'\xbc,\xa0u\x07',
b'\xde"`0\x07',
b'\xde,\xa0@\x07',
b'\xe2"`0\x07',
b'\xe2"`p\x07',
b'\xe2"`q\x07',
b'\xe2"`t\x07',
b'\xe2,\xa0p\x07',
b'\xe3,\xa0@\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xa5\xf6D@\x00',
b'\xa5\xfe\xf6@\x00',
b'\xa5\xfe\xf7@\x00',
b'\xa5\xfe\xf8@\x00',
b'\xa7\x8e\xf40\x00',
b'\xa7\xf6D@\x00',
b'\xa7\xfe\xf4@\x00',
b'\xa7\xfe\xf6@\x00',
],
},
CAR.SUBARU_FORESTER_2022: {
(Ecu.abs, 0x7b0, None): [
b'\xa3 !v\x00',
b'\xa3 !x\x00',
b'\xa3 "v\x00',
b'\xa3 "x\x00',
],
(Ecu.eps, 0x746, None): [
b'-\xc0\x040',
b'-\xc0%0',
b'=\xc0%\x02',
b'=\xc04\x02',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x04!\x01\x1eD\x07!\x00\x04,',
b'\x04!\x08\x01.\x07!\x08\x022',
b'\r!\x08\x017\x00\x00\x00\x00\x00',
b'\r!\x08\x017\n!\x08\x003',
],
(Ecu.engine, 0x7e0, None): [
b'\xd5"`0\x07',
b'\xd5"a0\x07',
b'\xf1"`q\x07',
b'\xf1"aq\x07',
b'\xfa"`p\x07',
b'\xfa"ap\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\x1d\x86B0\x00',
b'\x1d\xf6B0\x00',
b'\x1e\x86B0\x00',
b'\x1e\x86F0\x00',
b'\x1e\xf6D0\x00',
b'\x1f\xf6B1\x00',
],
},
CAR.SUBARU_OUTBACK_2023: {
(Ecu.abs, 0x7b0, None): [
b'\xa1 #\x14\x00',
b'\xa1 #\x17\x00',
],
(Ecu.eps, 0x746, None): [
b'+\xc0\x10\x11\x00',
b'+\xc0\x12\x11\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\t!\x08\x046\x05!\x08\x01/',
],
(Ecu.engine, 0x7a2, None): [
b'\xed,\xa0q\x07',
b'\xed,\xa2q\x07',
],
(Ecu.transmission, 0x7a3, None): [
b'\xa8\x8e\xf41\x00',
b'\xa8\xfe\xf41\x00',
],
},
}

View File

@@ -0,0 +1,122 @@
from iqdbc.car import get_safety_config, structs, uds
from iqdbc.car.disable_ecu import disable_ecu
from iqdbc.car.interfaces import CarInterfaceBase
from iqdbc.car.subaru.carcontroller import CarController
from iqdbc.car.subaru.carstate import CarState
from iqdbc.car.subaru.values import CAR, GLOBAL_ES_ADDR, SubaruFlags, SubaruSafetyFlags
class CarInterface(CarInterfaceBase):
CarState = CarState
CarController = CarController
@staticmethod
def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
ret.brand = "subaru"
ret.radarUnavailable = True
# for HYBRID CARS to be upstreamed, we need:
# - replacement for ES_Distance so we can cancel the cruise control
# - to find the Cruise_Activated bit from the car
# - proper panda safety setup (use the correct cruise_activated bit, throttle from Throttle_Hybrid, etc)
ret.dashcamOnly = bool(ret.flags & (SubaruFlags.PREGLOBAL | SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
ret.autoResumeSng = False
# Detect infotainment message sent from the camera
if not (ret.flags & SubaruFlags.PREGLOBAL) and 0x323 in fingerprint[2]:
ret.flags |= SubaruFlags.SEND_INFOTAINMENT.value
if ret.flags & SubaruFlags.PREGLOBAL:
ret.enableBsm = 0x25c in fingerprint[0]
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.subaruPreglobal)]
else:
ret.enableBsm = 0x228 in fingerprint[0]
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.subaru)]
if ret.flags & SubaruFlags.GLOBAL_GEN2:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.GEN2.value
ret.steerLimitTimer = 0.4
ret.steerActuatorDelay = 0.1
if not (ret.flags & SubaruFlags.LKAS_ANGLE):
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
if ret.flags & SubaruFlags.LKAS_ANGLE:
ret.steerControlType = structs.CarParams.SteerControlType.angle
elif candidate == CAR.SUBARU_ASCENT:
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00003
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]]
elif candidate == CAR.SUBARU_IMPREZA:
ret.steerActuatorDelay = 0.4 # end-to-end angle controller
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00005
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]]
elif candidate == CAR.SUBARU_IMPREZA_2020:
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00005
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]]
elif candidate == CAR.SUBARU_CROSSTREK_HYBRID:
ret.steerActuatorDelay = 0.1
elif candidate in (CAR.SUBARU_FORESTER, CAR.SUBARU_FORESTER_HYBRID):
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.000038
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]]
elif candidate in (CAR.SUBARU_OUTBACK, CAR.SUBARU_LEGACY):
ret.steerActuatorDelay = 0.1
elif candidate in (CAR.SUBARU_FORESTER_PREGLOBAL, CAR.SUBARU_OUTBACK_PREGLOBAL_2018):
# Outback 2018-2019 and Forester have reversed driver torque signal
ret.safetyConfigs[0].safetyParam = SubaruSafetyFlags.PREGLOBAL_REVERSED_DRIVER_TORQUE.value
elif candidate == CAR.SUBARU_LEGACY_PREGLOBAL:
ret.steerActuatorDelay = 0.15
elif candidate == CAR.SUBARU_OUTBACK_PREGLOBAL:
pass
else:
raise ValueError(f"unknown car: {candidate}")
ret.alphaLongitudinalAvailable = not (ret.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.PREGLOBAL |
SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
ret.openpilotLongitudinalControl = alpha_long and ret.alphaLongitudinalAvailable
if ret.flags & SubaruFlags.GLOBAL_GEN2 and ret.openpilotLongitudinalControl:
ret.flags |= SubaruFlags.DISABLE_EYESIGHT.value
if ret.openpilotLongitudinalControl:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.LONG.value
return ret
@staticmethod
def _get_params_iq(stock_cp: structs.CarParams, ret: structs.IQCarParams, candidate, fingerprint: dict[int, dict[int, int]],
car_fw: list[structs.CarParams.CarFw], alpha_long: bool, is_release_iq: bool, docs: bool) -> structs.IQCarParams:
stock_cp.dashcamOnly = bool(stock_cp.flags & (SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
if not stock_cp.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID):
stock_cp.autoResumeSng = True
return ret
@staticmethod
def init(CP, CP_IQ, can_recv, can_send, communication_control=None):
if CP.flags & SubaruFlags.DISABLE_EYESIGHT:
if communication_control is None:
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
disable_ecu(can_recv, can_send, bus=2, addr=GLOBAL_ES_ADDR, com_cont_req=communication_control)
@staticmethod
def deinit(CP, can_recv, can_send):
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_ENABLE_TX, uds.MESSAGE_TYPE.NORMAL])
CarInterface.init(CP, can_recv, can_send, communication_control)

View File

@@ -0,0 +1,337 @@
from iqdbc.car import structs
from iqdbc.car.subaru.values import CanBus
VisualAlert = structs.CarControl.HUDControl.VisualAlert
def create_steering_control(packer, apply_torque, steer_req):
values = {
"LKAS_Output": apply_torque,
"LKAS_Request": steer_req,
"SET_1": 1
}
return packer.make_can_msg("ES_LKAS", 0, values)
def create_steering_control_angle(packer, apply_torque, steer_req):
values = {
"LKAS_Output": apply_torque,
"LKAS_Request": steer_req,
"SET_3": 3
}
return packer.make_can_msg("ES_LKAS_ANGLE", 0, values)
def create_steering_status(packer):
return packer.make_can_msg("ES_LKAS_State", 0, {})
def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long_enabled = False, brake_cmd = False, cruise_throttle = 0):
values = {s: es_distance_msg[s] for s in [
"CHECKSUM",
"Signal1",
"Cruise_Fault",
"Cruise_Throttle",
"Signal2",
"Car_Follow",
"Low_Speed_Follow",
"Cruise_Soft_Disable",
"Signal7",
"Cruise_Brake_Active",
"Distance_Swap",
"Cruise_EPB",
"Signal4",
"Close_Distance",
"Signal5",
"Cruise_Cancel",
"Cruise_Set",
"Cruise_Resume",
"Signal6",
]}
values["COUNTER"] = frame % 0x10
if long_enabled:
values["Cruise_Throttle"] = cruise_throttle
# Do not disable openpilot on Eyesight Soft Disable, if openpilot is controlling long
values["Cruise_Soft_Disable"] = 0
values["Cruise_Fault"] = 0
values["Cruise_Brake_Active"] = brake_cmd
if pcm_cancel_cmd:
values["Cruise_Cancel"] = 1
values["Cruise_Throttle"] = 1818 # inactive throttle
return packer.make_can_msg("ES_Distance", bus, values)
def create_es_lkas_state(packer, frame, es_lkas_state_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart):
values = {s: es_lkas_state_msg[s] for s in [
"CHECKSUM",
"LKAS_Alert_Msg",
"Signal1",
"LKAS_ACTIVE",
"LKAS_Dash_State",
"Signal2",
"Backward_Speed_Limit_Menu",
"LKAS_Left_Line_Enable",
"LKAS_Left_Line_Light_Blink",
"LKAS_Right_Line_Enable",
"LKAS_Right_Line_Light_Blink",
"LKAS_Left_Line_Visible",
"LKAS_Right_Line_Visible",
"LKAS_Alert",
"Signal3",
]}
values["COUNTER"] = frame % 0x10
# Filter the stock LKAS "Keep hands on wheel" alert
if values["LKAS_Alert_Msg"] == 1:
values["LKAS_Alert_Msg"] = 0
# Filter the stock LKAS sending an audible alert when it turns off LKAS
if values["LKAS_Alert"] == 27:
values["LKAS_Alert"] = 0
# Filter the stock LKAS sending an audible alert when "Keep hands on wheel" alert is active (2020+ models)
if values["LKAS_Alert"] == 28 and values["LKAS_Alert_Msg"] == 7:
values["LKAS_Alert"] = 0
# Filter the stock LKAS sending an audible alert when "Keep hands on wheel OFF" alert is active (2020+ models)
if values["LKAS_Alert"] == 30:
values["LKAS_Alert"] = 0
# Filter the stock LKAS sending "Keep hands on wheel OFF" alert (2020+ models)
if values["LKAS_Alert_Msg"] == 7:
values["LKAS_Alert_Msg"] = 0
# Show Keep hands on wheel alert for openpilot steerRequired alert
if visual_alert == VisualAlert.steerRequired:
values["LKAS_Alert_Msg"] = 1
# Ensure we don't overwrite potentially more important alerts from stock (e.g. FCW)
if visual_alert == VisualAlert.ldw and values["LKAS_Alert"] == 0:
if left_lane_depart:
values["LKAS_Alert"] = 12 # Left lane departure dash alert
elif right_lane_depart:
values["LKAS_Alert"] = 11 # Right lane departure dash alert
if enabled:
values["LKAS_ACTIVE"] = 1 # Show LKAS lane lines
values["LKAS_Dash_State"] = 2 # Green enabled indicator
else:
values["LKAS_Dash_State"] = 0 # LKAS Not enabled
values["LKAS_Left_Line_Visible"] = int(left_line)
values["LKAS_Right_Line_Visible"] = int(right_line)
return packer.make_can_msg("ES_LKAS_State", CanBus.main, values)
def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, long_active, lead_visible):
values = {s: dashstatus_msg[s] for s in [
"CHECKSUM",
"PCB_Off",
"LDW_Off",
"Signal1",
"Cruise_State_Msg",
"LKAS_State_Msg",
"Signal2",
"Cruise_Soft_Disable",
"Cruise_Status_Msg",
"Signal3",
"Cruise_Distance",
"Signal4",
"Conventional_Cruise",
"Signal5",
"Cruise_Disengaged_Dash",
"Cruise_Activated_Dash",
"Signal6",
"Cruise_Set_Speed",
"Cruise_Fault",
"Cruise_On",
"Display_Own_Car",
"Brake_Lights",
"Car_Follow",
"Signal7",
"Far_Distance",
"Cruise_State",
]}
values["COUNTER"] = frame % 0x10
if long_enabled:
values["Cruise_State"] = 0
# TODO: Cruise_Activated_dash should respect gas pressed and standstill stock behavior
values["Cruise_Activated_Dash"] = enabled
values["Cruise_Disengaged_Dash"] = 0
values["Car_Follow"] = int(lead_visible)
values["PCB_Off"] = 1 # AEB is not presevered, so show the PCB_Off on dash
values["LDW_Off"] = 0
values["Cruise_Fault"] = 0
# Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts
if values["LKAS_State_Msg"] in (2, 3):
values["LKAS_State_Msg"] = 0
return packer.make_can_msg("ES_DashStatus", CanBus.main, values)
def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value):
values = {s: es_brake_msg[s] for s in [
"CHECKSUM",
"Signal1",
"Brake_Pressure",
"AEB_Status",
"Cruise_Brake_Lights",
"Cruise_Brake_Fault",
"Cruise_Brake_Active",
"Cruise_Activated",
"Signal3",
]}
values["COUNTER"] = frame % 0x10
if long_enabled:
values["Cruise_Brake_Fault"] = 0
values["Cruise_Activated"] = long_active
values["Brake_Pressure"] = brake_value
values["Cruise_Brake_Active"] = brake_value > 0
values["Cruise_Brake_Lights"] = brake_value >= 70
return packer.make_can_msg("ES_Brake", CanBus.main, values)
def create_es_status(packer, frame, es_status_msg, long_enabled, long_active, cruise_rpm):
values = {s: es_status_msg[s] for s in [
"CHECKSUM",
"Signal1",
"Cruise_Fault",
"Cruise_RPM",
"Cruise_Activated",
"Brake_Lights",
"Cruise_Hold",
"Signal3",
]}
values["COUNTER"] = frame % 0x10
if long_enabled:
values["Cruise_RPM"] = cruise_rpm
values["Cruise_Fault"] = 0
values["Cruise_Activated"] = long_active
return packer.make_can_msg("ES_Status", CanBus.main, values)
def create_es_infotainment(packer, frame, es_infotainment_msg, visual_alert):
# Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts
values = {s: es_infotainment_msg[s] for s in [
"CHECKSUM",
"LKAS_State_Infotainment",
"LKAS_Blue_Lines",
"Signal1",
"Signal2",
]}
values["COUNTER"] = frame % 0x10
if values["LKAS_State_Infotainment"] in (3, 4):
values["LKAS_State_Infotainment"] = 0
# Show Keep hands on wheel alert for openpilot steerRequired alert
if visual_alert == VisualAlert.steerRequired:
values["LKAS_State_Infotainment"] = 3
# Show Obstacle Detected for fcw
if visual_alert == VisualAlert.fcw:
values["LKAS_State_Infotainment"] = 2
return packer.make_can_msg("ES_Infotainment", CanBus.main, values)
def create_es_highbeamassist(packer):
values = {
"HBA_Available": False,
}
return packer.make_can_msg("ES_HighBeamAssist", CanBus.main, values)
def create_es_static_1(packer):
values = {
"SET_3": 3,
}
return packer.make_can_msg("ES_STATIC_1", CanBus.main, values)
def create_es_static_2(packer):
values = {
"SET_3": 3,
}
return packer.make_can_msg("ES_STATIC_2", CanBus.main, values)
# *** Subaru Pre-global ***
def subaru_preglobal_checksum(packer, values, addr, checksum_byte=7):
dat = packer.make_can_msg(addr, 0, values)[1]
return (sum(dat[:checksum_byte]) + sum(dat[checksum_byte+1:])) % 256
def create_preglobal_steering_control(packer, frame, apply_torque, steer_req):
values = {
"COUNTER": frame % 0x08,
"LKAS_Command": apply_torque,
"LKAS_Active": steer_req,
}
values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_LKAS")
return packer.make_can_msg("ES_LKAS", CanBus.main, values)
def create_preglobal_es_distance(packer, cruise_button, es_distance_msg):
values = {s: es_distance_msg[s] for s in [
"Cruise_Throttle",
"Signal1",
"Car_Follow",
"Signal2",
"Cruise_Brake_Active",
"Distance_Swap",
"Standstill",
"Signal3",
"Close_Distance",
"Signal4",
"Standstill_2",
"Cruise_Fault",
"Signal5",
"COUNTER",
"Signal6",
"Cruise_Button",
"Signal7",
]}
values["Cruise_Button"] = cruise_button
values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_Distance")
return packer.make_can_msg("ES_Distance", CanBus.main, values)
def subaru_checksum(address: int, sig, d: bytearray) -> int:
s = 0
addr = address
while addr:
s += addr & 0xFF
addr >>= 8
for i in range(1, len(d)):
s += d[i]
return s & 0xFF

View File

@@ -0,0 +1,10 @@
from iqdbc.car.subaru.fingerprints import FW_VERSIONS
class TestSubaruFingerprint:
def test_fw_version_format(self):
for platform, fws_per_ecu in FW_VERSIONS.items():
for (ecu, _, _), fws in fws_per_ecu.items():
fw_size = len(fws[0])
for fw in fws:
assert len(fw) == fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}"

View File

@@ -0,0 +1,279 @@
from dataclasses import dataclass, field
from enum import Enum, IntFlag
from iqdbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
from iqdbc.car.structs import CarParams
from iqdbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column
from iqdbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
Ecu = CarParams.Ecu
class CarControllerParams:
def __init__(self, CP):
self.STEER_STEP = 2 # how often we update the steer cmd
self.STEER_DELTA_UP = 50 # torque increase per refresh, 0.8s to max
self.STEER_DELTA_DOWN = 70 # torque decrease per refresh
self.STEER_DRIVER_ALLOWANCE = 60 # allowed driver torque before start limiting
self.STEER_DRIVER_MULTIPLIER = 50 # weight driver torque heavily
self.STEER_DRIVER_FACTOR = 1 # from dbc
if CP.flags & SubaruFlags.GLOBAL_GEN2:
self.STEER_MAX = 1500
self.STEER_DELTA_UP = 35
self.STEER_DELTA_DOWN = 50
elif CP.carFingerprint == CAR.SUBARU_IMPREZA_2020:
self.STEER_DELTA_UP = 35
self.STEER_MAX = 1439
else:
self.STEER_MAX = 2047
THROTTLE_MIN = 808
THROTTLE_MAX = 3400
THROTTLE_INACTIVE = 1818 # corresponds to zero acceleration
THROTTLE_ENGINE_BRAKE = 808 # while braking, eyesight sets throttle to this, probably for engine braking
BRAKE_MIN = 0
BRAKE_MAX = 600 # about -3.5m/s2 from testing
RPM_MIN = 0
RPM_MAX = 3600
RPM_INACTIVE = 600 # a good base rpm for zero acceleration
THROTTLE_LOOKUP_BP = [0, 2]
THROTTLE_LOOKUP_V = [THROTTLE_INACTIVE, THROTTLE_MAX]
RPM_LOOKUP_BP = [0, 2]
RPM_LOOKUP_V = [RPM_INACTIVE, RPM_MAX]
BRAKE_LOOKUP_BP = [-3.5, 0]
BRAKE_LOOKUP_V = [BRAKE_MAX, BRAKE_MIN]
class SubaruSafetyFlags(IntFlag):
GEN2 = 1
LONG = 2
PREGLOBAL_REVERSED_DRIVER_TORQUE = 4
class SubaruFlags(IntFlag):
# Detected flags
SEND_INFOTAINMENT = 1
DISABLE_EYESIGHT = 2
# Static flags
GLOBAL_GEN2 = 4
# Cars that temporarily fault when steering angle rate is greater than some threshold.
# Appears to be all torque-based cars produced around 2019 - present
STEER_RATE_LIMITED = 8
PREGLOBAL = 16
HYBRID = 32
LKAS_ANGLE = 64
GLOBAL_ES_ADDR = 0x787
GEN2_ES_BUTTONS_DID = b'\x11\x30'
class CanBus:
main = 0
alt = 1
camera = 2
class Footnote(Enum):
GLOBAL = CarFootnote(
"In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.",
Column.PACKAGE)
EXP_LONG = CarFootnote(
"Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.",
Column.LONGITUDINAL)
@dataclass
class SubaruCarDocs(CarDocs):
package: str = "EyeSight Driver Assistance"
car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.subaru_a]))
footnotes: list[Enum] = field(default_factory=lambda: [Footnote.GLOBAL])
def init_make(self, CP: CarParams):
if CP.alphaLongitudinalAvailable:
self.footnotes.append(Footnote.EXP_LONG)
@dataclass
class SubaruPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'subaru_global_2017_generated'})
def init(self):
if self.flags & SubaruFlags.HYBRID:
self.dbc_dict = {Bus.pt: 'subaru_global_2020_hybrid_generated'}
@dataclass
class SubaruGen2PlatformConfig(SubaruPlatformConfig):
def init(self):
super().init()
self.flags |= SubaruFlags.GLOBAL_GEN2
if not (self.flags & SubaruFlags.LKAS_ANGLE):
self.flags |= SubaruFlags.STEER_RATE_LIMITED
class CAR(Platforms):
# Global platform
SUBARU_ASCENT = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Ascent 2019-21", "All")],
CarSpecs(mass=2031, wheelbase=2.89, steerRatio=13.5),
)
SUBARU_OUTBACK = SubaruGen2PlatformConfig(
[SubaruCarDocs("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17),
)
SUBARU_LEGACY = SubaruGen2PlatformConfig(
[SubaruCarDocs("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))],
SUBARU_OUTBACK.specs,
)
SUBARU_IMPREZA = SubaruPlatformConfig(
[
SubaruCarDocs("Subaru Impreza 2017-19"),
SubaruCarDocs("Subaru Crosstrek 2018-19", video="https://youtu.be/Agww7oE1k-s?t=26"),
SubaruCarDocs("Subaru XV 2018-19", video="https://youtu.be/Agww7oE1k-s?t=26"),
],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15),
)
SUBARU_IMPREZA_2020 = SubaruPlatformConfig(
[
SubaruCarDocs("Subaru Impreza 2020-22"),
SubaruCarDocs("Subaru Crosstrek 2020-23"),
SubaruCarDocs("Subaru XV 2020-21"),
],
CarSpecs(mass=1480, wheelbase=2.67, steerRatio=17),
flags=SubaruFlags.STEER_RATE_LIMITED,
)
# TODO: is there an XV and Impreza too?
SUBARU_CROSSTREK_HYBRID = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b]))],
CarSpecs(mass=1668, wheelbase=2.67, steerRatio=17),
flags=SubaruFlags.HYBRID,
)
SUBARU_FORESTER = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Forester 2019-21", "All")],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17),
flags=SubaruFlags.STEER_RATE_LIMITED,
)
SUBARU_FORESTER_HYBRID = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Forester Hybrid 2020")],
SUBARU_FORESTER.specs,
flags=SubaruFlags.HYBRID,
)
# Pre-global
SUBARU_FORESTER_PREGLOBAL = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Forester 2017-18")],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=20),
{Bus.pt: 'subaru_forester_2017_generated'},
flags=SubaruFlags.PREGLOBAL,
)
SUBARU_LEGACY_PREGLOBAL = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Legacy 2015-18")],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=12.5),
{Bus.pt: 'subaru_outback_2015_generated'},
flags=SubaruFlags.PREGLOBAL,
)
SUBARU_OUTBACK_PREGLOBAL = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Outback 2015-17")],
SUBARU_FORESTER_PREGLOBAL.specs,
{Bus.pt: 'subaru_outback_2015_generated'},
flags=SubaruFlags.PREGLOBAL,
)
SUBARU_OUTBACK_PREGLOBAL_2018 = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Outback 2018-19")],
SUBARU_FORESTER_PREGLOBAL.specs,
{Bus.pt: 'subaru_outback_2019_generated'},
flags=SubaruFlags.PREGLOBAL,
)
# Angle LKAS
SUBARU_FORESTER_2022 = SubaruPlatformConfig(
[SubaruCarDocs("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c]))],
SUBARU_FORESTER.specs,
flags=SubaruFlags.LKAS_ANGLE,
)
SUBARU_OUTBACK_2023 = SubaruGen2PlatformConfig(
[SubaruCarDocs("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))],
SUBARU_OUTBACK.specs,
flags=SubaruFlags.LKAS_ANGLE,
)
SUBARU_ASCENT_2023 = SubaruGen2PlatformConfig(
[SubaruCarDocs("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))],
SUBARU_ASCENT.specs,
flags=SubaruFlags.LKAS_ANGLE,
)
SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
# The EyeSight ECU takes 10s to respond to SUBARU_VERSION_REQUEST properly,
# log this alternate manufacturer-specific query
SUBARU_ALT_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
p16(0xf100)
SUBARU_ALT_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
p16(0xf100)
FW_QUERY_CONFIG = FwQueryConfig(
requests=[
Request(
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
logging=True,
),
# Non-OBD requests
# Some Eyesight modules fail on TESTER_PRESENT_REQUEST
# TODO: check if this resolves the fingerprinting issue for the 2023 Ascent and other new Subaru cars
Request(
[SUBARU_VERSION_REQUEST],
[SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdCamera],
bus=0,
),
Request(
[SUBARU_ALT_VERSION_REQUEST],
[SUBARU_ALT_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdCamera],
bus=0,
logging=True,
),
Request(
[StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdCamera],
bus=0,
logging=True,
),
Request(
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
bus=0,
),
# GEN2 powertrain bus query
Request(
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
bus=1,
obd_multiplexing=False,
),
],
# We don't get the EPS from non-OBD queries on GEN2 cars. Note that we still attempt to match when it exists
non_essential_ecus={
Ecu.eps: list(CAR.with_flags(SubaruFlags.GLOBAL_GEN2)),
}
)
DBC = CAR.create_dbc_map()