diff --git a/.gitmodules b/.gitmodules
index 26f93ef164e782..c735238ec3247d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,7 @@
[submodule "panda"]
path = panda
- url = ../../commaai/panda.git
+ url = ../../martinl/panda.git
+ branch = forester-2022-PR
[submodule "opendbc"]
path = opendbc
url = ../../commaai/opendbc.git
diff --git a/RELEASES.md b/RELEASES.md
index 6b5751507bb968..c51c30d98039a0 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -11,6 +11,7 @@ Version 0.9.4 (2023-XX-XX)
* Alerts are shown inside the border. Black/grey means info, orange means warning, and red means critical alert
* Bookmarked segments are preserved on the device's storage
* Ford Focus 2018 support
+* Subaru Forester 2022 support thanks to martinl!
* Kia Carnival 2023 support thanks to sunnyhaibin!
Version 0.9.3 (2023-06-29)
diff --git a/docs/CARS.md b/docs/CARS.md
index a6148bb78135bf..fa94e8398d5c9f 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
-# 255 Supported Cars
+# 256 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|
Hardware Needed
|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -171,6 +171,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|View
- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|View
- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Subaru|Forester 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|View
- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Subaru|Forester 2022|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|View
- 1 RJ45 cable (7 ft)
- 1 Subaru C connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|View
- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|View
- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|View
- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
diff --git a/opendbc b/opendbc
index 4231b0f12d8cf1..5880fbbccf5a67 160000
--- a/opendbc
+++ b/opendbc
@@ -1 +1 @@
-Subproject commit 4231b0f12d8cf10d0554c4eb513ac984defc1f90
+Subproject commit 5880fbbccf5a670631b51836f20e446de643795a
diff --git a/panda b/panda
index dd78b2bf6c9d63..3b96f0d160cf24 160000
--- a/panda
+++ b/panda
@@ -1 +1 @@
-Subproject commit dd78b2bf6c9d63ef59e81d0c400e85c8b477a8be
+Subproject commit 3b96f0d160cf24937bdc36c1a49b8c24cc1cd7e2
diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py
index 5db720b8f5a124..86f3483ba67214 100644
--- a/selfdrive/car/docs_definitions.py
+++ b/selfdrive/car/docs_definitions.py
@@ -87,6 +87,7 @@ class CarHarness(EnumBase):
toyota = BaseCarHarness("Toyota connector")
subaru_a = BaseCarHarness("Subaru A connector")
subaru_b = BaseCarHarness("Subaru B connector")
+ subaru_c = BaseCarHarness("Subaru C connector")
fca = BaseCarHarness("FCA connector")
ram = BaseCarHarness("Ram connector")
vw = BaseCarHarness("VW connector")
diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py
index b37c88797a9367..fd737aa4012b28 100644
--- a/selfdrive/car/subaru/carcontroller.py
+++ b/selfdrive/car/subaru/carcontroller.py
@@ -1,7 +1,7 @@
from opendbc.can.packer import CANPacker
-from selfdrive.car import apply_driver_steer_torque_limits
+from selfdrive.car import apply_driver_steer_torque_limits, apply_std_steer_angle_limits
from selfdrive.car.subaru import subarucan
-from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, CarControllerParams, SubaruFlags
+from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, LKAS_ANGLE, PREGLOBAL_CARS, CanBus, CarControllerParams, SubaruFlags
class CarController:
@@ -25,23 +25,31 @@ def update(self, CC, CS, now_nanos):
# *** steering ***
if (self.frame % self.p.STEER_STEP) == 0:
- apply_steer = int(round(actuators.steer * self.p.STEER_MAX))
+ # angle based steering
+ if self.CP.carFingerprint in LKAS_ANGLE:
+ apply_steer = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_steer_last, CS.out.vEgoRaw, self.p)
+
+ if not CC.latActive:
+ apply_steer = CS.out.steeringAngleDeg
- # limits due to driver torque
+ can_sends.append(subarucan.create_steering_control_angle(self.packer, apply_steer, CC.latActive))
- new_steer = int(round(apply_steer))
- apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.p)
+ # torque based steering
+ else:
+ apply_steer = int(round(actuators.steer * self.p.STEER_MAX))
- if not CC.latActive:
- apply_steer = 0
+ # limits due to driver torque
+ apply_steer = apply_driver_steer_torque_limits(apply_steer, self.apply_steer_last, CS.out.steeringTorque, self.p)
- if self.CP.carFingerprint in PREGLOBAL_CARS:
- can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, CC.latActive))
- else:
- can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, CC.latActive))
+ if not CC.latActive:
+ apply_steer = 0
- self.apply_steer_last = apply_steer
+ if self.CP.carFingerprint in PREGLOBAL_CARS:
+ can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, CC.latActive))
+ else:
+ can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, CC.latActive))
+ self.apply_steer_last = apply_steer
# *** alerts and pcm cancel ***
if self.CP.carFingerprint in PREGLOBAL_CARS:
@@ -80,8 +88,10 @@ def update(self, CC, CS, now_nanos):
can_sends.append(subarucan.create_es_infotainment(self.packer, CS.es_infotainment_msg, hud_control.visualAlert))
new_actuators = actuators.copy()
- new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX
- new_actuators.steerOutputCan = self.apply_steer_last
-
+ if self.CP.carFingerprint in LKAS_ANGLE:
+ new_actuators.steeringAngleDeg = self.apply_steer_last
+ else:
+ new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX
+ new_actuators.steerOutputCan = self.apply_steer_last
self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py
index 189c244ca82727..a850ba7594a5e0 100644
--- a/selfdrive/car/subaru/carstate.py
+++ b/selfdrive/car/subaru/carstate.py
@@ -4,7 +4,7 @@
from common.conversions import Conversions as CV
from selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
-from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, SubaruFlags
+from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, ES_STATUS, PREGLOBAL_CARS, CanBus, SubaruFlags
class CarState(CarStateBase):
@@ -53,9 +53,13 @@ def update(self, cp, cp_cam, cp_body):
steer_threshold = 75 if self.CP.carFingerprint in PREGLOBAL_CARS else 80
ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold
- cp_cruise = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
- ret.cruiseState.enabled = cp_cruise.vl["CruiseControl"]["Cruise_Activated"] != 0
- ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0
+ if self.car_fingerprint in ES_STATUS:
+ ret.cruiseState.enabled = cp_cam.vl["ES_Status"]['Cruise_Activated'] != 0
+ ret.cruiseState.available = cp_cam.vl["ES_DashStatus"]['Cruise_On'] != 0
+ else:
+ cp_cruise = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
+ 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.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \
@@ -135,11 +139,13 @@ def get_common_global_es_signals():
("Cruise_Set", "ES_Distance"),
("Cruise_Resume", "ES_Distance"),
("Signal6", "ES_Distance"),
+ ("Cruise_Activated", "ES_Status"),
]
checks = [
("ES_Brake", 20),
("ES_Distance", 20),
+ ("ES_Status", 20),
]
return signals, checks
diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py
index f7698dbe7c0565..861bb9a8c25de8 100644
--- a/selfdrive/car/subaru/interface.py
+++ b/selfdrive/car/subaru/interface.py
@@ -3,7 +3,7 @@
from panda import Panda
from selfdrive.car import STD_CARGO_KG, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
-from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS, SubaruFlags
+from selfdrive.car.subaru.values import LKAS_ANGLE, CAR, ES_STATUS, GLOBAL_GEN2, PREGLOBAL_CARS, SubaruFlags
class CarInterface(CarInterfaceBase):
@@ -27,6 +27,10 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs):
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)]
if candidate in GLOBAL_GEN2:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_GEN2
+ if candidate in LKAS_ANGLE:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_LKAS_ANGLE
+ if candidate in ES_STATUS:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_ES_STATUS
ret.steerLimitTimer = 0.4
ret.steerActuatorDelay = 0.1
@@ -64,7 +68,7 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs):
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.FORESTER:
+ elif candidate in (CAR.FORESTER, CAR.FORESTER_2022):
ret.mass = 1568. + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py
index 0c32a150d8c604..78ddaf1075f7a7 100644
--- a/selfdrive/car/subaru/subarucan.py
+++ b/selfdrive/car/subaru/subarucan.py
@@ -12,6 +12,13 @@ def create_steering_control(packer, apply_steer, steer_req):
}
return packer.make_can_msg("ES_LKAS", 0, values)
+def create_steering_control_angle(packer, apply_steer, steer_req):
+ values = {
+ "LKAS_Output": apply_steer,
+ "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, {})
diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py
index 0e3f2e8d0523b8..ed9ae899f09633 100644
--- a/selfdrive/car/subaru/values.py
+++ b/selfdrive/car/subaru/values.py
@@ -4,7 +4,7 @@
from cereal import car
from panda.python import uds
-from selfdrive.car import dbc_dict
+from selfdrive.car import AngleRateLimit, dbc_dict
from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column
from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
@@ -19,15 +19,19 @@ def __init__(self, CP):
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.carFingerprint in GLOBAL_GEN2:
- self.STEER_MAX = 1000
- self.STEER_DELTA_UP = 40
- self.STEER_DELTA_DOWN = 40
- elif CP.carFingerprint == CAR.IMPREZA_2020:
- self.STEER_MAX = 1439
+
+ if CP.carFingerprint in LKAS_ANGLE:
+ self.ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[0.], angle_v=[1.])
+ self.ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[0.], angle_v=[1.])
else:
- self.STEER_MAX = 2047
+ if CP.carFingerprint in GLOBAL_GEN2:
+ self.STEER_MAX = 1000
+ self.STEER_DELTA_UP = 40
+ self.STEER_DELTA_DOWN = 40
+ elif CP.carFingerprint == CAR.IMPREZA_2020:
+ self.STEER_MAX = 1439
+ else:
+ self.STEER_MAX = 2047
class SubaruFlags(IntFlag):
@@ -46,6 +50,7 @@ class CAR:
IMPREZA = "SUBARU IMPREZA LIMITED 2019"
IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020"
FORESTER = "SUBARU FORESTER 2019"
+ FORESTER_2022 = "SUBARU FORESTER 2022"
OUTBACK = "SUBARU OUTBACK 6TH GEN"
LEGACY = "SUBARU LEGACY 7TH GEN"
@@ -84,6 +89,7 @@ class SubaruCarInfo(CarInfo):
SubaruCarInfo("Subaru XV 2020-21"),
],
CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"),
+ CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022", car_parts=CarParts.common([CarHarness.subaru_c])),
CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"),
CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"),
CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"),
@@ -331,6 +337,35 @@ class SubaruCarInfo(CarInfo):
b'\x1a\xe6F1\x00',
],
},
+ CAR.FORESTER_2022: {
+ (Ecu.abs, 0x7b0, None): [
+ b'\xa3 !x\x00',
+ b'\xa3 !v\x00',
+ b'\xa3 "v\x00',
+ b'\xa3 "x\x00',
+ ],
+ (Ecu.eps, 0x746, None): [
+ b'-\xc0%0',
+ b'-\xc0\x040',
+ b'=\xc0%\x02',
+ b'=\xc04\x02',
+ ],
+ (Ecu.fwdCamera, 0x787, None): [
+ b'\x04!\x01\x1eD\x07!\x00\x04,'
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'\xd5"a0\x07',
+ b'\xd5"`0\x07',
+ b'\xf1"aq\x07',
+ b'\xf1"`q\x07',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\x1d\x86B0\x00',
+ b'\x1d\xf6B0\x00',
+ b'\x1e\x86B0\x00',
+ b'\x1e\xf6D0\x00',
+ ],
+ },
CAR.FORESTER_PREGLOBAL: {
(Ecu.abs, 0x7b0, None): [
b'\x7d\x97\x14\x40',
@@ -543,6 +578,7 @@ class SubaruCarInfo(CarInfo):
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.LEGACY: dbc_dict('subaru_global_2017_generated', None),
CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None),
@@ -551,5 +587,8 @@ class SubaruCarInfo(CarInfo):
CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None),
}
+LKAS_ANGLE = (CAR.FORESTER_2022,)
+ES_STATUS = (CAR.FORESTER_2022,)
+
GLOBAL_GEN2 = (CAR.OUTBACK, CAR.LEGACY)
PREGLOBAL_CARS = (CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018)
diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py
index 66f4ad2de583a3..22ec1656ca0c07 100644
--- a/selfdrive/car/tests/routes.py
+++ b/selfdrive/car/tests/routes.py
@@ -234,6 +234,8 @@
CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020),
CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10),
CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3),
+ CarTestRoute("7fd1e4f3a33c1673|2022-12-04--15-09-53", SUBARU.FORESTER_2022, segment=4),
+
# Pre-global, dashcam
CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL),
CarTestRoute("df5ca7660000fba8|2020-06-16--17-37-19", SUBARU.LEGACY_PREGLOBAL),
diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml
index d79dbe8573d17e..5fe279ed022ec0 100644
--- a/selfdrive/car/torque_data/substitute.yaml
+++ b/selfdrive/car/torque_data/substitute.yaml
@@ -77,6 +77,7 @@ SEAT LEON 3RD GEN: VOLKSWAGEN GOLF 7TH GEN
SEAT ATECA 1ST GEN: VOLKSWAGEN GOLF 7TH GEN
SUBARU LEGACY 7TH GEN: SUBARU OUTBACK 6TH GEN
+SUBARU FORESTER 2022: SUBARU FORESTER 2019
# Old subarus don't have much data guessing it's like low torque impreza
SUBARU OUTBACK 2018 - 2019: SUBARU IMPREZA LIMITED 2019