Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b32ae52
Add support for Subaru Crosstrek 2025 and LKAS angle-based steering c…
jacobwaller Oct 17, 2025
7e4c6f2
Introduce LKAS angle-based control for Subaru Crosstrek 2025
jacobwaller Oct 17, 2025
4009605
Merge branch 'master' into crosstrek-port-oct-25
jacobwaller Oct 17, 2025
ac6cf1f
Configure panda safety for LKAS angle platforms and fix Crosstrek 202…
jacobwaller Oct 17, 2025
20ad363
Remove incorrect `inactive_angle_is_zero` from Subaru safety config
jacobwaller Oct 17, 2025
fe411cd
Actually use the damn alt-bus
jacobwaller Oct 18, 2025
2715055
Move freq, don't re-convert to centidegrees
jacobwaller Oct 19, 2025
3af8ccd
Fix Subaru Crosstrek 2025 recognition and adjust LKAS angle safety logic
jacobwaller Oct 20, 2025
db2a23e
docs: Scheduled auto-update CARS.md
jacobwaller Oct 20, 2025
2d812ae
Update angle limits, add .idea to gitignore
jacobwaller Oct 21, 2025
5f18e84
Merge branch 'master' of github.com:jacobwaller/opendbc into o-master
jacobwaller Oct 21, 2025
c03d41a
Merge branch 'commaai:master' into master
jacobwaller Oct 21, 2025
3847543
Update routes.py
jacobwaller Oct 21, 2025
cc0d9ee
attempt to add steering msgs to replay_drive
Oct 21, 2025
eb7b7cb
Revert to V1 Angle limiting. Run the limits on whatever angle the ste…
Oct 22, 2025
86cdba3
Revert to completely normal angle rate limiting, reduce the angles al…
Oct 22, 2025
a3ea3ca
Horrible debugging idea
Oct 22, 2025
0076d26
undo bad debugging idea
Oct 22, 2025
73d7b5a
Use steering offset in car controller, re-up angle limits in values.py
Oct 22, 2025
ed1019d
Don't add steeringOffset, i don't think we need to do that
Oct 23, 2025
6d827fb
Try making sure the limits are accesible
Oct 25, 2025
e00ea82
Cleanup, add long messages, available... not now
Oct 25, 2025
a5b1f73
Clean up the looks of angle limits + reduce limits for car controller…
Oct 26, 2025
7b4bf17
Set limits to be less horizontal at normal driving speeds
Oct 26, 2025
167361c
Make car controller slightly more restrictive with angle rates
Oct 26, 2025
ce09ffc
Merge branch 'commaai:master' into master
jacobwaller Oct 28, 2025
62fa9c4
Remove slight angle limit restrictions and cleanup unused function in…
Oct 28, 2025
3207460
don't send torque and old angle message for angle-lkas
Oct 29, 2025
f8f1ff0
Update route
Oct 30, 2025
92e7343
Merge branch 'commaai:master' into master
jacobwaller Nov 7, 2025
5dab047
Merge branch 'master' into master
jacobwaller Nov 10, 2025
9a81668
docs: Scheduled auto-update CARS.md
jacobwaller Nov 11, 2025
6e2a89b
Merge branch 'master' into master
jacobwaller Nov 11, 2025
91dbf8b
Merge branch 'master' into master
jacobwaller Nov 17, 2025
dc8aa2e
Merge branch 'master' into master
jacobwaller Nov 18, 2025
b5502a9
Merge branch 'master' into master
jacobwaller Nov 29, 2025
8b31db1
Respond to PR comments
Dec 3, 2025
4fae595
Rest of the comments
Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/CARS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--- AUTOGENERATED FROM selfdrive/car/CARS_template.md, DO NOT EDIT. --->

# Support Information for 384 Known Cars
# Support Information for 385 Known Cars

