This commit is contained in:
Your Name
2024-04-27 13:48:05 -05:00
parent 2fbe9dbea1
commit 931db76fc6
432 changed files with 12973 additions and 3300 deletions

View File

@@ -1,9 +1,9 @@
from openpilot.common.numpy_fast import clip, interp
from opendbc.can.packer import CANPacker
from openpilot.selfdrive.car import apply_driver_steer_torque_limits, common_fault_avoidance
from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.selfdrive.car.subaru import subarucan
from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_ES_ADDR, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, STEER_RATE_LIMITED, \
CanBus, CarControllerParams, SubaruFlags
from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags
# 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
@@ -11,7 +11,7 @@ MAX_STEER_RATE = 25 # deg/s
MAX_STEER_RATE_FRAMES = 7 # tx control frames needed before torque can be cut
class CarController:
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP, VM):
self.CP = CP
self.apply_steer_last = 0
@@ -42,12 +42,12 @@ class CarController:
if not CC.latActive:
apply_steer = 0
if self.CP.carFingerprint in PREGLOBAL_CARS:
if self.CP.flags & SubaruFlags.PREGLOBAL:
can_sends.append(subarucan.create_preglobal_steering_control(self.packer, self.frame // self.p.STEER_STEP, apply_steer, CC.latActive))
else:
apply_steer_req = CC.latActive
if self.CP.carFingerprint in STEER_RATE_LIMITED:
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,
@@ -74,7 +74,7 @@ class CarController:
cruise_brake = CarControllerParams.BRAKE_MIN
# *** alerts and pcm cancel ***
if self.CP.carFingerprint in PREGLOBAL_CARS:
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
@@ -117,8 +117,8 @@ class CarController:
self.CP.openpilotLongitudinalControl, cruise_brake > 0, cruise_throttle))
else:
if pcm_cancel_cmd:
if self.CP.carFingerprint not in HYBRID_CARS:
bus = CanBus.alt if self.CP.carFingerprint in GLOBAL_GEN2 else CanBus.main
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:

View File

