diff --git a/RELEASES.md b/RELEASES.md
index dd18b4b775..bcd6aa7aa1 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,5 +1,6 @@
Version 0.8.16 (2022-XX-XX)
========================
+* Chevrolet Bolt EUV 2022-23 support thanks to JasonJShuler!
* Hyundai Ioniq 5 2022 support thanks to sunnyhaibin!
* Hyundai Kona Electric 2022 support thanks to sunnyhaibin!
* Subaru Outback 2020-22 support
diff --git a/docs/CARS.md b/docs/CARS.md
index 00022bfb8b..c3278d7066 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -19,7 +19,7 @@ A supported vehicle is one that just works when you install a comma device. Ever
- [](##) - Limited ability to make tighter turns.
-# 200 Supported Cars
+# 201 Supported Cars
|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|
|---|---|---|:---:|:---:|:---:|:---:|
@@ -33,6 +33,7 @@ A supported vehicle is one that just works when you install a comma device. Ever
|Audi|RS3 2018|ACC + Lane Assist|[](##)|[](##)|[](##)|[](##)|
|Audi|S3 2015-17|ACC + Lane Assist|[](##)|[](##)|[](##)|[](##)|
|Cadillac|Escalade ESV 2016[1](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|[](##)|[](##)|[](##)|[](##)|
+|Chevrolet|Bolt EUV 2022-23|All|[](##)|[](##)|[](##)|[](##)|
|Chevrolet|Volt 2017-18[1](#footnotes)|Adaptive Cruise Control|[](##)|[](##)|[](##)|[](##)|
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control|[](##)|[](##)|[](##)|[](##)|
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control|[](##)|[](##)|[](##)|[](##)|
diff --git a/panda b/panda
index 508ee90f8e..5e0dde7b48 160000
--- a/panda
+++ b/panda
@@ -1 +1 @@
-Subproject commit 508ee90f8ecf1ce70fc9d002bbc14b47a50a459a
+Subproject commit 5e0dde7b480a930d3c3a164331c930541e905d71
diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py
index 2567a5e19a..0605ec1f2a 100644
--- a/selfdrive/car/docs_definitions.py
+++ b/selfdrive/car/docs_definitions.py
@@ -10,6 +10,10 @@ from common.conversions import Conversions as CV
GOOD_TORQUE_THRESHOLD = 1.0 # m/s^2
MODEL_YEARS_RE = r"(?<= )((\d{4}-\d{2})|(\d{4}))(,|$)"
+# Makes that lack auto-resume with stock long, and auto resume in any configuration
+NO_AUTO_RESUME_STOCK_LONG = {"toyota", "gm"}
+NO_AUTO_RESUME = NO_AUTO_RESUME_STOCK_LONG | {"nissan", "subaru"}
+
class Tier(Enum):
GOLD = 0
@@ -147,11 +151,11 @@ class CarInfo:
else:
alc = ""
- # Exception for Nissan, Subaru, and stock long Toyota which do not auto-resume yet
+ # Exception for cars which do not auto-resume yet
acc = ""
if self.min_enable_speed > 0:
acc = f" while driving above {self.min_enable_speed * CV.MS_TO_MPH:.0f} mph"
- elif CP.carName not in ("nissan", "subaru", "toyota") or (CP.carName == "toyota" and CP.openpilotLongitudinalControl):
+ elif CP.carName not in NO_AUTO_RESUME or (CP.carName in NO_AUTO_RESUME_STOCK_LONG and CP.openpilotLongitudinalControl):
acc = " that automatically resumes from a stop"
if self.row[Column.STEERING_TORQUE] != Star.FULL:
@@ -210,6 +214,7 @@ class Harness(Enum):
hyundai_p = "Hyundai P"
custom = "Developer"
obd_ii = "OBD-II"
+ gm = "GM"
nissan_a = "Nissan A"
nissan_b = "Nissan B"
mazda = "Mazda"
diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py
index 0e74065143..10e6027973 100644
--- a/selfdrive/car/gm/carcontroller.py
+++ b/selfdrive/car/gm/carcontroller.py
@@ -5,7 +5,7 @@ from common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker
from selfdrive.car import apply_std_steer_torque_limits
from selfdrive.car.gm import gmcan
-from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, EV_CAR
+from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, EV_CAR
VisualAlert = car.CarControl.HUDControl.VisualAlert
NetworkLocation = car.CarParams.NetworkLocation
@@ -19,6 +19,7 @@ class CarController:
self.apply_gas = 0
self.apply_brake = 0
self.frame = 0
+ self.last_button_frame = 0
self.lka_steering_cmd_counter_last = -1
self.lka_icon_status_last = (False, False)
@@ -46,8 +47,7 @@ class CarController:
if CS.lka_steering_cmd_counter != self.lka_steering_cmd_counter_last:
self.lka_steering_cmd_counter_last = CS.lka_steering_cmd_counter
elif (self.frame % self.params.STEER_STEP) == 0:
- lkas_enabled = CC.latActive and CS.out.vEgo > self.params.MIN_STEER_SPEED
- if lkas_enabled:
+ if CC.latActive:
new_steer = int(round(actuators.steer * self.params.STEER_MAX))
apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params)
else:
@@ -58,7 +58,7 @@ class CarController:
# moment of disengaging, increment the counter based on the last message known to pass Panda safety checks.
idx = (CS.lka_steering_cmd_counter + 1) % 4
- can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, lkas_enabled))
+ can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive))
if self.CP.openpilotLongitudinalControl:
# Gas/regen, brakes, and UI commands - all at 25Hz
@@ -107,6 +107,13 @@ class CarController:
if self.CP.networkLocation == NetworkLocation.gateway and self.frame % self.params.ADAS_KEEPALIVE_STEP == 0:
can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN)
+ else:
+ # Stock longitudinal, integrated at camera
+ if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
+ if CC.cruiseControl.cancel:
+ self.last_button_frame = self.frame
+ can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CruiseButtons.CANCEL))
+
# Show green icon when LKA torque is applied, and
# alarming orange icon when approaching torque limit.
# If not sent again, LKA icon disappears in about 5 seconds.
@@ -114,6 +121,8 @@ class CarController:
lka_active = CS.lkas_status == 1
lka_critical = lka_active and abs(actuators.steer) > 0.9
lka_icon_status = (lka_active, lka_critical)
+
+ # SW_GMLAN not yet on cam harness, no HUD alerts
if self.CP.networkLocation != NetworkLocation.fwdCamera and (self.frame % self.params.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last):
steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw)
can_sends.append(gmcan.create_lka_icon_command(CanBus.SW_GMLAN, lka_active, lka_critical, steer_alert))
diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py
index b174d0327e..e89ece198e 100644
--- a/selfdrive/car/gm/carstate.py
+++ b/selfdrive/car/gm/carstate.py
@@ -1,4 +1,5 @@
from cereal import car
+from common.conversions import Conversions as CV
from common.numpy_fast import mean
from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
@@ -6,6 +7,7 @@ from selfdrive.car.interfaces import CarStateBase
from selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD
TransmissionType = car.CarParams.TransmissionType
+NetworkLocation = car.CarParams.NetworkLocation
class CarState(CarStateBase):
@@ -15,7 +17,7 @@ class CarState(CarStateBase):
self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"]
self.lka_steering_cmd_counter = 0
- def update(self, pt_cp, loopback_cp):
+ def update(self, pt_cp, cam_cp, loopback_cp):
ret = car.CarState.new_message()
self.prev_cruise_buttons = self.cruise_buttons
@@ -77,9 +79,21 @@ class CarState(CarStateBase):
ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
+ if self.CP.networkLocation == NetworkLocation.fwdCamera:
+ ret.cruiseState.speed = (cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] / 16) * CV.KPH_TO_MS
return ret
+ @staticmethod
+ def get_cam_can_parser(CP):
+ signals = []
+ checks = []
+ if CP.networkLocation == NetworkLocation.fwdCamera:
+ signals.append(("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"))
+ checks.append(("ASCMActiveCruiseControlStatus", 25))
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.CAMERA)
+
@staticmethod
def get_can_parser(CP):
signals = [
diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py
index 47e6090a03..98265e8c6b 100644
--- a/selfdrive/car/gm/gmcan.py
+++ b/selfdrive/car/gm/gmcan.py
@@ -1,5 +1,9 @@
from selfdrive.car import make_can_msg
+def create_buttons(packer, bus, button):
+ values = {"ACCButtons": button}
+ return packer.make_can_msg("ASCMSteeringButton", bus, values)
+
def create_steering_control(packer, bus, apply_steer, idx, lkas_active):
values = {
diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py
index 10ae670ac8..f3f1e587ca 100755
--- a/selfdrive/car/gm/interface.py
+++ b/selfdrive/car/gm/interface.py
@@ -1,10 +1,11 @@
#!/usr/bin/env python3
from cereal import car
from math import fabs
+from panda import Panda
from common.conversions import Conversions as CV
from selfdrive.car import STD_CARGO_KG, create_button_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
-from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams
+from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR
from selfdrive.car.interfaces import CarInterfaceBase
ButtonType = car.CarState.ButtonEvent.Type
@@ -48,29 +49,36 @@ class CarInterface(CarInterfaceBase):
ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
ret.carName = "gm"
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)]
- ret.pcmCruise = False # For ASCM, stock non-adaptive cruise control is kept off
- ret.radarOffCan = False # For ASCM, radar exists
- ret.transmissionType = TransmissionType.automatic
- # NetworkLocation.gateway: OBD-II harness (typically ASCM), NetworkLocation.fwdCamera: non-ASCM
- ret.networkLocation = NetworkLocation.gateway
+
+ if candidate in EV_CAR:
+ ret.transmissionType = TransmissionType.direct
+ else:
+ ret.transmissionType = TransmissionType.automatic
+
+ if candidate in CAMERA_ACC_CAR:
+ ret.openpilotLongitudinalControl = False
+ ret.networkLocation = NetworkLocation.fwdCamera
+ ret.radarOffCan = True # no radar
+ ret.pcmCruise = True
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM
+ else: # ASCM, OBD-II harness
+ ret.openpilotLongitudinalControl = True
+ ret.networkLocation = NetworkLocation.gateway
+ ret.radarOffCan = False
+ ret.pcmCruise = False # stock non-adaptive cruise control is kept off
# These cars have been put into dashcam only due to both a lack of users and test coverage.
# These cars likely still work fine. Once a user confirms each car works and a test route is
# added to selfdrive/car/tests/routes.py, we can remove it from this list.
ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL}
- # Presence of a camera on the object bus is ok.
- # Have to go to read_only if ASCM is online (ACC-enabled cars),
- # or camera is on powertrain bus (LKA cars without ACC).
- ret.openpilotLongitudinalControl = True
- tire_stiffness_factor = 0.444 # not optimized yet
-
# Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below.
ret.minSteerSpeed = 7 * CV.MPH_TO_MS
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]]
ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
+ tire_stiffness_factor = 0.444 # not optimized yet
ret.longitudinalTuning.kpBP = [5., 35.]
ret.longitudinalTuning.kpV = [2.4, 1.5]
@@ -84,7 +92,6 @@ class CarInterface(CarInterfaceBase):
ret.minEnableSpeed = 18 * CV.MPH_TO_MS
if candidate == CAR.VOLT:
- ret.transmissionType = TransmissionType.direct
ret.mass = 1607. + STD_CARGO_KG
ret.wheelbase = 2.69
ret.steerRatio = 17.7 # Stock 15.7, LiveParameters
@@ -143,6 +150,16 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kf = 0.000045
tire_stiffness_factor = 1.0
+ elif candidate == CAR.BOLT_EUV:
+ ret.minEnableSpeed = -1
+ ret.mass = 1669. + STD_CARGO_KG
+ ret.wheelbase = 2.675
+ ret.steerRatio = 16.8
+ ret.centerToFront = ret.wheelbase * 0.4
+ tire_stiffness_factor = 1.0
+ ret.steerActuatorDelay = 0.2
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
# TODO: get actual value, for now starting with reasonable value for
# civic and scaling by mass and wheelbase
ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
@@ -156,7 +173,7 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState
def _update(self, c):
- ret = self.CS.update(self.cp, self.cp_loopback)
+ ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback)
if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT:
be = create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)
diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py
index 016910a40f..6d34093102 100644
--- a/selfdrive/car/gm/values.py
+++ b/selfdrive/car/gm/values.py
@@ -14,7 +14,6 @@ class CarControllerParams:
STEER_STEP = 2 # Control frames per command (50hz)
STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness
STEER_DELTA_DOWN = 17
- MIN_STEER_SPEED = 3. # m/s
STEER_DRIVER_ALLOWANCE = 50
STEER_DRIVER_MULTIPLIER = 4
STEER_DRIVER_FACTOR = 100
@@ -59,10 +58,7 @@ class CAR:
ACADIA = "GMC ACADIA DENALI 2018"
BUICK_REGAL = "BUICK REGAL ESSENCE 2018"
ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016"
-
-
-EV_CAR = {CAR.VOLT}
-STEER_THRESHOLD = 1.0
+ BOLT_EUV = "CHEVROLET BOLT EUV 2022"
class Footnote(Enum):
@@ -87,6 +83,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = {
CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"),
CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"),
CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"),
+ CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "All", footnotes=[], harness=Harness.gm),
}
@@ -107,6 +104,7 @@ class AccState:
class CanBus:
POWERTRAIN = 0
OBSTACLE = 1
+ CAMERA = 2
CHASSIS = 2
SW_GMLAN = 3
LOOPBACK = 128
@@ -155,6 +153,17 @@ FINGERPRINTS = {
{
309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8
}],
+ CAR.BOLT_EUV: [
+ {
+ 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7
+ }],
}
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'))
+
+EV_CAR = {CAR.VOLT, CAR.BOLT_EUV}
+
+# We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness)
+CAMERA_ACC_CAR = {CAR.BOLT_EUV}
+
+STEER_THRESHOLD = 1.0
diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py
index 2195befb22..0b1726b010 100644
--- a/selfdrive/car/tests/routes.py
+++ b/selfdrive/car/tests/routes.py
@@ -47,6 +47,7 @@ routes = [
TestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL),
TestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV),
TestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT),
+ TestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV),
TestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G),
TestRoute("a74b011b32b51b56|2020-07-26--17-09-36", HONDA.CIVIC),
diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py
index 8906942f26..4a1f1a61ee 100755
--- a/selfdrive/car/tests/test_models.py
+++ b/selfdrive/car/tests/test_models.py
@@ -58,7 +58,6 @@ class TestCarModelBase(unittest.TestCase):
raise unittest.SkipTest
if 'FILTER' in os.environ:
- print(tuple(os.environ.get('FILTER').split(', ')))
if not cls.car_model.startswith(tuple(os.environ.get('FILTER').split(','))):
raise unittest.SkipTest
diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml
index 635743c11a..0a958a9d03 100644
--- a/selfdrive/car/torque_data/override.yaml
+++ b/selfdrive/car/torque_data/override.yaml
@@ -24,6 +24,7 @@ KIA EV6 2022: [3.5, 2.5, 0.0]
RAM 1500 5TH GEN: [2.0, 2.0, 0.0]
RAM HD 5TH GEN: [1.4, 1.4, 0.0]
SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11]
+CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05]
# Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0]