|Make|Model|Package|Support Level|
|---|---|---|:---:|
Expand Down Expand Up @@ -260,6 +260,7 @@
|Subaru|Ascent 2023|All|[Dashcam mode](#dashcam)|
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|[Upstream](#upstream)|
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|[Upstream](#upstream)|
|Subaru|Crosstrek 2025|All|[Upstream](#upstream)|
|Subaru|Crosstrek Hybrid 2020|EyeSight Driver Assistance|[Dashcam mode](#dashcam)|
|Subaru|Forester 2017-18|EyeSight Driver Assistance|[Dashcam mode](#dashcam)|
|Subaru|Forester 2019-21|All|[Upstream](#upstream)|
Expand Down
1 change: 1 addition & 0 deletions opendbc/car/fingerprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ def all_legacy_fingerprint_cars():
"SUBARU IMPREZA LIMITED 2019": SUBARU.SUBARU_IMPREZA,
"SUBARU IMPREZA SPORT 2020": SUBARU.SUBARU_IMPREZA_2020,
"SUBARU CROSSTREK HYBRID 2020": SUBARU.SUBARU_CROSSTREK_HYBRID,
"SUBARU CROSSTREK 2025": SUBARU.SUBARU_CROSSTREK_2025,
"SUBARU FORESTER 2019": SUBARU.SUBARU_FORESTER,
"SUBARU FORESTER HYBRID 2020": SUBARU.SUBARU_FORESTER_HYBRID,
"SUBARU FORESTER 2017 - 2018": SUBARU.SUBARU_FORESTER_PREGLOBAL,
Expand Down
63 changes: 42 additions & 21 deletions opendbc/car/subaru/carcontroller.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy as np
from opendbc.can import CANPacker
from opendbc.car import Bus, make_tester_present_msg
from opendbc.car.lateral import apply_driver_steer_torque_limits, common_fault_avoidance
from opendbc.car.lateral import apply_driver_steer_torque_limits, common_fault_avoidance, apply_std_steer_angle_limits
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.subaru import subarucan
from opendbc.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarControllerParams, SubaruFlags
Expand All @@ -16,6 +16,7 @@ class CarController(CarControllerBase):
def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP)
self.apply_torque_last = 0
self.apply_steer_last = 0

self.cruise_button_prev = 0
self.steer_rate_counter = 0
Expand All @@ -32,30 +33,47 @@ def update(self, CC, CS, now_nanos):

# *** steering ***
if (self.frame % self.p.STEER_STEP) == 0:
apply_torque = int(round(actuators.torque * self.p.STEER_MAX))

# limits due to driver torque
if self.CP.flags & SubaruFlags.LKAS_ANGLE:
actual_steering_angle_deg = CS.out.steeringAngleDeg
desired_steering_angle_deg = actuators.steeringAngleDeg

apply_steer = apply_std_steer_angle_limits(
desired_steering_angle_deg,
self.apply_steer_last,
CS.out.vEgoRaw,
actual_steering_angle_deg,
CC.latActive,
self.p.ANGLE_LIMITS
)

if not CC.latActive:
apply_steer = actual_steering_angle_deg

can_sends.append(subarucan.create_steering_control_angle(self.packer, apply_steer, CC.latActive))
self.apply_steer_last = apply_steer
else:
apply_torque = int(round(actuators.torque * self.p.STEER_MAX))

new_torque = int(round(apply_torque))
apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.p)
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 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.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)
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))
can_sends.append(subarucan.create_steering_control(self.packer, apply_torque, apply_steer_req))

self.apply_torque_last = apply_torque
self.apply_torque_last = apply_torque

# *** longitudinal ***

Expand Down Expand Up @@ -137,8 +155,11 @@ def update(self, CC, CS, now_nanos):
can_sends.append(subarucan.create_es_static_2(self.packer))

new_actuators = actuators.as_builder()
new_actuators.torque = self.apply_torque_last / self.p.STEER_MAX
new_actuators.torqueOutputCan = self.apply_torque_last
if self.CP.flags & SubaruFlags.LKAS_ANGLE:
new_actuators.steeringAngleDeg = self.apply_steer_last
else:
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
16 changes: 11 additions & 5 deletions opendbc/car/subaru/carstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,17 @@ def update(self, can_parsers) -> structs.CarState:
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.CP.flags & SubaruFlags.LKAS_ANGLE:
ret.steeringAngleDeg = cp.vl["Steering_2"]["Steering_Angle"]
counter = cp.vl["Steering_2"]["COUNTER"]

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.steeringRateDeg = self.angle_rate_calulator.update(ret.steeringAngleDeg, counter)
else:
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]

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"]
Expand All @@ -73,7 +79,7 @@ def update(self, can_parsers) -> structs.CarState:
ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold

cp_cruise = cp_alt if self.CP.flags & SubaruFlags.GLOBAL_GEN2 else cp
if self.CP.flags & SubaruFlags.HYBRID:
if self.CP.flags & SubaruFlags.HYBRID or self.CP.flags & SubaruFlags.LKAS_ANGLE:
ret.cruiseState.enabled = cp_cam.vl["ES_DashStatus"]['Cruise_Activated'] != 0
ret.cruiseState.available = cp_cam.vl["ES_DashStatus"]['Cruise_On'] != 0
else:
Expand Down
14 changes: 14 additions & 0 deletions opendbc/car/subaru/fingerprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,20 @@
b'\xd7!`p\x07',
b'\xf4!`0\x07',
],
},
CAR.SUBARU_CROSSTREK_2025: {
(Ecu.abs, 0x7b0, None): [
b'\xa2 $\x17\x06',
],
(Ecu.eps, 0x746, None): [
b'\xc2 $\x00\x01',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x1d!\x08\x00F\x14!\x08\x00=',
],
(Ecu.engine, 0x7a2, None): [
b'\x04"cP\x07',
],
},
CAR.SUBARU_FORESTER: {
(Ecu.abs, 0x7b0, None): [
Expand Down
7 changes: 7 additions & 0 deletions opendbc/car/subaru/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alp
else:
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)

if ret.flags & SubaruFlags.LKAS_ANGLE:
ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.LKAS_ANGLE.value

if candidate in (CAR.SUBARU_ASCENT, CAR.SUBARU_ASCENT_2023):
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
ret.lateralTuning.init('pid')
Expand All @@ -65,6 +68,10 @@ def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alp
elif candidate == CAR.SUBARU_CROSSTREK_HYBRID:
ret.steerActuatorDelay = 0.1

elif candidate == CAR.SUBARU_CROSSTREK_2025:
ret.dashcamOnly = is_release
ret.steerActuatorDelay = 0.3

elif candidate in (CAR.SUBARU_FORESTER, CAR.SUBARU_FORESTER_2022, CAR.SUBARU_FORESTER_HYBRID):
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.000038
Expand Down
13 changes: 13 additions & 0 deletions opendbc/car/subaru/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from enum import Enum, IntFlag

from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
from opendbc.car.lateral import AngleSteeringLimits
from opendbc.car.structs import CarParams
from opendbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column
from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
Expand All @@ -18,6 +19,12 @@ def __init__(self, CP):
self.STEER_DRIVER_MULTIPLIER = 50 # weight driver torque heavily
self.STEER_DRIVER_FACTOR = 1 # from dbc

self.ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
545,
([0., 5., 35.], [5., .8, .15,]),
([0., 5., 35.], [5., .8, .15,]),
)