@@ -4,7 +4,7 @@ from opendbc.can.can_define import CANDefine
from openpilot.common.conversions import Conversions as CV
from openpilot.selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
from openpilot.selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, CanBus, SubaruFlags
from openpilot.selfdrive.car.subaru.values import DBC, CanBus, SubaruFlags
from openpilot.selfdrive.car import CanSignalRateCalculator
@@ -19,17 +19,27 @@ class CarState(CarStateBase):
def update(self, cp, cp_cam, cp_body, frogpilot_variables):
ret = car.CarState.new_message()
throttle_msg = cp.vl["Throttle"] if self.car_fingerprint not in HYBRID_CARS else cp_body.vl["Throttle_Hybrid"]
throttle_msg = cp.vl["Throttle"] if not (self.CP.flags & SubaruFlags.HYBRID) else cp_body.vl["Throttle_Hybrid"]
ret.gas = throttle_msg["Throttle_Pedal"] / 255.
ret.gasPressed = ret.gas > 1e-5
if self.car_fingerprint in PREGLOBAL_CARS:
if self.CP.flags & SubaruFlags.PREGLOBAL:
ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 0
else:
cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
cp_brakes = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1
cp_wheels = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
cp_es_distance = cp_body 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_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
ret.wheelSpeeds = self.get_wheel_speeds(
cp_wheels.vl["Wheel_Speeds"]["FL"],
cp_wheels.vl["Wheel_Speeds"]["FR"],
@@ -48,24 +58,24 @@ class CarState(CarStateBase):
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_body if self.car_fingerprint in HYBRID_CARS else cp
cp_transmission = cp_body 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))
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]
if self.car_fingerprint not in PREGLOBAL_CARS:
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, cp.vl["Steering_Torque"]["COUNTER"])
ret.steeringTorque = cp.vl["Steering_Torque"]["Steer_Torque_Sensor"]
ret.steeringTorqueEps = cp.vl["Steering_Torque"]["Steer_Torque_Output"]
steer_threshold = 75 if self.CP.carFingerprint in PREGLOBAL_CARS else 80
steer_threshold = 75 if self.CP.flags & SubaruFlags.PREGLOBAL else 80
ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold
cp_cruise = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
if self.car_fingerprint in HYBRID_CARS:
cp_cruise = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
if self.CP.flags & SubaruFlags.HYBRID:
ret.cruiseState.enabled = cp_cam.vl["ES_DashStatus"]['Cruise_Activated'] != 0
ret.cruiseState.available = cp_cam.vl["ES_DashStatus"]['Cruise_On'] != 0
else:
@@ -73,8 +83,8 @@ class CarState(CarStateBase):
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.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \
(self.car_fingerprint not in PREGLOBAL_CARS and cp.vl["Dashlights"]["UNITS"] == 1):
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
@@ -84,8 +94,7 @@ class CarState(CarStateBase):
cp.vl["BodyInfo"]["DOOR_OPEN_FL"]])
ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1
cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam
if self.car_fingerprint in PREGLOBAL_CARS:
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:
@@ -96,12 +105,12 @@ class CarState(CarStateBase):
(cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2)
self.es_lkas_state_msg = copy.copy(cp_cam.vl["ES_LKAS_State"])
cp_es_brake = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam
cp_es_brake = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp_cam
self.es_brake_msg = copy.copy(cp_es_brake.vl["ES_Brake"])
cp_es_status = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam
cp_es_status = cp_body if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp_cam
# TODO: Hybrid cars don't have ES_Distance, need a replacement
if self.car_fingerprint not in HYBRID_CARS:
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)
@@ -109,13 +118,16 @@ class CarState(CarStateBase):
self.es_status_msg = copy.copy(cp_es_status.vl["ES_Status"])
self.cruise_control_msg = copy.copy(cp_cruise.vl["CruiseControl"])
if self.car_fingerprint not in HYBRID_CARS:
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"])
self.lkas_previously_enabled = self.lkas_enabled
self.lkas_enabled = cp_cam.vl["ES_LKAS_State"]["LKAS_Dash_State"]
return ret
@staticmethod
@@ -125,7 +137,7 @@ class CarState(CarStateBase):
("Brake_Status", 50),
]
if CP.carFingerprint not in HYBRID_CARS:
if not (CP.flags & SubaruFlags.HYBRID):
messages.append(("CruiseControl", 20))
return messages
@@ -136,7 +148,7 @@ class CarState(CarStateBase):
("ES_Brake", 20),
]
if CP.carFingerprint not in HYBRID_CARS:
if not (CP.flags & SubaruFlags.HYBRID):
messages += [
("ES_Distance", 20),
("ES_Status", 20)
@@ -164,7 +176,7 @@ class CarState(CarStateBase):
("Brake_Pedal", 50),
]
if CP.carFingerprint not in HYBRID_CARS:
if not (CP.flags & SubaruFlags.HYBRID):
messages += [
("Throttle", 100),
("Transmission", 100)
@@ -173,8 +185,8 @@ class CarState(CarStateBase):
if CP.enableBsm:
messages.append(("BSD_RCTA", 17))
if CP.carFingerprint not in PREGLOBAL_CARS:
if CP.carFingerprint not in GLOBAL_GEN2:
if not (CP.flags & SubaruFlags.PREGLOBAL):
if not (CP.flags & SubaruFlags.GLOBAL_GEN2):
messages += CarState.get_common_global_body_messages(CP)
else:
messages += CarState.get_common_preglobal_body_messages()
@@ -183,7 +195,7 @@ class CarState(CarStateBase):
@staticmethod
def get_cam_can_parser(CP):
if CP.carFingerprint in PREGLOBAL_CARS:
if CP.flags & SubaruFlags.PREGLOBAL:
messages = [
("ES_DashStatus", 20),
("ES_Distance", 20),
@@ -194,7 +206,7 @@ class CarState(CarStateBase):
("ES_LKAS_State", 10),
]
if CP.carFingerprint not in GLOBAL_GEN2:
if not (CP.flags & SubaruFlags.GLOBAL_GEN2):
messages += CarState.get_common_global_es_messages(CP)
if CP.flags & SubaruFlags.SEND_INFOTAINMENT:
@@ -206,15 +218,14 @@ class CarState(CarStateBase):
def get_body_can_parser(CP):
messages = []
if CP.carFingerprint in GLOBAL_GEN2:
if CP.flags & SubaruFlags.GLOBAL_GEN2:
messages += CarState.get_common_global_body_messages(CP)
messages += CarState.get_common_global_es_messages(CP)
if CP.carFingerprint in HYBRID_CARS:
if CP.flags & SubaruFlags.HYBRID:
messages += [
("Throttle_Hybrid", 40),
("Transmission", 100)
]
return CANParser(DBC[CP.carFingerprint]["pt"], messages, CanBus.alt)

View File

@@ -26,6 +26,7 @@ FW_VERSIONS = {
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',

View File

@@ -1,15 +1,16 @@
from cereal import car
from cereal import car, custom
from panda import Panda
from openpilot.selfdrive.car import get_safety_config
from openpilot.selfdrive.car import create_button_events, get_safety_config
from openpilot.selfdrive.car.disable_ecu import disable_ecu
from openpilot.selfdrive.car.interfaces import CarInterfaceBase
from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, LKAS_ANGLE, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, SubaruFlags
from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, SubaruFlags
FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type
class CarInterface(CarInterfaceBase):
@staticmethod
def _get_params(ret, params, candidate, fingerprint, car_fw, experimental_long, docs):
def _get_params(ret, params, candidate: CAR, fingerprint, car_fw, disable_openpilot_long, experimental_long, docs):
crosstrek_torque_increase = params.get_bool("CrosstrekTorque")
ret.carName = "subaru"
@@ -18,112 +19,78 @@ class CarInterface(CarInterfaceBase):
# - replacement for ES_Distance so we can cancel the cruise control
# - to find the Cruise_Activated bit from the car
# - proper panda safety setup (use the correct cruise_activated bit, throttle from Throttle_Hybrid, etc)
ret.dashcamOnly = candidate in (LKAS_ANGLE | HYBRID_CARS)
ret.dashcamOnly = bool(ret.flags & (SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
ret.autoResumeSng = False
# Detect infotainment message sent from the camera
if candidate not in PREGLOBAL_CARS and 0x323 in fingerprint[2]:
if not (ret.flags & SubaruFlags.PREGLOBAL) and 0x323 in fingerprint[2]:
ret.flags |= SubaruFlags.SEND_INFOTAINMENT.value
if candidate in PREGLOBAL_CARS:
if ret.flags & SubaruFlags.PREGLOBAL:
ret.enableBsm = 0x25c in fingerprint[0]
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaruPreglobal)]
else:
ret.enableBsm = 0x228 in fingerprint[0]
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)]
if candidate in GLOBAL_GEN2:
if ret.flags & SubaruFlags.GLOBAL_GEN2:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_GEN2
ret.steerLimitTimer = 0.4
ret.steerActuatorDelay = 0.1
if candidate in LKAS_ANGLE:
if ret.flags & SubaruFlags.LKAS_ANGLE:
ret.steerControlType = car.CarParams.SteerControlType.angle
else:
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
if candidate in (CAR.ASCENT, CAR.ASCENT_2023):
ret.mass = 2031.
ret.wheelbase = 2.89
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 13.5
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00003333 if crosstrek_torque_increase else 0.00003
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.133, 0.2], [0.0133, 0.02]] if crosstrek_torque_increase else [[0.0025, 0.1], [0.00025, 0.01]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]]
elif candidate == CAR.IMPREZA:
ret.mass = 1568.
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 15
ret.steerActuatorDelay = 0.4 # end-to-end angle controller
ret.steerActuatorDelay = 0.4 # end-to-end angle controller
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00005
ret.lateralTuning.pid.kf = 0.00003333 if crosstrek_torque_increase else 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]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.133, 0.2], [0.0133, 0.02]] if crosstrek_torque_increase else [[0.2, 0.3], [0.02, 0.03]]
elif candidate == CAR.IMPREZA_2020:
ret.mass = 1480.
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17 # learned, 14 stock
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.CROSSTREK_HYBRID:
ret.mass = 1668.
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17
ret.steerActuatorDelay = 0.1
elif candidate in (CAR.FORESTER, CAR.FORESTER_2022, CAR.FORESTER_HYBRID):
ret.mass = 1568.
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17 # learned, 14 stock
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.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023):
ret.mass = 1568.
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17
ret.steerActuatorDelay = 0.1
elif candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018):
ret.safetyConfigs[0].safetyParam = Panda.FLAG_SUBARU_PREGLOBAL_REVERSED_DRIVER_TORQUE # Outback 2018-2019 and Forester have reversed driver torque signal
ret.mass = 1568
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 20 # learned, 14 stock
elif candidate == CAR.LEGACY_PREGLOBAL:
ret.mass = 1568
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 12.5 # 14.5 stock
ret.steerActuatorDelay = 0.15
elif candidate == CAR.OUTBACK_PREGLOBAL:
ret.mass = 1568
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 20 # learned, 14 stock
pass
else:
raise ValueError(f"unknown car: {candidate}")
ret.experimentalLongitudinalAvailable = candidate not in (GLOBAL_GEN2 | PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS)
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable and not params.get_bool("DisableOpenpilotLongitudinal")
ret.experimentalLongitudinalAvailable = not (ret.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.PREGLOBAL |
SubaruFlags.LKAS_ANGLE | SubaruFlags.HYBRID))
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
if candidate in GLOBAL_GEN2 and ret.openpilotLongitudinalControl:
if ret.flags & SubaruFlags.GLOBAL_GEN2 and ret.openpilotLongitudinalControl:
ret.flags |= SubaruFlags.DISABLE_EYESIGHT.value
if ret.openpilotLongitudinalControl:
@@ -142,7 +109,11 @@ class CarInterface(CarInterfaceBase):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_body, frogpilot_variables)
ret.events = self.create_common_events(ret, frogpilot_variables).to_msg()
ret.buttonEvents = [
*create_button_events(self.CS.lkas_enabled, self.CS.lkas_previously_enabled, {1: FrogPilotButtonType.lkas}),
]
ret.events = self.create_common_events(ret).to_msg()
return ret

