diff --git a/cereal/car.capnp b/cereal/car.capnp
index 3df23d99214ea2..c724b43f72dc00 100644
--- a/cereal/car.capnp
+++ b/cereal/car.capnp
@@ -610,6 +610,7 @@ struct CarParams {
volkswagenMqbEvo @29;
chryslerCusw @30;
psa @31;
+ fcaGiorgio @32; # certain FCA/Stellantis vehicles including Jeep, Alfa Romeo, and Maserati
}
enum SteerControlType {
diff --git a/docs/CARS.md b/docs/CARS.md
index 8fafc483f2ded3..1ab3dd0c64ac79 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -4,13 +4,14 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
-# 287 Supported Cars
+# 288 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Parts
- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Parts
- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Parts
- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Alfa|Romeo Stelvio 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Parts
- 1 RJ45 cable (7 ft)
- 1 VW A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Parts
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Parts
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Parts
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
diff --git a/launch_env.sh b/launch_env.sh
index 81578aff0157e3..81fe4ee0beb11b 100755
--- a/launch_env.sh
+++ b/launch_env.sh
@@ -11,3 +11,5 @@ if [ -z "$AGNOS_VERSION" ]; then
fi
export STAGING_ROOT="/data/safe_staging"
+
+export FINGERPRINT="ALFA_ROMEO_STELVIO_1ST_GEN"
diff --git a/opendbc b/opendbc
index de39b143a31cd2..739bc5004b851a 160000
--- a/opendbc
+++ b/opendbc
@@ -1 +1 @@
-Subproject commit de39b143a31cd29b4df4c0cbba71316c1bc8e71b
+Subproject commit 739bc5004b851a5daebcf5d7eb437c3f38b3eac6
diff --git a/panda b/panda
index 376408bb4f6706..cb63c2a9fef00a 160000
--- a/panda
+++ b/panda
@@ -1 +1 @@
-Subproject commit 376408bb4f6706b682eb6d607bedd14958665084
+Subproject commit cb63c2a9fef00aedcb80dff1135ae120943947f8
diff --git a/selfdrive/car/fca_giorgio/__init__.py b/selfdrive/car/fca_giorgio/__init__.py
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/selfdrive/car/fca_giorgio/carcontroller.py b/selfdrive/car/fca_giorgio/carcontroller.py
new file mode 100644
index 00000000000000..daaee560f3c2e4
--- /dev/null
+++ b/selfdrive/car/fca_giorgio/carcontroller.py
@@ -0,0 +1,46 @@
+from opendbc.can.packer import CANPacker
+from openpilot.selfdrive.car import apply_driver_steer_torque_limits
+from openpilot.selfdrive.car.interfaces import CarControllerBase
+
+from openpilot.selfdrive.car.fca_giorgio import fca_giorgiocan
+from openpilot.selfdrive.car.fca_giorgio.values import CANBUS, CarControllerParams
+
+
+class CarController(CarControllerBase):
+ def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
+ self.CCP = CarControllerParams(CP)
+ self.packer_pt = CANPacker(dbc_name)
+
+ self.apply_steer_last = 0
+ self.frame = 0
+
+ def update(self, CC, CS, now_nanos):
+ actuators = CC.actuators
+ can_sends = []
+
+ # **** Steering Controls ************************************************ #
+
+ if self.frame % self.CCP.STEER_STEP == 0:
+ if CC.latActive:
+ new_steer = int(round(actuators.steer * self.CCP.STEER_MAX))
+ apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.CCP)
+ else:
+ apply_steer = 0
+
+ self.apply_steer_last = apply_steer
+ can_sends.append(fca_giorgiocan.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, CC.latActive))
+
+ # **** HUD Controls ***************************************************** #
+
+ if self.frame % self.CCP.HUD_1_STEP == 0:
+ can_sends.append(fca_giorgiocan.create_lka_hud_1_control(self.packer_pt, CANBUS.pt, CC.latActive))
+ if self.frame % self.CCP.HUD_2_STEP == 0:
+ can_sends.append(fca_giorgiocan.create_lka_hud_2_control(self.packer_pt, CANBUS.pt, CC.latActive))
+
+ new_actuators = actuators.as_builder()
+ new_actuators.steer = self.apply_steer_last / self.CCP.STEER_MAX
+ new_actuators.steerOutputCan = self.apply_steer_last
+
+ self.frame += 1
+ return new_actuators, can_sends
diff --git a/selfdrive/car/fca_giorgio/carstate.py b/selfdrive/car/fca_giorgio/carstate.py
new file mode 100644
index 00000000000000..8bd42e4bd4035a
--- /dev/null
+++ b/selfdrive/car/fca_giorgio/carstate.py
@@ -0,0 +1,88 @@
+import numpy as np
+from cereal import car
+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.fca_giorgio.values import DBC, CANBUS, CarControllerParams
+
+
+GearShifter = car.CarState.GearShifter
+
+class CarState(CarStateBase):
+ def __init__(self, CP):
+ super().__init__(CP)
+ self.frame = 0
+ self.CCP = CarControllerParams(CP)
+
+ def update(self, pt_cp, cam_cp):
+ ret = car.CarState.new_message()
+ # Update vehicle speed and acceleration from ABS wheel speeds.
+ ret.wheelSpeeds = self.get_wheel_speeds(
+ pt_cp.vl["ABS_1"]["WHEEL_SPEED_FL"],
+ pt_cp.vl["ABS_1"]["WHEEL_SPEED_FR"],
+ pt_cp.vl["ABS_1"]["WHEEL_SPEED_RL"],
+ pt_cp.vl["ABS_1"]["WHEEL_SPEED_RR"],
+ unit=1.0
+ )
+
+ ret.vEgoRaw = float(np.mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]))
+ ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+ ret.standstill = ret.vEgoRaw == 0
+
+ ret.steeringAngleDeg = pt_cp.vl["EPS_1"]["STEERING_ANGLE"]
+ ret.steeringRateDeg = pt_cp.vl["EPS_1"]["STEERING_RATE"]
+ ret.steeringTorque = pt_cp.vl["EPS_2"]["DRIVER_TORQUE"]
+ ret.steeringTorqueEps = pt_cp.vl["EPS_3"]["EPS_TORQUE"]
+ ret.steeringPressed = ret.steeringTorque > 80
+ ret.yawRate = pt_cp.vl["ABS_2"]["YAW_RATE"]
+ ret.steerFaultPermanent = bool(pt_cp.vl["EPS_2"]["LKA_FAULT"])
+
+ # TODO: unsure if this is accel pedal or engine throttle
+ #ret.gas = pt_cp.vl["ENGINE_1"]["ACCEL_PEDAL"]
+ ret.gasPressed = ret.gas > 0
+ ret.brake = pt_cp.vl["ABS_4"]["BRAKE_PRESSURE"]
+ ret.brakePressed = bool(pt_cp.vl["ABS_3"]["BRAKE_PEDAL_SWITCH"])
+ #ret.parkingBrake = TODO
+
+ if bool(pt_cp.vl["ENGINE_1"]["REVERSE"]):
+ ret.gearShifter = GearShifter.reverse
+ else:
+ ret.gearShifter = GearShifter.drive
+
+ ret.cruiseState.available = pt_cp.vl["ACC_1"]["CRUISE_STATUS"] in (1, 2, 3)
+ ret.cruiseState.enabled = pt_cp.vl["ACC_1"]["CRUISE_STATUS"] in (2, 3)
+ ret.cruiseState.speed = pt_cp.vl["ACC_1"]["HUD_SPEED"] * CV.KPH_TO_MS
+
+ ret.leftBlinker = bool(pt_cp.vl["BCM_1"]["LEFT_TURN_STALK"])
+ ret.rightBlinker = bool(pt_cp.vl["BCM_1"]["RIGHT_TURN_STALK"])
+ # ret.buttonEvents = TODO
+ # ret.espDisabled = TODO
+
+ self.frame += 1
+ return ret
+
+
+ @staticmethod
+ def get_can_parser(CP):
+ messages = [
+ # sig_address, frequency
+ ("ABS_1", 100),
+ ("ABS_2", 100),
+ ("ABS_3", 100),
+ ("ABS_4", 100),
+ ("ENGINE_1", 100),
+ ("EPS_1", 100),
+ ("EPS_2", 100),
+ ("EPS_3", 100),
+ ("ACC_1", 12), # 12hz inactive / 50hz active
+ ("BCM_1", 4), # 4Hz plus triggered updates
+ ]
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], messages, CANBUS.pt)
+
+
+ @staticmethod
+ def get_cam_can_parser(CP):
+ messages = []
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], messages, CANBUS.cam)
diff --git a/selfdrive/car/fca_giorgio/fca_giorgiocan.py b/selfdrive/car/fca_giorgio/fca_giorgiocan.py
new file mode 100644
index 00000000000000..ea27227bc9edd5
--- /dev/null
+++ b/selfdrive/car/fca_giorgio/fca_giorgiocan.py
@@ -0,0 +1,24 @@
+def create_steering_control(packer, bus, apply_steer, lkas_enabled):
+ values = {
+ "LKA_ACTIVE": lkas_enabled,
+ "LKA_TORQUE": apply_steer,
+ }
+
+ return packer.make_can_msg("LKA_COMMAND", bus, values)
+
+
+def create_lka_hud_1_control(packer, bus, lat_active):
+ values = {
+ "NEW_SIGNAL_5": 1,
+ "NEW_SIGNAL_4": 6,
+ }
+
+ return packer.make_can_msg("LKA_HUD_1", bus, values)
+
+
+def create_lka_hud_2_control(packer, bus, lat_active):
+ values = {
+ "NEW_SIGNAL_1": 1,
+ }
+
+ return packer.make_can_msg("LKA_HUD_2", bus, values)
diff --git a/selfdrive/car/fca_giorgio/fingerprints.py b/selfdrive/car/fca_giorgio/fingerprints.py
new file mode 100644
index 00000000000000..46416c07a19296
--- /dev/null
+++ b/selfdrive/car/fca_giorgio/fingerprints.py
@@ -0,0 +1,12 @@
+from cereal import car
+from openpilot.selfdrive.car.fca_giorgio.values import CAR
+
+Ecu = car.CarParams.Ecu
+
+FW_VERSIONS = {
+ CAR.ALFA_ROMEO_STELVIO_1ST_GEN: {
+ (Ecu.engine, 0x7e0, None): [
+ b'PLACEHOLDER',
+ ],
+ }
+}
diff --git a/selfdrive/car/fca_giorgio/interface.py b/selfdrive/car/fca_giorgio/interface.py
new file mode 100644
index 00000000000000..f6e401e5eafce1
--- /dev/null
+++ b/selfdrive/car/fca_giorgio/interface.py
@@ -0,0 +1,37 @@
+from cereal import car
+from openpilot.selfdrive.car import get_safety_config
+from openpilot.selfdrive.car.interfaces import CarInterfaceBase
+from openpilot.selfdrive.car.fca_giorgio.values import CAR
+
+
+class CarInterface(CarInterfaceBase):
+ @staticmethod
+ def _get_params(ret, candidate: CAR, fingerprint, car_fw, experimental_long, docs):
+ ret.carName = "fca_giorgio"
+ ret.radarUnavailable = True
+
+ # Set global parameters
+
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.fcaGiorgio)]
+
+ # Global lateral tuning defaults, can be overridden per-vehicle
+
+ ret.steerLimitTimer = 1.0
+ ret.steerActuatorDelay = 0.1
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
+ # Global longitudinal tuning defaults, can be overridden per-vehicle
+
+ ret.pcmCruise = not ret.openpilotLongitudinalControl
+
+ return ret
+
+ # returns a car.CarState
+ def _update(self, c):
+ ret = self.CS.update(self.cp, self.cp_cam)
+
+ events = self.create_common_events(ret, pcm_enable=not self.CS.CP.openpilotLongitudinalControl)
+
+ ret.events = events.to_msg()
+ return ret
+
diff --git a/selfdrive/car/fca_giorgio/radar_interface.py b/selfdrive/car/fca_giorgio/radar_interface.py
new file mode 100644
index 00000000000000..e654bd61fd4afd
--- /dev/null
+++ b/selfdrive/car/fca_giorgio/radar_interface.py
@@ -0,0 +1,4 @@
+from openpilot.selfdrive.car.interfaces import RadarInterfaceBase
+
+class RadarInterface(RadarInterfaceBase):
+ pass
diff --git a/selfdrive/car/fca_giorgio/values.py b/selfdrive/car/fca_giorgio/values.py
new file mode 100644
index 00000000000000..a61825fc124c8a
--- /dev/null
+++ b/selfdrive/car/fca_giorgio/values.py
@@ -0,0 +1,67 @@
+from dataclasses import dataclass, field
+
+from openpilot.selfdrive.car import dbc_dict, CarSpecs, DbcDict, PlatformConfig, Platforms
+from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarParts
+from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
+
+
+class CarControllerParams:
+ STEER_STEP = 1
+ HUD_1_STEP = 50
+ HUD_2_STEP = 25
+
+ STEER_MAX = 300
+ STEER_DRIVER_ALLOWANCE = 80
+ STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily
+ STEER_DRIVER_FACTOR = 1 # from dbc
+ STEER_DELTA_UP = 4
+ STEER_DELTA_DOWN = 4
+
+ def __init__(self, CP):
+ pass
+
+
+class CANBUS:
+ pt = 0
+ cam = 2
+
+
+@dataclass
+class FcaGiorgioPlatformConfig(PlatformConfig):
+ dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('fca_giorgio', None))
+
+
+@dataclass(frozen=True, kw_only=True)
+class FcaGiorgioCarSpecs(CarSpecs):
+ centerToFrontRatio: float = 0.45
+ steerRatio: float = 14.2
+
+
+@dataclass
+class FcaGiorgioCarDocs(CarDocs):
+ package: str = "Adaptive Cruise Control (ACC) & Lane Assist"
+ car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.vw_a]))
+
+
+class CAR(Platforms):
+ config: FcaGiorgioPlatformConfig
+
+ ALFA_ROMEO_STELVIO_1ST_GEN = FcaGiorgioPlatformConfig(
+ [FcaGiorgioCarDocs("Alfa Romeo Stelvio 2017-24")],
+ FcaGiorgioCarSpecs(mass=1660, wheelbase=2.82),
+ )
+
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ # TODO: check data to ensure ABS does not skip ISO-TP frames on bus 0
+ Request(
+ [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
+ [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
+ bus=0,
+ ),
+ ],
+)
+
+
+DBC = CAR.create_dbc_map()
diff --git a/selfdrive/car/torque_data/override.toml b/selfdrive/car/torque_data/override.toml
index 993eb3fb3c0fa5..98a8cdf91ab772 100644
--- a/selfdrive/car/torque_data/override.toml
+++ b/selfdrive/car/torque_data/override.toml
@@ -48,6 +48,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"VOLKSWAGEN_CADDY_MK3" = [1.2, 1.2, 0.1]
"VOLKSWAGEN_PASSAT_NMS" = [2.5, 2.5, 0.1]
"VOLKSWAGEN_SHARAN_MK2" = [2.5, 2.5, 0.1]
+"ALFA_ROMEO_STELVIO_1ST_GEN" = [1.9, 1.9, 0.11]
"HYUNDAI_SANTA_CRUZ_1ST_GEN" = [2.7, 2.7, 0.1]
"KIA_SPORTAGE_5TH_GEN" = [2.6, 2.6, 0.1]
"GENESIS_GV70_1ST_GEN" = [2.42, 2.42, 0.1]
diff --git a/selfdrive/car/values.py b/selfdrive/car/values.py
index bf5d378ab4f58c..f58c87ea6633e7 100644
--- a/selfdrive/car/values.py
+++ b/selfdrive/car/values.py
@@ -1,6 +1,7 @@
from typing import get_args
from openpilot.selfdrive.car.body.values import CAR as BODY
from openpilot.selfdrive.car.chrysler.values import CAR as CHRYSLER
+from openpilot.selfdrive.car.fca_giorgio.values import CAR as FCA_GIORGIO
from openpilot.selfdrive.car.ford.values import CAR as FORD
from openpilot.selfdrive.car.gm.values import CAR as GM
from openpilot.selfdrive.car.honda.values import CAR as HONDA
@@ -13,7 +14,7 @@
from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA
from openpilot.selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN
-Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN
+Platform = BODY | CHRYSLER | FCA_GIORGIO | FORD | GM | HONDA | HYUNDAI | MAZDA | MOCK | NISSAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN
BRANDS = get_args(Platform)
PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in brand}