import copy from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from opendbc.car import Bus, structs from opendbc.car.common.conversions import Conversions as CV from opendbc.car.interfaces import CarStateBase from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD ButtonType = structs.CarState.ButtonEvent.Type class CarState(CarStateBase): def __init__(self, CP): super().__init__(CP) self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party]) self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] self.hands_on_level = 0 self.das_control = None def update(self, can_parsers) -> structs.CarState: cp_party = can_parsers[Bus.party] cp_ap_party = can_parsers[Bus.ap_party] ret = structs.CarState() # Vehicle speed ret.vEgoRaw = cp_party.vl["DI_speed"]["DI_vehicleSpeed"] * CV.KPH_TO_MS ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) # Gas pedal pedal_status = cp_party.vl["DI_systemStatus"]["DI_accelPedalPos"] ret.gas = pedal_status / 100.0 ret.gasPressed = pedal_status > 0 # Brake pedal ret.brake = 0 ret.brakePressed = cp_party.vl["IBST_status"]["IBST_driverBrakeApply"] == 2 # Steering wheel epas_status = cp_party.vl["EPAS3S_sysStatus"] self.hands_on_level = epas_status["EPAS3S_handsOnLevel"] ret.steeringAngleDeg = -epas_status["EPAS3S_internalSAS"] ret.steeringRateDeg = -cp_ap_party.vl["SCCM_steeringAngleSensor"]["SCCM_steeringAngleSpeed"] ret.steeringTorque = -epas_status["EPAS3S_torsionBarTorque"] # This matches stock logic, but with halved minimum frames (0.25-0.3s) ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 15) eac_status = self.can_define.dv["EPAS3S_sysStatus"]["EPAS3S_eacStatus"].get(int(epas_status["EPAS3S_eacStatus"]), None) ret.steerFaultPermanent = eac_status == "EAC_FAULT" ret.steerFaultTemporary = eac_status == "EAC_INHIBITED" # FSD disengages using union of handsOnLevel (slow overrides) and high angle rate faults (fast overrides, high speed) # TODO: implement in safety eac_error_code = self.can_define.dv["EPAS3S_sysStatus"]["EPAS3S_eacErrorCode"].get(int(epas_status["EPAS3S_eacErrorCode"]), None) ret.steeringDisengage = self.hands_on_level >= 3 or (eac_status == "EAC_INHIBITED" and eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY") # Cruise state cruise_state = self.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp_party.vl["DI_state"]["DI_cruiseState"]), None) speed_units = self.can_define.dv["DI_state"]["DI_speedUnits"].get(int(cp_party.vl["DI_state"]["DI_speedUnits"]), None) ret.cruiseState.enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL") if speed_units == "KPH": ret.cruiseState.speed = max(cp_party.vl["DI_state"]["DI_digitalSpeed"] * CV.KPH_TO_MS, 1e-3) elif speed_units == "MPH": ret.cruiseState.speed = max(cp_party.vl["DI_state"]["DI_digitalSpeed"] * CV.MPH_TO_MS, 1e-3) ret.cruiseState.available = cruise_state == "STANDBY" or ret.cruiseState.enabled ret.cruiseState.standstill = False # This needs to be false, since we can resume from stop without sending anything special ret.standstill = cruise_state == "STANDSTILL" ret.accFaulted = cruise_state == "FAULT" # Gear ret.gearShifter = GEAR_MAP[self.can_define.dv["DI_systemStatus"]["DI_gear"].get(int(cp_party.vl["DI_systemStatus"]["DI_gear"]), "DI_GEAR_INVALID")] # Doors ret.doorOpen = cp_party.vl["UI_warning"]["anyDoorOpen"] == 1 # Blinkers ret.leftBlinker = cp_party.vl["UI_warning"]["leftBlinkerBlinking"] in (1, 2) ret.rightBlinker = cp_party.vl["UI_warning"]["rightBlinkerBlinking"] in (1, 2) # Seatbelt ret.seatbeltUnlatched = cp_party.vl["UI_warning"]["buckleStatus"] != 1 # Blindspot ret.leftBlindspot = cp_ap_party.vl["DAS_status"]["DAS_blindSpotRearLeft"] != 0 ret.rightBlindspot = cp_ap_party.vl["DAS_status"]["DAS_blindSpotRearRight"] != 0 # AEB ret.stockAeb = cp_ap_party.vl["DAS_control"]["DAS_aebEvent"] == 1 # LKAS ret.stockLkas = cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 2 # LANE_KEEP_ASSIST # Stock Autosteer should be off (includes FSD) ret.invalidLkasSetting = cp_ap_party.vl["DAS_settings"]["DAS_autosteerEnabled"] != 0 # Buttons # ToDo: add Gap adjust button # Messages needed by carcontroller self.das_control = copy.copy(cp_ap_party.vl["DAS_control"]) return ret @staticmethod def get_can_parsers(CP): party_messages = [ # sig_address, frequency ("DI_speed", 50), ("DI_systemStatus", 100), ("IBST_status", 25), ("DI_state", 10), ("EPAS3S_sysStatus", 100), ("UI_warning", 10) ] ap_party_messages = [ ("DAS_control", 25), ("DAS_steeringControl", 50), ("DAS_status", 2), ("DAS_settings", 2), ("SCCM_steeringAngleSensor", 100), ] return { Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], party_messages, CANBUS.party), Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], ap_party_messages, CANBUS.autopilot_party) }