View File

@@ -0,0 +1,20 @@
from cereal import car
import unittest
from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS
Ecu = car.CarParams.Ecu
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
class TestSubaruFingerprint(unittest.TestCase):
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:
self.assertEqual(len(fw), fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}")
if __name__ == "__main__":
unittest.main()

View File

@@ -1,12 +1,11 @@
from dataclasses import dataclass, field
from enum import Enum, IntFlag, StrEnum
from typing import Dict, List, Union
from enum import Enum, IntFlag
from cereal import car
from panda.python import uds
from openpilot.common.params import Params
from openpilot.selfdrive.car import dbc_dict
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Tool, Column
from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict
from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Tool, Column
from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
Ecu = car.CarParams.Ecu
@@ -21,7 +20,7 @@ class CarControllerParams:
self.STEER_DRIVER_MULTIPLIER = 50 # weight driver torque heavily
self.STEER_DRIVER_FACTOR = 1 # from dbc
if CP.carFingerprint in GLOBAL_GEN2:
if CP.flags & SubaruFlags.GLOBAL_GEN2:
self.STEER_MAX = 1000
self.STEER_DELTA_UP = 40
self.STEER_DELTA_DOWN = 40
@@ -57,9 +56,20 @@ class CarControllerParams:
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'
@@ -71,27 +81,6 @@ class CanBus:
camera = 2
class CAR(StrEnum):
# Global platform
ASCENT = "SUBARU ASCENT LIMITED 2019"
ASCENT_2023 = "SUBARU ASCENT 2023"
IMPREZA = "SUBARU IMPREZA LIMITED 2019"
IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020"
FORESTER = "SUBARU FORESTER 2019"
OUTBACK = "SUBARU OUTBACK 6TH GEN"
CROSSTREK_HYBRID = "SUBARU CROSSTREK HYBRID 2020"
FORESTER_HYBRID = "SUBARU FORESTER HYBRID 2020"
LEGACY = "SUBARU LEGACY 7TH GEN"
FORESTER_2022 = "SUBARU FORESTER 2022"
OUTBACK_2023 = "SUBARU OUTBACK 7TH GEN"
# Pre-global
FORESTER_PREGLOBAL = "SUBARU FORESTER 2017 - 2018"
LEGACY_PREGLOBAL = "SUBARU LEGACY 2015 - 2018"
OUTBACK_PREGLOBAL = "SUBARU OUTBACK 2015 - 2017"
OUTBACK_PREGLOBAL_2018 = "SUBARU OUTBACK 2018 - 2019"
class Footnote(Enum):
GLOBAL = CarFootnote(
"In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.",
@@ -102,10 +91,10 @@ class Footnote(Enum):
@dataclass
class SubaruCarInfo(CarInfo):
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])
footnotes: list[Enum] = field(default_factory=lambda: [Footnote.GLOBAL])
def init_make(self, CP: car.CarParams):
self.car_parts.parts.extend([Tool.socket_8mm_deep, Tool.pry_tool])
@@ -113,68 +102,180 @@ class SubaruCarInfo(CarInfo):
if CP.experimentalLongitudinalAvailable:
self.footnotes.append(Footnote.EXP_LONG)
CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = {
CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"),
CAR.OUTBACK: SubaruCarInfo("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])),
CAR.LEGACY: SubaruCarInfo("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])),
CAR.IMPREZA: [
SubaruCarInfo("Subaru Impreza 2017-19"),
SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
],
CAR.IMPREZA_2020: [
SubaruCarInfo("Subaru Impreza 2020-22"),
SubaruCarInfo("Subaru Crosstrek 2020-23"),
SubaruCarInfo("Subaru XV 2020-21"),
],
@dataclass
class SubaruPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('subaru_global_2017_generated', None))
def init(self):
if self.flags & SubaruFlags.HYBRID:
self.dbc_dict = dbc_dict('subaru_global_2020_hybrid_generated', None)
@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
ASCENT = SubaruPlatformConfig(
"SUBARU ASCENT LIMITED 2019",
[SubaruCarDocs("Subaru Ascent 2019-21", "All")],
CarSpecs(mass=2031, wheelbase=2.89, steerRatio=13.5),
)
OUTBACK = SubaruGen2PlatformConfig(
"SUBARU OUTBACK 6TH GEN",
[SubaruCarDocs("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17),
)
LEGACY = SubaruGen2PlatformConfig(
"SUBARU LEGACY 7TH GEN",
[SubaruCarDocs("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b]))],
OUTBACK.specs,
)
IMPREZA = SubaruPlatformConfig(
"SUBARU IMPREZA LIMITED 2019",
[
SubaruCarDocs("Subaru Impreza 2017-19"),
SubaruCarDocs("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
SubaruCarDocs("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15),
)
IMPREZA_2020 = SubaruPlatformConfig(
"SUBARU IMPREZA SPORT 2020",
[
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?
CAR.CROSSTREK_HYBRID: SubaruCarInfo("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b])),
CAR.FORESTER_HYBRID: SubaruCarInfo("Subaru Forester Hybrid 2020"),
CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"),
CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"),
CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"),
CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"),
CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"),
CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c])),
CAR.OUTBACK_2023: SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])),
CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])),
}
CROSSTREK_HYBRID = SubaruPlatformConfig(
"SUBARU CROSSTREK HYBRID 2020",
[SubaruCarDocs("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b]))],
CarSpecs(mass=1668, wheelbase=2.67, steerRatio=17),
flags=SubaruFlags.HYBRID,
)
FORESTER = SubaruPlatformConfig(
"SUBARU FORESTER 2019",
[SubaruCarDocs("Subaru Forester 2019-21", "All")],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17),
flags=SubaruFlags.STEER_RATE_LIMITED,
)
FORESTER_HYBRID = SubaruPlatformConfig(
"SUBARU FORESTER HYBRID 2020",
[SubaruCarDocs("Subaru Forester Hybrid 2020")],
FORESTER.specs,
flags=SubaruFlags.HYBRID,
)
# Pre-global
FORESTER_PREGLOBAL = SubaruPlatformConfig(
"SUBARU FORESTER 2017 - 2018",
[SubaruCarDocs("Subaru Forester 2017-18")],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=20),
dbc_dict('subaru_forester_2017_generated', None),
flags=SubaruFlags.PREGLOBAL,
)
LEGACY_PREGLOBAL = SubaruPlatformConfig(
"SUBARU LEGACY 2015 - 2018",
[SubaruCarDocs("Subaru Legacy 2015-18")],
CarSpecs(mass=1568, wheelbase=2.67, steerRatio=12.5),
dbc_dict('subaru_outback_2015_generated', None),
flags=SubaruFlags.PREGLOBAL,
)
OUTBACK_PREGLOBAL = SubaruPlatformConfig(
"SUBARU OUTBACK 2015 - 2017",
[SubaruCarDocs("Subaru Outback 2015-17")],
FORESTER_PREGLOBAL.specs,
dbc_dict('subaru_outback_2015_generated', None),
flags=SubaruFlags.PREGLOBAL,
)
OUTBACK_PREGLOBAL_2018 = SubaruPlatformConfig(
"SUBARU OUTBACK 2018 - 2019",
[SubaruCarDocs("Subaru Outback 2018-19")],
FORESTER_PREGLOBAL.specs,
dbc_dict('subaru_outback_2019_generated', None),
flags=SubaruFlags.PREGLOBAL,
)
# Angle LKAS
FORESTER_2022 = SubaruPlatformConfig(
"SUBARU FORESTER 2022",
[SubaruCarDocs("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c]))],
FORESTER.specs,
flags=SubaruFlags.LKAS_ANGLE,
)
OUTBACK_2023 = SubaruGen2PlatformConfig(
"SUBARU OUTBACK 7TH GEN",
[SubaruCarDocs("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))],
OUTBACK.specs,
flags=SubaruFlags.LKAS_ANGLE,
)
ASCENT_2023 = SubaruGen2PlatformConfig(
"SUBARU ASCENT 2023",
[SubaruCarDocs("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d]))],
ASCENT.specs,
flags=SubaruFlags.LKAS_ANGLE,
)
LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023, CAR.ASCENT_2023}
GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023, CAR.ASCENT_2023}
PREGLOBAL_CARS = {CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018}
HYBRID_CARS = {CAR.CROSSTREK_HYBRID, CAR.FORESTER_HYBRID}
# 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 = GLOBAL_GEN2 | {CAR.IMPREZA_2020, CAR.FORESTER}
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,
),
# Non-OBD 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],
bus=0,
),
# GEN2 powertrain bus query
Request(
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
@@ -185,24 +286,11 @@ FW_QUERY_CONFIG = FwQueryConfig(
],
# 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(GLOBAL_GEN2),
Ecu.eps: list(CAR.with_flags(SubaruFlags.GLOBAL_GEN2)),
}
)
DBC = {
CAR.ASCENT: dbc_dict('subaru_global_2017_generated', None),
CAR.ASCENT_2023: dbc_dict('subaru_global_2017_generated', None),
CAR.IMPREZA: dbc_dict('subaru_global_2017_generated', None),
CAR.IMPREZA_2020: dbc_dict('subaru_global_2017_generated', None),
CAR.FORESTER: dbc_dict('subaru_global_2017_generated', None),
CAR.FORESTER_2022: dbc_dict('subaru_global_2017_generated', None),
CAR.OUTBACK: dbc_dict('subaru_global_2017_generated', None),
CAR.FORESTER_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None),
CAR.CROSSTREK_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None),
CAR.OUTBACK_2023: dbc_dict('subaru_global_2017_generated', None),
CAR.LEGACY: dbc_dict('subaru_global_2017_generated', None),
CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None),
CAR.LEGACY_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None),
CAR.OUTBACK_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None),
CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None),
}
DBC = CAR.create_dbc_map()
if __name__ == "__main__":
CAR.print_debug(SubaruFlags)