这是indexloc提供的服务,不要输入任何密码
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2bf6181
Add support for Subaru Crosstrek 2025 and LKAS angle-based steering c…
jacobwaller Oct 17, 2025
b515f9b
Introduce LKAS angle-based control for Subaru Crosstrek 2025
jacobwaller Oct 17, 2025
05d0a46
Configure panda safety for LKAS angle platforms and fix Crosstrek 202…
jacobwaller Oct 17, 2025
08776f2
Remove incorrect `inactive_angle_is_zero` from Subaru safety config
jacobwaller Oct 17, 2025
0b91522
Actually use the damn alt-bus
jacobwaller Oct 18, 2025
6d5b641
Move freq, don't re-convert to centidegrees
jacobwaller Oct 19, 2025
5e0fb2c
Fix Subaru Crosstrek 2025 recognition and adjust LKAS angle safety logic
jacobwaller Oct 20, 2025
0927565
Update angle limits, add .idea to gitignore
jacobwaller Oct 21, 2025
7ebffd4
docs: Scheduled auto-update CARS.md
jacobwaller Oct 20, 2025
faa905b
Update routes.py
jacobwaller Oct 21, 2025
d133e87
attempt to add steering msgs to replay_drive
Oct 21, 2025
e146680
Revert to V1 Angle limiting. Run the limits on whatever angle the ste…
Oct 22, 2025
89d6172
Revert to completely normal angle rate limiting, reduce the angles al…
Oct 22, 2025
a31028a
Horrible debugging idea
Oct 22, 2025
35d8c2b
undo bad debugging idea
Oct 22, 2025
7719f8e
Use steering offset in car controller, re-up angle limits in values.py
Oct 22, 2025
d21a4ca
Don't add steeringOffset, i don't think we need to do that
Oct 23, 2025
4346f00
Try making sure the limits are accesible
Oct 25, 2025
b44da8c
Cleanup, add long messages, available... not now
Oct 25, 2025
c041259
Clean up the looks of angle limits + reduce limits for car controller…
Oct 26, 2025
617b69c
Initial vehicle model angle limits implementation based on tesla
martinl Oct 26, 2025
dd6d575
Remove duplicate include
martinl Oct 26, 2025
2f3565c
fix copy-paste typo
martinl Oct 26, 2025
c769ce6
remove unused apply_std_steer_angle_limits
martinl Oct 26, 2025
0477c20
add missing functions to tests
martinl Oct 26, 2025
cad9c1f
Trigger GitHub Actions
martinl Oct 26, 2025
788a56b
fix AVERAGE_ROAD_ROLL
martinl Oct 26, 2025
8db7475
Add VM to carcontroller
martinl Oct 26, 2025
f213e51
include VehicleModel
martinl Oct 26, 2025
a42c453
add np
martinl Oct 26, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea/
.cache/
/build/
.mypy_cache/
Expand Down
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 383 Known Cars
# Support Information for 384 Known Cars

|Make|Model|Package|Support Level|
|---|---|---|:---:|
Expand Down Expand Up @@ -259,6 +259,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
69 changes: 47 additions & 22 deletions opendbc/car/subaru/carcontroller.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
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_steer_angle_limits_vm
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
from opendbc.car.vehicle_model import VehicleModel

# 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):
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

self.p = CarControllerParams(CP)
self.packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])

# Vehicle model used for lateral limiting
self.VM = VehicleModel(CP)

def update(self, CC, CS, now_nanos):
actuators = CC.actuators
hud_control = CC.hudControl
Expand All @@ -32,30 +36,48 @@ 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_steer_angle_limits_vm(
desired_steering_angle_deg,
self.apply_steer_last,
CS.out.vEgoRaw,
actual_steering_angle_deg,
CC.latActive,
CarControllerParams,
self.VM
)

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 +159,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 = False
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
27 changes: 26 additions & 1 deletion opendbc/car/subaru/values.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from dataclasses import dataclass, field
from enum import Enum, IntFlag

from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds, ACCELERATION_DUE_TO_GRAVITY
from opendbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL
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 Down Expand Up @@ -52,11 +53,30 @@ def __init__(self, CP):
BRAKE_LOOKUP_BP = [-3.5, 0]
BRAKE_LOOKUP_V = [BRAKE_MAX, BRAKE_MIN]

AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation.

ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
100,
# v1 limits, not used
([], []),
([], []),

# Vehicle model angle limits
# Add extra tolerance for average banked road since safety doesn't have the roll
MAX_LATERAL_ACCEL=ISO_LATERAL_ACCEL + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL), # ~3.6 m/s^2
MAX_LATERAL_JERK=3.0 + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL), # ~3.6 m/s^3

# limit angle rate to both prevent a fault and for low speed comfort (~12 mph rate down to 0 mph)
# FIXME: convert from carcontroller known limit
# MAX_STEER_RATE = 25 # deg/s
MAX_ANGLE_RATE=5, # deg/20ms frame
)

class SubaruSafetyFlags(IntFlag):
GEN2 = 1
LONG = 2
PREGLOBAL_REVERSED_DRIVER_TORQUE = 4
LKAS_ANGLE = 8


class SubaruFlags(IntFlag):
Expand Down Expand Up @@ -211,6 +231,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 @@ -299,6 +299,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/00000001--5656806473", SUBARU.SUBARU_CROSSTREK_2025, segment=2),
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
Loading