if CP.flags & SubaruFlags.GLOBAL_GEN2:
# TODO: lower rate limits, this reaches min/max in 0.5s which negatively affects tuning
self.STEER_MAX = 1000
Expand Down Expand Up @@ -57,6 +64,7 @@ class SubaruSafetyFlags(IntFlag):
GEN2 = 1
LONG = 2
PREGLOBAL_REVERSED_DRIVER_TORQUE = 4
LKAS_ANGLE = 8


class SubaruFlags(IntFlag):
Expand Down Expand Up @@ -211,6 +219,11 @@ class CAR(Platforms):
SUBARU_ASCENT.specs,
flags=SubaruFlags.LKAS_ANGLE,
)
SUBARU_CROSSTREK_2025 = SubaruGen2PlatformConfig(
[SubaruCarDocs("Subaru Crosstrek 2025", "All", car_parts=CarParts.common([CarHarness.subaru_d]))],
CarSpecs(mass=1529, wheelbase=2.5781, steerRatio=13.5),
flags=SubaruFlags.LKAS_ANGLE
)


SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
Expand Down
1 change: 1 addition & 0 deletions opendbc/car/tests/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ class CarTestRoute(NamedTuple):
CarTestRoute("1bbe6bf2d62f58a8/2022-07-14--17-11-43", SUBARU.SUBARU_OUTBACK, segment=10),
CarTestRoute("c56e69bbc74b8fad/2022-08-18--09-43-51", SUBARU.SUBARU_LEGACY, segment=3),
CarTestRoute("f4e3a0c511a076f4/2022-08-04--16-16-48", SUBARU.SUBARU_CROSSTREK_HYBRID, segment=2),
CarTestRoute("38b065e31c0a9ed7/0000000b--eab0d07145", SUBARU.SUBARU_CROSSTREK_2025, segment=39),
CarTestRoute("7fd1e4f3a33c1673/2022-12-04--15-09-53", SUBARU.SUBARU_FORESTER_2022, segment=4),
CarTestRoute("f3b34c0d2632aa83/2023-07-23--20-43-25", SUBARU.SUBARU_OUTBACK_2023, segment=7),
CarTestRoute("99437cef6d5ff2ee/2023-03-13--21-21-38", SUBARU.SUBARU_ASCENT_2023, segment=7),
Expand Down
1 change: 1 addition & 0 deletions opendbc/car/torque_data/override.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"SUBARU_FORESTER_2022" = [nan, 3.0, nan]
"SUBARU_OUTBACK_2023" = [nan, 3.0, nan]
"SUBARU_ASCENT_2023" = [nan, 3.0, nan]
"SUBARU_CROSSTREK_2025" = [nan, 3.0, nan]

# Toyota LTA also has torque
"TOYOTA_RAV4_TSS2_2023" = [nan, 3.0, nan]
Expand Down
Loading