diff --git a/RELEASES.md b/RELEASES.md index 35ed1b9520..51387091b8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -12,6 +12,7 @@ Version 0.8.17 (2022-XX-XX) * Border turns grey while overriding steering * Added button to bookmark events while driving; view them later in comma connect * AGNOS 6 +* Kia Sportage Hybrid 2023 support thanks to sunnyhaibin! Version 0.8.16 (2022-08-26) ======================== diff --git a/cereal b/cereal index 107048c83e..1e3dd70a39 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 107048c83ec2f488286a1be314e7aece0a20a6b1 +Subproject commit 1e3dd70a391bc1bbe437d3eea8be30947f929a75 diff --git a/docs/CARS.md b/docs/CARS.md index 40eef06102..a03da5ca6a 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 208 Supported Cars +# 209 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -18,8 +18,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Cadillac|Escalade ESV 2016[1](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Chevrolet|Silverado 1500 2020-21|Safety Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| +|Chevrolet|Silverado 1500 2020-21|Safety Package II|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Chevrolet|Volt 2017-18[1](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| @@ -32,7 +32,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Genesis|G90 2017-18|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |GMC|Acadia 2018[1](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Honda|Accord 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Accord Hybrid 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| @@ -100,6 +100,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| +|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Telluride 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| diff --git a/panda b/panda index b1ca52f86d..02b74fcfe1 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b1ca52f86d59500c6232df6afac97a51daf7bd51 +Subproject commit 02b74fcfe19cbb000e5fb696e028f6f67690c20c diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 248828e757..e25f203772 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -62,11 +62,14 @@ class CarInterface(CarInterfaceBase): ret.radarOffCan = True # no radar ret.pcmCruise = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM + ret.minEnableSpeed = 5 * CV.KPH_TO_MS 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 + # supports stop and go, but initial engage must (conservatively) be above 18mph + ret.minEnableSpeed = 18 * CV.MPH_TO_MS # 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 @@ -90,9 +93,6 @@ class CarInterface(CarInterfaceBase): ret.steerLimitTimer = 0.4 ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz - # supports stop and go, but initial engage must (conservatively) be above 18mph - ret.minEnableSpeed = 18 * CV.MPH_TO_MS - if candidate == CAR.VOLT: ret.mass = 1607. + STD_CARGO_KG ret.wheelbase = 2.69 @@ -153,7 +153,6 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 1.0 elif candidate in (CAR.BOLT_EV, CAR.BOLT_EUV): - ret.minEnableSpeed = -1 ret.mass = 1669. + STD_CARGO_KG ret.wheelbase = 2.63779 ret.steerRatio = 16.8 @@ -163,7 +162,6 @@ class CarInterface(CarInterfaceBase): CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.SILVERADO: - ret.minEnableSpeed = -1 ret.mass = 2200. + STD_CARGO_KG ret.wheelbase = 3.75 ret.steerRatio = 16.3 @@ -172,7 +170,6 @@ class CarInterface(CarInterfaceBase): CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.EQUINOX: - ret.minEnableSpeed = -1 ret.mass = 3500. * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.72 ret.steerRatio = 14.4 @@ -207,19 +204,16 @@ class CarInterface(CarInterfaceBase): GearShifter.eco, GearShifter.manumatic], pcm_enable=self.CP.pcmCruise) - if ret.vEgo < self.CP.minEnableSpeed: + # Enabling at a standstill with brake is allowed + # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs + if ret.vEgo < self.CP.minEnableSpeed and not (ret.standstill and ret.brake >= 20 and + self.CP.networkLocation == NetworkLocation.fwdCamera): events.add(EventName.belowEngageSpeed) if ret.cruiseState.standstill: events.add(EventName.resumeRequired) if ret.vEgo < self.CP.minSteerSpeed: events.add(EventName.belowSteerSpeed) - if self.CP.networkLocation == NetworkLocation.fwdCamera and self.CP.pcmCruise: - # The ECM has a higher brake pressed threshold than the camera, causing an - # ACC fault when you engage at a stop with your foot partially on the brake - if ret.vEgoRaw < 0.1 and ret.brake < 20: - events.add(EventName.gmAccFaultedTemp) - ret.events = events.to_msg() return ret diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index e48edc42ba..638008934a 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -105,7 +105,6 @@ class Footnote(Enum): @dataclass class HondaCarInfo(CarInfo): package: str = "Honda Sensing" - min_steer_speed: float = 12. * CV.MPH_TO_MS CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { @@ -114,31 +113,31 @@ CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), ], CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", harness=Harness.nidec, video_link="https://youtu.be/-IkImTe1NYE"), + CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec, video_link="https://youtu.be/-IkImTe1NYE"), CAR.CIVIC_BOSCH: [ - HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS, harness=Harness.bosch_a), - HondaCarInfo("Honda Civic Hatchback 2017-21", harness=Harness.bosch_a), + HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), ], CAR.CIVIC_BOSCH_DIESEL: None, # same platform CAR.CIVIC_2022: [ - HondaCarInfo("Honda Civic 2022", "All", min_steer_speed=0., harness=Harness.bosch_b), - HondaCarInfo("Honda Civic Hatchback 2022", "All", min_steer_speed=0., harness=Harness.bosch_b), + HondaCarInfo("Honda Civic 2022", "All", harness=Harness.bosch_b), + HondaCarInfo("Honda Civic Hatchback 2022", "All", harness=Harness.bosch_b), ], CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", harness=Harness.nidec), - CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", harness=Harness.bosch_a), + CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), + CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring - CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", harness=Harness.bosch_a), - CAR.FIT: HondaCarInfo("Honda Fit 2018-20", harness=Harness.nidec), - CAR.FREED: HondaCarInfo("Honda Freed 2020", harness=Harness.nidec), - CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", harness=Harness.nidec), - CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", min_steer_speed=0., harness=Harness.nidec), + CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), + CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), + CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), + CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", harness=Harness.nidec), CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey - CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", harness=Harness.nidec), + CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", harness=Harness.nidec), - CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", harness=Harness.nidec), - CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", harness=Harness.nidec), + CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), + CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), + CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), } diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 6b38297eb9..913f683e2c 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -135,14 +135,17 @@ class CarController: self.last_button_frame = self.frame else: for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.CANCEL)) + can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.CANCEL)) self.last_button_frame = self.frame # cruise standstill resume elif CC.cruiseControl.resume: - if not (self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): + if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: + # TODO: resume for alt button cars + pass + else: for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL)) + can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.RES_ACCEL)) self.last_button_frame = self.frame else: can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, lat_active, diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index f2cbafdcf0..e1478e6f18 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -3,7 +3,7 @@ from selfdrive.car.hyundai.values import HyundaiFlags def get_e_can_bus(CP): # On the CAN-FD platforms, the LKAS camera is on both A-CAN and E-CAN. HDA2 cars - # have a a different harness than the HDA1 and non-HDA variants in order to split + # have a different harness than the HDA1 and non-HDA variants in order to split # a different bus, since the steering is done by different ECUs. return 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 @@ -39,13 +39,15 @@ def create_cam_0x2a4(packer, camera_values): }) return packer.make_can_msg("CAM_0x2a4", 4, camera_values) -def create_buttons(packer, cnt, btn): +def create_buttons(packer, CP, cnt, btn): values = { "COUNTER": cnt, "SET_ME_1": 1, "CRUISE_BUTTONS": btn, } - return packer.make_can_msg("CRUISE_BUTTONS", 5, values) + + bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 6 + return packer.make_can_msg("CRUISE_BUTTONS", bus, values) def create_acc_cancel(packer, CP, cruise_info_copy): values = cruise_info_copy diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 13e93c7d06..b1fa8d9e1d 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -8,6 +8,7 @@ from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu +Ecu = car.CarParams.Ecu ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL) @@ -33,8 +34,8 @@ class CarInterface(CarInterfaceBase): ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30} if candidate in CANFD_CAR: - # detect HDA2 with LKAS message - if 0x50 in fingerprint[6]: + # detect HDA2 with ADAS Driving ECU + if Ecu.adas in [fw.ecu for fw in car_fw]: ret.flags |= HyundaiFlags.CANFD_HDA2.value else: # non-HDA2 @@ -247,6 +248,11 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 16. tire_stiffness_factor = 0.65 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: + ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only + ret.wheelbase = 2.756 + ret.steerRatio = 13.6 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) # Genesis elif candidate == CAR.GENESIS_G70: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index f69273ba55..d5b8427f66 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -36,7 +36,7 @@ class CarControllerParams: # If the max stock LKAS request is <384, add your car to this list. elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV, - CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER): + CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO): self.STEER_MAX = 255 # Default for most HKG @@ -90,6 +90,7 @@ class CAR: KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" KIA_SELTOS = "KIA SELTOS 2021" KIA_SORENTO = "KIA SORENTO GT LINE 2018" + KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" KIA_CEED = "KIA CEED INTRO ED 2019" KIA_EV6 = "KIA EV6 2022" @@ -172,6 +173,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), ], + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), CAR.KIA_EV6: [ @@ -304,7 +306,7 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdRadar], + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar], bus=4, ), Request( @@ -1367,6 +1369,14 @@ FW_VERSIONS = { b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', ], }, + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', + ], + }, } CHECKSUM = { @@ -1384,12 +1394,12 @@ FEATURES = { "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } -HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN} # these cars use a different gas signal +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN} # these cars use a different gas signal EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5} # these cars require a special panda safety mode due to missing counters and checksums in the messages @@ -1442,4 +1452,5 @@ DBC = { CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 85787ae88d..8c4efe3061 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -91,13 +91,14 @@ routes = [ CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4), CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0), CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), + CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_HYBRID_5TH_GEN), CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), - CarTestRoute("22de8111a8c5463c|2022-07-29--13-34-49", HYUNDAI.IONIQ_5), + CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2 CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019), CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV), CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020), @@ -110,7 +111,7 @@ routes = [ CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), - CarTestRoute("d824e27e8c60172c|2022-05-19--16-15-28", HYUNDAI.KIA_EV6), # HDA2 + CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2 CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1 CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021), CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index ed323b0563..b9926301f1 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -45,6 +45,7 @@ class TestFwFingerprint(unittest.TestCase): self.assertFalse(len(duplicates), f"{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}") def test_data_collection_ecus(self): + # Asserts no extra ECUs are in the fingerprinting database for brand, config in FW_QUERY_CONFIGS.items(): for car_model, ecus in VERSIONS[brand].items(): bad_ecus = set(ecus).intersection(config.extra_ecus) @@ -80,10 +81,11 @@ class TestFwFingerprint(unittest.TestCase): def test_fw_request_ecu_whitelist(self): for brand, config in FW_QUERY_CONFIGS.items(): with self.subTest(brand=brand): - whitelisted_ecus = set([ecu for r in config.requests for ecu in r.whitelist_ecus]) - brand_ecus = set([fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw]) + whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus} + brand_ecus = {fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw} + brand_ecus |= {ecu[0] for ecu in config.extra_ecus} - # each ecu in brand's fw versions needs to be whitelisted at least once + # each ecu in brand's fw versions + extra ecus needs to be whitelisted at least once ecus_not_whitelisted = brand_ecus - whitelisted_ecus ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted]) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 889eeffb25..13a0dae7a7 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -32,6 +32,7 @@ CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0] +KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 5bfe89b31c..44426620e9 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -811,7 +811,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.NO_ENTRY: NoEntryAlert("Cruise Faulted"), }, - EventName.gmAccFaultedTemp: { + EventName.accFaultedTemp: { ET.NO_ENTRY: NoEntryAlert("Cruise Temporarily Faulted"), }, diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 7d656b55a9..51676086ba 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -17,7 +17,7 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY # friction in the steering wheel that needs to be overcome to # move it at all, this is compensated for too. -LOW_SPEED_FACTOR = 100 +LOW_SPEED_FACTOR = 200 class LatControlTorque(LatControl): diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 9fbfd4a11f..29137defd3 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -22,8 +22,7 @@ LATERAL_JERK_COST = 0.05 # TODO this cost should be lowered when low # speed lateral control is stable on all cars STEERING_RATE_COST = 800.0 - -MIN_SPEED = .3 +MIN_SPEED = 1.5 class LateralPlanner: diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 998bf4e756..b0613393ad 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -6bb7d8baae51d88dd61f0baf561e386664ddd266 \ No newline at end of file +a87455caf93e91fae0f3704aa476e0732d066b77 \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 683387dce8..cecabd8a3a 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -18,7 +18,7 @@ from tools.lib.logreader import LogReader source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA - ("HYUNDAI2", "d824e27e8c60172c|2022-09-13--11-26-50--2"), # HYUNDAI.KIA_EV6 + ("HYUNDAI2", "d545129f3ca90f28|2022-10-19--09-22-54--9"), # HYUNDAI.KIA_EV6 ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 @@ -40,7 +40,7 @@ source_segments = [ segments = [ ("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"), ("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--0"), - ("HYUNDAI2", "regenFA8B5CA9840|2022-10-12--21-47-06--0"), + ("HYUNDAI2", "d545129f3ca90f28|2022-10-19--09-22-54--9"), ("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"), ("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"), ("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"), diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 201ffb745a..a2b971999c 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 +from functools import lru_cache import sys import subprocess +from tqdm import tqdm from azure.storage.blob import BlockBlobService # pylint: disable=import-error from selfdrive.car.tests.routes import routes as test_car_models_routes @@ -10,11 +12,11 @@ from xx.chffr.lib.storage import _DATA_ACCOUNT_PRODUCTION, _DATA_ACCOUNT_CI, _DA SOURCES = [ (_DATA_ACCOUNT_PRODUCTION, _DATA_BUCKET_PRODUCTION), - (_DATA_ACCOUNT_PRODUCTION, "preserve"), (_DATA_ACCOUNT_CI, "commadataci"), ] +@lru_cache def get_azure_keys(): dest_key = azureutil.get_user_token(_DATA_ACCOUNT_CI, "openpilotci") source_keys = [azureutil.get_user_token(account, bucket) for account, bucket in SOURCES] @@ -82,7 +84,7 @@ if __name__ == "__main__": to_sync.extend([rt.route for rt in test_car_models_routes]) to_sync.extend([s[1].rsplit('--', 1)[0] for s in replay_segments]) - for r in to_sync: + for r in tqdm(to_sync): if not sync_to_ci_public(r): failed_routes.append(r) diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 88ffab2717..d7a552eabb 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -3,3 +3,5 @@ moc_* _cabana settings +car_fingerprint_to_dbc.json + diff --git a/tools/cabana/README.md b/tools/cabana/README.md index f64e6b2d2d..99d0d4c9ce 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -6,4 +6,19 @@ Cabana is a tool developed to view raw CAN data. One use for this is creating an ## Usage Instructions +```bash +$ ./cabana -h +Usage: ./_cabana [options] route + +Options: + -h, --help Displays this help. + --demo use a demo route instead of providing your own + --qcam load qcamera + --data_dir local directory with routes + +Arguments: + route the drive to replay. find your drives at + connect.comma.ai +``` + See [openpilot wiki](https://github.com/commaai/openpilot/wiki/Cabana) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index fd44ecd138..d791466ce7 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -12,5 +12,6 @@ else: qt_libs = ['qt_util', 'Qt5Charts'] + base_libs cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs +qt_env.Execute('./generate_dbc_json.py --out car_fingerprint_to_dbc.json') qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', 'canmessages.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 71175e783e..168edc75f4 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -1,8 +1,11 @@ #include "tools/cabana/binaryview.h" #include +#include #include +#include #include +#include #include "tools/cabana/canmessages.h" @@ -18,6 +21,7 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { horizontalHeader()->hide(); verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setMouseTracking(true); // replace selection model auto old_model = selectionModel(); @@ -29,6 +33,24 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { }); } +void BinaryView::highlight(const Signal *sig) { + if (sig != hovered_sig) { + hovered_sig = sig; + model->dataChanged(model->index(0, 0), model->index(model->rowCount() - 1, model->columnCount() - 1)); + emit signalHovered(hovered_sig); + } +} + +void BinaryView::mouseMoveEvent(QMouseEvent *event) { + if (auto index = indexAt(event->pos()); index.isValid()) { + auto item = (BinaryViewModel::Item *)index.internalPointer(); + highlight(item->sig); + if (item->sig) + QToolTip::showText(event->globalPos(), item->sig->name.c_str(), this, rect()); + } + QTableView::mouseMoveEvent(event); +} + void BinaryView::mouseReleaseEvent(QMouseEvent *event) { QTableView::mouseReleaseEvent(event); @@ -39,6 +61,11 @@ void BinaryView::mouseReleaseEvent(QMouseEvent *event) { } } +void BinaryView::leaveEvent(QEvent *event) { + highlight(nullptr); + QTableView::leaveEvent(event); +} + void BinaryView::setMessage(const QString &message_id) { msg_id = message_id; model->setMessage(message_id); @@ -79,7 +106,8 @@ void BinaryViewModel::setMessage(const QString &message_id) { } else if (j == end) { sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; } - items[idx].bg_color = QColor(getColor(i)); + items[idx].bg_color = getColor(i); + items[idx].sig = &dbc_msg->sigs[i]; } } } @@ -157,7 +185,8 @@ void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectio BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { // cache fonts and color small_font.setPointSize(6); - bold_font.setBold(true); + hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + hex_font.setBold(true); highlight_color = QApplication::style()->standardPalette().color(QPalette::Active, QPalette::Highlight); } @@ -168,11 +197,19 @@ QSize BinaryItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto item = (const BinaryViewModel::Item *)index.internalPointer(); + BinaryView *bin_view = (BinaryView *)parent(); painter->save(); - // TODO: highlight signal cells on mouse over - painter->fillRect(option.rect, option.state & QStyle::State_Selected ? highlight_color : item->bg_color); + + // background + QColor bg_color = item->sig && bin_view->hoveredSignal() == item->sig ? hoverColor(item->bg_color) : item->bg_color; + if (option.state & QStyle::State_Selected) { + bg_color = highlight_color; + } + painter->fillRect(option.rect, bg_color); + + // text if (index.column() == 8) { - painter->setFont(bold_font); + painter->setFont(hex_font); } painter->drawText(option.rect, Qt::AlignCenter, item->val); if (item->is_msb || item->is_lsb) { diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 631797ca48..48eb5eff8a 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -14,7 +14,7 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: - QFont small_font, bold_font; + QFont small_font, hex_font; QColor highlight_color; }; @@ -37,6 +37,7 @@ public: bool is_msb = false; bool is_lsb = false; QString val = "0"; + const Signal *sig = nullptr; }; private: @@ -59,14 +60,21 @@ class BinaryView : public QTableView { public: BinaryView(QWidget *parent = nullptr); - void mouseReleaseEvent(QMouseEvent *event) override; void setMessage(const QString &message_id); void updateState(); + void highlight(const Signal *sig); + const Signal *hoveredSignal() const { return hovered_sig; } signals: void cellsSelected(int start_bit, int size); + void signalHovered(const Signal *sig); private: + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; + QString msg_id; BinaryViewModel *model; + const Signal *hovered_sig = nullptr; }; diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index 252a8c680c..897381b3f2 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -3,6 +3,8 @@ #include #include +#include "tools/cabana/dbcmanager.h" + Q_DECLARE_METATYPE(std::vector); Settings settings; @@ -38,6 +40,33 @@ bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool return false; } +QList CANMessages::findSignalValues(const QString &id, const Signal *signal, double value, FindFlags flag, int max_count) { + auto evts = events(); + if (!evts) return {}; + + auto l = id.split(':'); + int bus = l[0].toInt(); + uint32_t address = l[1].toUInt(nullptr, 16); + + QList ret; + ret.reserve(max_count); + for (auto &evt : *evts) { + if (evt->which != cereal::Event::Which::CAN) continue; + + for (auto c : evt->event.getCan()) { + if (bus == c.getSrc() && address == c.getAddress()) { + double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), *signal); + if ((flag == EQ && val == value) || (flag == LT && val < value) || (flag == GT && val > value)) { + ret.push_back({(evt->mono_time / (double)1e9) - can->routeStartTime(), val}); + } + if (ret.size() >= max_count) + return ret; + } + } + } + return ret; +} + void CANMessages::process(QHash> *messages) { for (auto it = messages->begin(); it != messages->end(); ++it) { ++counters[it.key()]; diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index 58f5ad70b7..a468fb2956 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -4,8 +4,11 @@ #include #include +#include #include +#include +#include "opendbc/can/common_dbc.h" #include "tools/replay/replay.h" class Settings : public QObject { @@ -35,12 +38,14 @@ class CANMessages : public QObject { Q_OBJECT public: + enum FindFlags{ EQ, LT, GT }; CANMessages(QObject *parent); ~CANMessages(); bool loadRoute(const QString &route, const QString &data_dir, bool use_qcam); void seekTo(double ts); void resetRange(); void setRange(double min, double max); + QList findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count); bool eventFilter(const Event *event); inline std::pair range() const { return {begin_sec, end_sec}; } @@ -99,6 +104,12 @@ inline const QString &getColor(int i) { return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; } +inline QColor hoverColor(const QColor &color) { + QColor c = color.convertTo(QColor::Hsv); + c.setHsv(color.hue(), 180, 180); + return c; +} + // A global pointer referring to the unique CANMessages object extern CANMessages *can; extern Settings settings; diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 60d0632d4c..1f1b73638b 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -29,6 +29,17 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { title_layout->addWidget(edit_btn); main_layout->addLayout(title_layout); + // warning + warning_widget = new QWidget(this); + QHBoxLayout *warning_hlayout = new QHBoxLayout(warning_widget); + QLabel *warning_icon = new QLabel(this); + warning_icon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap({16, 16})); + warning_hlayout->addWidget(warning_icon); + warning_label = new QLabel(this); + warning_hlayout->addWidget(warning_label,1, Qt::AlignLeft); + warning_widget->hide(); + main_layout->addWidget(warning_widget); + // binary view binary_view = new BinaryView(this); main_layout->addWidget(binary_view, 0, Qt::AlignTop); @@ -64,6 +75,7 @@ void DetailWidget::setMessage(const QString &message_id) { void DetailWidget::dbcMsgChanged() { if (msg_id.isEmpty()) return; + warning_widget->hide(); qDeleteAll(signals_container->findChildren()); QString msg_name = tr("untitled"); if (auto msg = dbc()->msg(msg_id)) { @@ -74,8 +86,14 @@ void DetailWidget::dbcMsgChanged() { QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); + QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight); + QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered); } msg_name = msg->name.c_str(); + if (msg->size != can->lastMessage(msg_id).dat.size()) { + warning_label->setText(tr("Message size (%1) is incorrect!").arg(msg->size)); + warning_widget->show(); + } } edit_btn->setVisible(true); name_label->setText(msg_name); diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 9d3b81dcb0..07c78caa4a 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -46,7 +46,8 @@ private: void updateState(); QString msg_id; - QLabel *name_label, *time_label; + QLabel *name_label, *time_label, *warning_label; + QWidget *warning_widget; QPushButton *edit_btn; QWidget *signals_container; HistoryLog *history_log; diff --git a/tools/cabana/generate_dbc_json.py b/tools/cabana/generate_dbc_json.py new file mode 100755 index 0000000000..cb122e2eb2 --- /dev/null +++ b/tools/cabana/generate_dbc_json.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import argparse +import json + +from selfdrive.car.car_helpers import get_interface_attr + + +def generate_dbc_json() -> str: + all_cars_by_brand = get_interface_attr("CAR_INFO") + all_dbcs_by_brand = get_interface_attr("DBC") + dbc_map = {car: all_dbcs_by_brand[brand][car]['pt'] for brand, cars in all_cars_by_brand.items() for car in cars if car != 'mock'} + return json.dumps(dict(sorted(dbc_map.items())), indent=2) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate mapping for all car fingerprints to DBC names and outputs json file", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument("--out", required=True, help="Generated json filepath") + args = parser.parse_args() + + with open(args.out, 'w') as f: + f.write(generate_dbc_json()) + print(f"Generated and written to {args.out}") diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index c0efbca280..a20845f15f 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -70,7 +70,7 @@ HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) { table = new QTableView(this); table->setModel(model); table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); table->setColumnWidth(0, 60); table->verticalHeader()->setVisible(false); table->setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 28f79adad3..dfe665d750 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -42,6 +42,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { // message filter QLineEdit *filter = new QLineEdit(this); + filter->setClearButtonEnabled(true); filter->setPlaceholderText(tr("filter messages")); main_layout->addWidget(filter); @@ -67,9 +68,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { // signals/slots QObject::connect(filter, &QLineEdit::textChanged, proxy_model, &QSortFilterProxyModel::setFilterFixedString); + QObject::connect(can, &CANMessages::eventsMerged, this, &MessagesWidget::loadDBCFromFingerprint); QObject::connect(can, &CANMessages::updated, model, &MessageListModel::updateState); - QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(dbcSelectionChanged(const QString &))); - QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadFromPaste); + QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &))); + QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste); QObject::connect(save_btn, &QPushButton::clicked, [=]() { // TODO: save DBC to file }); @@ -79,17 +81,20 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { } }); - // For test purpose - dbc_combo->setCurrentText("toyota_nodsu_pt_generated"); + QFile json_file("./car_fingerprint_to_dbc.json"); + if(json_file.open(QIODevice::ReadOnly)) { + fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); + } } -void MessagesWidget::dbcSelectionChanged(const QString &dbc_file) { - dbc()->open(dbc_file); - // TODO: reset model? - table_widget->sortByColumn(0, Qt::AscendingOrder); +void MessagesWidget::loadDBCFromName(const QString &name) { + dbc()->open(name); + dbc_combo->setCurrentText(name); + // refresh model + model->updateState(); } -void MessagesWidget::loadFromPaste() { +void MessagesWidget::loadDBCFromPaste() { LoadDBCDialog dlg(this); if (dlg.exec()) { dbc()->open("from paste", dlg.dbc_edit->toPlainText()); @@ -97,6 +102,16 @@ void MessagesWidget::loadFromPaste() { } } +void MessagesWidget::loadDBCFromFingerprint() { + auto fingerprint = can->carFingerprint(); + if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) { + auto dbc_name = fingerprint_to_dbc[fingerprint]; + if (dbc_name != QJsonValue::Undefined) { + loadDBCFromName(dbc_name.toString()); + } + } +} + // MessageListModel QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 1184772f3b..bef15c2cf7 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -38,8 +39,9 @@ public: MessagesWidget(QWidget *parent); public slots: - void dbcSelectionChanged(const QString &dbc_file); - void loadFromPaste(); + void loadDBCFromName(const QString &name); + void loadDBCFromFingerprint(); + void loadDBCFromPaste(); signals: void msgSelectionChanged(const QString &message_id); @@ -48,4 +50,5 @@ protected: QTableView *table_widget; QComboBox *dbc_combo; MessageListModel *model; + QJsonDocument fingerprint_to_dbc; }; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 7cace48402..d5dccf0e6d 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -3,8 +3,12 @@ #include #include #include +#include +#include #include +#include "selfdrive/ui/qt/util.h" + // SignalForm SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start_bit), QWidget(parent) { @@ -78,7 +82,7 @@ Signal SignalForm::getSignal() { // SignalEdit SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent) - : sig_name(sig.name.c_str()), QWidget(parent) { + : sig(&sig), form_idx(index), sig_name(sig.name.c_str()), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); @@ -93,6 +97,12 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWid title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index))); title_layout->addWidget(title, 1); + QPushButton *seek_btn = new QPushButton("⌕"); + seek_btn->setStyleSheet("font-weight:bold;font-size:20px"); + seek_btn->setToolTip(tr("Find signal values")); + seek_btn->setFixedSize(20, 20); + title_layout->addWidget(seek_btn); + QPushButton *plot_btn = new QPushButton("📈"); plot_btn->setToolTip(tr("Show Plot")); plot_btn->setFixedSize(20, 20); @@ -124,13 +134,17 @@ SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWid main_layout->addWidget(hline); QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); + QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); QObject::connect(save_btn, &QPushButton::clicked, [=]() { QString new_name = form->getSignal().name.c_str(); title->setText(QString("%1. %2").arg(index + 1).arg(new_name)); emit save(); sig_name = new_name; }); - QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); + QObject::connect(seek_btn, &QPushButton::clicked, [this, msg_id, s = &sig]() { + SignalFindDlg dlg(msg_id, s, this); + dlg.exec(); + }); } void SignalEdit::setFormVisible(bool visible) { @@ -138,6 +152,21 @@ void SignalEdit::setFormVisible(bool visible) { icon->setText(visible ? "▼" : ">"); } +void SignalEdit::signalHovered(const Signal *s) { + auto color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx)); + title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color.name())); +} + +void SignalEdit::enterEvent(QEvent *event) { + emit highlight(sig); + QWidget::enterEvent(event); +} + +void SignalEdit::leaveEvent(QEvent *event) { + emit highlight(nullptr); + QWidget::leaveEvent(event); +} + // AddSignalDialog AddSignalDialog::AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent) : QDialog(parent) { @@ -159,3 +188,66 @@ AddSignalDialog::AddSignalDialog(const QString &id, int start_bit, int size, QWi connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); } + +SignalFindDlg::SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Find signal values")); + QVBoxLayout *main_layout = new QVBoxLayout(this); + + QHBoxLayout *h = new QHBoxLayout(); + h->addWidget(new QLabel(signal->name.c_str())); + QComboBox *comp_box = new QComboBox(); + comp_box->addItems({">", "=", "<"}); + h->addWidget(comp_box); + QLineEdit *value_edit = new QLineEdit("0", this); + value_edit->setValidator( new QDoubleValidator(-500000, 500000, 6, this) ); + h->addWidget(value_edit, 1); + QPushButton *search_btn = new QPushButton(tr("Find"), this); + h->addWidget(search_btn); + main_layout->addLayout(h); + + QWidget *container = new QWidget(this); + QVBoxLayout *signals_layout = new QVBoxLayout(container); + QScrollArea *scroll = new QScrollArea(this); + scroll->setWidget(container); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + main_layout->addWidget(scroll); + + QObject::connect(search_btn, &QPushButton::clicked, [=]() { + clearLayout(signals_layout); + + CANMessages::FindFlags comp = CANMessages::EQ; + if (comp_box->currentIndex() == 0) { + comp = CANMessages::GT; + } else if (comp_box->currentIndex() == 2) { + comp = CANMessages::LT; + } + double value = value_edit->text().toDouble(); + + const int limit_results = 50; + auto values = can->findSignalValues(id, signal, value, comp, limit_results); + for (auto &v : values) { + QHBoxLayout *item_layout = new QHBoxLayout(); + item_layout->addWidget(new QLabel(QString::number(v.x(), 'f', 2))); + item_layout->addWidget(new QLabel(QString::number(v.y()))); + item_layout->addStretch(1); + + QPushButton *goto_btn = new QPushButton(tr("Goto"), this); + QObject::connect(goto_btn, &QPushButton::clicked, [sec = v.x()]() { can->seekTo(sec); }); + item_layout->addWidget(goto_btn); + signals_layout->addLayout(item_layout); + } + if (values.size() == limit_results) { + QFrame *hline = new QFrame(); + hline->setFrameShape(QFrame::HLine); + hline->setFrameShadow(QFrame::Sunken); + signals_layout->addWidget(hline); + QLabel *info = new QLabel(tr("Only display the first %1 results").arg(limit_results)); + info->setAlignment(Qt::AlignCenter); + signals_layout->addWidget(info); + } + if (values.size() * 30 > container->height()) { + scroll->setFixedHeight(std::min(values.size() * 30, 300)); + } + }); +} diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index f31408657f..1213b3ec51 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -30,17 +30,24 @@ class SignalEdit : public QWidget { public: SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent = nullptr); void setFormVisible(bool show); + void signalHovered(const Signal *sig); inline bool isFormVisible() const { return form_container->isVisible(); } QString sig_name; SignalForm *form; + int form_idx = 0; + const Signal *sig = nullptr; signals: + void highlight(const Signal *sig); void showChart(); void showFormClicked(); void remove(); void save(); protected: + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + ElidedLabel *title; QWidget *form_container; QLabel *icon; @@ -51,3 +58,10 @@ public: AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent); SignalForm *form; }; + +class SignalFindDlg : public QDialog { + Q_OBJECT + +public: + SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent); +};