from cereal import car, custom, log import cereal.messaging as messaging from opendbc.car import DT_CTRL, structs from opendbc.car.chrysler.values import RAM_DT from opendbc.car.gm.values import CAR as GM_CAR, GMFlags, SDGM_CAR from opendbc.car.interfaces import MAX_CTRL_SPEED from openpilot.selfdrive.selfdrived.events import Events ButtonType = structs.CarState.ButtonEvent.Type GearShifter = structs.CarState.GearShifter EventName = log.OnroadEvent.EventName NetworkLocation = structs.CarParams.NetworkLocation # TODO: the goal is to abstract this file into the CarState struct and make events generic class MockCarState: def __init__(self): self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) def update(self, CS: car.CarState, FPCS: custom.StarPilotCarState): self.sm.update(0) gps_sock = 'gpsLocationExternal' if self.sm.recv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' CS.vEgo = self.sm[gps_sock].speed CS.vEgoRaw = self.sm[gps_sock].speed return CS, FPCS BRAND_EXTRA_GEARS = { 'ford': [GearShifter.low, GearShifter.manumatic], 'nissan': [GearShifter.brake], 'chrysler': [GearShifter.low], 'honda': [GearShifter.sport], 'toyota': [GearShifter.sport], 'gm': [GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], 'volkswagen': [GearShifter.eco, GearShifter.sport, GearShifter.manumatic], 'hyundai': [GearShifter.sport, GearShifter.manumatic] } GM_STANDSTILL_BRAKE_CAMERA_CARS = { GM_CAR.CHEVROLET_VOLT, GM_CAR.CHEVROLET_VOLT_2019, GM_CAR.CHEVROLET_VOLT_ASCM, GM_CAR.CHEVROLET_VOLT_CAMERA, GM_CAR.CHEVROLET_VOLT_CC, GM_CAR.CHEVROLET_MALIBU, GM_CAR.CHEVROLET_MALIBU_ASCM, GM_CAR.BUICK_LACROSSE_ASCM, GM_CAR.CHEVROLET_MALIBU_SDGM, GM_CAR.CHEVROLET_MALIBU_CC, GM_CAR.CHEVROLET_MALIBU_HYBRID_CC, GM_CAR.CHEVROLET_BLAZER, GM_CAR.CHEVROLET_TRAVERSE, } class CarSpecificEvents: def __init__(self, CP: structs.CarParams): self.CP = CP self.steering_unpressed = 0 self.low_speed_alert = False self.gm_low_speed_alert_shown = False self.no_steer_warning = False self.silent_steer_warning = True def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): extra_gears = BRAND_EXTRA_GEARS.get(self.CP.brand, None) if self.CP.brand in ('body', 'mock'): events = Events() elif self.CP.brand == 'chrysler': events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) # Low speed steer alert hysteresis logic if self.CP.carFingerprint in RAM_DT: if CS.vEgo >= self.CP.minEnableSpeed: self.low_speed_alert = False if (self.CP.minEnableSpeed >= 14.5) and (CS.gearShifter != GearShifter.drive): self.low_speed_alert = True else: if self.CP.minSteerSpeed > 0. and CS.vEgo < (self.CP.minSteerSpeed + 0.5): self.low_speed_alert = True elif CS.vEgo > (self.CP.minSteerSpeed + 1.): self.low_speed_alert = False if self.low_speed_alert: events.add(EventName.belowSteerSpeed) elif self.CP.brand == 'honda': events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=False) if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) if self.CP.pcmCruise: # we engage when pcm is active (rising edge) if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled: events.add(EventName.pcmEnable) elif not CS.cruiseState.enabled and (CC.actuators.accel >= 0. or not self.CP.openpilotLongitudinalControl): # it can happen that car cruise disables while comma system is enabled: need to # keep braking if needed or if the speed is very low if CS.vEgo < self.CP.minEnableSpeed + 2.: # non loud alert if cruise disables below 25mph as expected (+ a little margin) events.add(EventName.speedTooLow) else: events.add(EventName.cruiseDisabled) if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001: events.add(EventName.manualRestart) elif self.CP.brand == 'toyota': # TODO: when we check for unexpected disengagement, check gear not S1, S2, S3 events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) if self.CP.openpilotLongitudinalControl: # Only can leave standstill when planner wants to move if CS.cruiseState.standstill and not CS.brakePressed and CC.cruiseControl.resume: events.add(EventName.resumeRequired) if CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) if CC.actuators.accel > 0.3: # some margin on the actuator to not false trigger cancellation while stopping events.add(EventName.speedTooLow) if CS.vEgo < 0.001: # while in standstill, send a user alert events.add(EventName.manualRestart) elif self.CP.brand == 'gm': events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise, suppress_low_speed_alert=True) # Show the low-speed steer alert once per drive when speed dips below min steer speed. crossed_below_min_steer_speed = ( self.CP.minSteerSpeed > 0. and CS_prev.vEgo >= self.CP.minSteerSpeed and CS.vEgo < self.CP.minSteerSpeed ) if CS.lowSpeedAlert and crossed_below_min_steer_speed and not self.gm_low_speed_alert_shown: events.add(EventName.belowSteerSpeed) self.gm_low_speed_alert_shown = True # Most camera-ACC cars can engage below 5 kph only when stopped with brake applied; # SDGM remains narrower. standstill_brake_enable_allowed = ( CS.standstill and CS.brake >= 20 and self.CP.networkLocation == NetworkLocation.fwdCamera and (self.CP.carFingerprint in GM_STANDSTILL_BRAKE_CAMERA_CARS or self.CP.carFingerprint not in SDGM_CAR) ) below_min_enable_speed = CS.vEgo < self.CP.minEnableSpeed or getattr(CS, "moving_backward", False) if below_min_enable_speed and not standstill_brake_enable_allowed: events.add(EventName.belowEngageSpeed) if CS.cruiseState.standstill and not self.CP.autoResumeSng: events.add(EventName.resumeRequired) # Preserve the prior cycle's cruise-enabled state for low-speed disengage behavior. if ((self.CP.flags & GMFlags.CC_LONG) and CS.vEgo < self.CP.minEnableSpeed and (CS.cruiseState.enabled or CS_prev.cruiseState.enabled)): events.add(EventName.speedTooLow) elif self.CP.brand == 'volkswagen': events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise) if self.CP.openpilotLongitudinalControl: if CS.vEgo < self.CP.minEnableSpeed + 0.5: events.add(EventName.belowEngageSpeed) if CC.enabled and CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) # TODO: this needs to be implemented generically in carState struct # if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined] # events.add(EventName.steerTimeLimit) elif self.CP.brand == 'tesla': events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) if self.CP.carFingerprint == "TESLA_MODEL_S_PREAP": if self.CP.pcmCruise: if getattr(CS, 'teslaCCEngaged', False): events.add(EventName.teslaCCEngaged) if getattr(CS, 'teslaCCDisengaged', False): events.add(EventName.teslaCCDisengaged) if getattr(CS, 'teslaCCNotArmed', False): events.add(EventName.teslaCCNotArmed) else: from opendbc.car.tesla.preap.nap_conf import nap_conf if not nap_conf.pedal_calibrated: events.add(EventName.pedalNotCalibrated) elif self.CP.brand == 'hyundai': events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise, allow_button_cancel=False) else: events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) return events def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: list | None = None, pcm_enable=True, allow_button_cancel=True, suppress_low_speed_alert=False): events = Events() preap_software_cruise = (self.CP.brand == "tesla" and self.CP.carFingerprint == "TESLA_MODEL_S_PREAP" and self.CP.openpilotLongitudinalControl and not self.CP.pcmCruise) pcm_enable = pcm_enable or preap_software_cruise if CS.doorOpen: events.add(EventName.doorOpen) if CS.seatbeltUnlatched: events.add(EventName.seatbeltNotLatched) if CS.gearShifter != GearShifter.drive and (extra_gears is None or CS.gearShifter not in extra_gears): events.add(EventName.wrongGear) if CS.gearShifter == GearShifter.reverse: events.add(EventName.reverseGear) if not CS.cruiseState.available: events.add(EventName.wrongCarMode) if CS.espDisabled: events.add(EventName.espDisabled) if CS.espActive: events.add(EventName.espActive) if CS.stockFcw: events.add(EventName.stockFcw) if CS.stockAeb: events.add(EventName.stockAeb) if CS.vEgo > MAX_CTRL_SPEED: events.add(EventName.speedTooHigh) if CS.cruiseState.nonAdaptive: events.add(EventName.wrongCruiseMode) if CS.brakeHoldActive and self.CP.openpilotLongitudinalControl: events.add(EventName.brakeHold) if CS.parkingBrake: events.add(EventName.parkBrake) if CS.accFaulted: events.add(EventName.accFaulted) if CS.steeringPressed: events.add(EventName.steerOverride) if CS.steeringDisengage and not CS_prev.steeringDisengage: events.add(EventName.steerDisengage) if CS.brakePressed and CS.standstill: events.add(EventName.preEnableStandstill) if CS.gasPressed: events.add(EventName.gasPressedOverride) if CS.vehicleSensorsInvalid: events.add(EventName.vehicleSensorsInvalid) if CS.invalidLkasSetting: events.add(EventName.invalidLkasSetting) if CS.lowSpeedAlert and not suppress_low_speed_alert: events.add(EventName.belowSteerSpeed) if CS.buttonEnable: events.add(EventName.buttonEnable) # Handle cancel button presses for b in CS.buttonEvents: # Disable on rising and falling edge of cancel for both stock and OP long # TODO: only check the cancel button with openpilot longitudinal on all brands to match panda safety if b.type == ButtonType.cancel and (allow_button_cancel or not self.CP.pcmCruise): events.add(EventName.buttonCancel) # Handle permanent and temporary steering faults self.steering_unpressed = 0 if CS.steeringPressed else self.steering_unpressed + 1 if CS.steerFaultTemporary: if CS.steeringPressed and (not CS_prev.steerFaultTemporary or self.no_steer_warning): self.no_steer_warning = True else: self.no_steer_warning = False # if the user overrode recently, show a less harsh alert if self.silent_steer_warning or CS.standstill or self.steering_unpressed < int(1.5 / DT_CTRL): self.silent_steer_warning = True events.add(EventName.steerTempUnavailableSilent) else: events.add(EventName.steerTempUnavailable) else: self.no_steer_warning = False self.silent_steer_warning = False if CS.steerFaultPermanent: events.add(EventName.steerUnavailable) # we engage when pcm is active (rising edge) # enabling can optionally be blocked by the car interface if pcm_enable: if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled and not CS.blockPcmEnable: events.add(EventName.pcmEnable) elif not CS.cruiseState.enabled: events.add(EventName.pcmDisable) return events