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 - [![star](assets/icon-star-empty.svg)](##) - 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|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| |Audi|S3 2015-17|ACC + Lane Assist|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)| |Cadillac|Escalade ESV 2016[1](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| +|Chevrolet|Bolt EUV 2022-23|All|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| |Chevrolet|Volt 2017-18[1](#footnotes)|Adaptive Cruise Control|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)| 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]