172 lines
8.0 KiB
Python
172 lines
8.0 KiB
Python
import copy
|
|
from iqdbc.can import CANDefine, CANParser
|
|
from iqdbc.car import Bus, structs
|
|
from iqdbc.car.common.conversions import Conversions as CV
|
|
from iqdbc.car.interfaces import CarStateBase
|
|
from iqdbc.car.tesla import TESLA_BLINKERS
|
|
from iqdbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD, TeslaFlags
|
|
|
|
from iqdbc.lvbs.car.tesla.iq_carstate import IQCarState
|
|
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ, TeslaSafetyFlagsIQ
|
|
from iqpilot.common.params import Params
|
|
|
|
|
|
def stock_autosteer_invalid(CP, CP_IQ, autopilot_state: int) -> bool:
|
|
return (not (CP.flags & TeslaFlags.MISSING_DAS_SETTINGS) and
|
|
not (CP_IQ.iqSafetyFlags & TeslaSafetyFlagsIQ.FSD_VISUALIZATION) and
|
|
autopilot_state not in (0, 1, 2))
|
|
|
|
|
|
class CarState(CarStateBase, IQCarState):
|
|
def __init__(self, CP, CP_IQ):
|
|
from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
|
|
iq_lvbs_alc = import_verified_module("iqpilot_alc_private", "iqpilot_private.konn3kt.iqlvbs.alc")
|
|
CarStateBase.__init__(self, CP, CP_IQ)
|
|
IQCarState.__init__(self, CP, CP_IQ)
|
|
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
|
|
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"]
|
|
|
|
self.summon = False
|
|
self.summon_prev = False
|
|
self.cruise_enabled_prev = False
|
|
|
|
self.hands_on_level = 0
|
|
self.das_control = None
|
|
self.das_body_controls_dat = b""
|
|
self._odometer_store = iq_lvbs_alc.create_vehicle_odometer_store(CP, Params())
|
|
self.cruise_override = False
|
|
|
|
def update_summon_state(self, summon_state: str, cruise_enabled: bool):
|
|
summon_now = summon_state in ("ACTIVE", "COMPLETE", "SELFPARK_STARTED")
|
|
if summon_now and not self.summon_prev and not self.cruise_enabled_prev:
|
|
self.summon = True
|
|
if not summon_now:
|
|
self.summon = False
|
|
self.summon_prev = summon_now
|
|
self.cruise_enabled_prev = cruise_enabled
|
|
|
|
def update(self, can_parsers) -> tuple[structs.CarState, structs.IQCarState]:
|
|
cp_party = can_parsers[Bus.party]
|
|
cp_ap_party = can_parsers[Bus.ap_party]
|
|
ret = structs.CarState()
|
|
ret_iq = structs.IQCarState()
|
|
scale_speed = 1.01
|
|
length = 0.11
|
|
|
|
# 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)
|
|
|
|
# Displayed speed
|
|
ui_speed_units = self.can_define.dv["DI_speed"]["DI_uiSpeedUnits"].get(int(cp_party.vl["DI_speed"]["DI_uiSpeedUnits"]), None)
|
|
if ui_speed_units == "DI_SPEED_KPH":
|
|
ret.vEgoCluster = cp_party.vl["DI_speed"]["DI_uiSpeed"] * CV.KPH_TO_MS
|
|
elif ui_speed_units == "DI_SPEED_MPH":
|
|
ret.vEgoCluster = cp_party.vl["DI_speed"]["DI_uiSpeed"] * CV.MPH_TO_MS
|
|
|
|
# Gas pedal
|
|
ret.gasPressed = cp_party.vl["DI_systemStatus"]["DI_accelPedalPos"] > 0
|
|
|
|
# Brake pedal
|
|
ret.brake = 0
|
|
ret.brakePressed = cp_party.vl["ESP_status"]["ESP_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"]
|
|
ret.steeringTorqueEps = -epas_status["EPAS3S_steeringRackForce"] * length / self.CP.steerRatio
|
|
|
|
# stock handsOnLevel uses >0.5 for 0.25s, but is too slow
|
|
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
|
|
|
|
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)
|
|
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)
|
|
|
|
summon_state = self.can_define.dv["DI_state"]["DI_autoparkState"].get(int(cp_party.vl["DI_state"]["DI_autoparkState"]), None)
|
|
cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
|
|
self.cruise_override = cruise_state in ("OVERRIDE")
|
|
self.update_summon_state(summon_state, cruise_enabled)
|
|
|
|
# Match panda safety cruise engaged logic
|
|
ret.cruiseState.enabled = cruise_enabled and not self.summon
|
|
if speed_units == "KPH":
|
|
ret.cruiseState.speedCluster = cp_party.vl["DI_state"]["DI_digitalSpeed"] * CV.KPH_TO_MS
|
|
elif speed_units == "MPH":
|
|
ret.cruiseState.speedCluster = cp_party.vl["DI_state"]["DI_digitalSpeed"] * CV.MPH_TO_MS
|
|
ret.cruiseState.speed = max(ret.cruiseState.speedCluster / scale_speed, 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 = cp_party.vl["ESP_B"]["ESP_vehicleStandstillSts"] == 1
|
|
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
|
|
steer_control_type = int(cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"])
|
|
if self.CP.flags & TeslaFlags.LEGACY_DAS_STEERING:
|
|
steer_control_type >>= 1 # legacy firmware uses a 2-bit field, one bit up from the 3-bit signal
|
|
ret.stockLkas = steer_control_type == 2 # LANE_KEEP_ASSIST
|
|
|
|
# Stock Autosteer should be disengaged (includes FSD)
|
|
# TODO: find for TESLA_MODEL_X and HW2.5 vehicles
|
|
ret.invalidLkasSetting = stock_autosteer_invalid(self.CP, self.CP_IQ, int(cp_ap_party.vl["DAS_status"]["DAS_autopilotState"]))
|
|
|
|
# Buttons # ToDo: add Gap adjust button
|
|
|
|
# Messages needed by carcontroller
|
|
self.das_control = copy.copy(cp_ap_party.vl["DAS_control"])
|
|
|
|
# Raw stock DAS_bodyControls bytes (bus 2), used to ride the blinker on the vehicle bus.
|
|
# FIXME: gate by FingerPrint
|
|
if TESLA_BLINKERS and Bus.cam in can_parsers:
|
|
self.das_body_controls_dat = bytes(can_parsers[Bus.cam].dat.get(0x3E9, b""))
|
|
|
|
IQCarState.update(self, ret, ret_iq, can_parsers)
|
|
if ret.odometer > 0.0:
|
|
ret.odometer = self._odometer_store.record(ret.odometer) or 0.0
|
|
|
|
return ret, ret_iq
|
|
|
|
@staticmethod
|
|
def get_can_parsers(CP, CP_IQ):
|
|
parsers = {
|
|
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
|
|
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party),
|
|
**IQCarState.get_parser(CP, CP_IQ),
|
|
}
|
|
# Stock DAS_bodyControls from the AP bus (bus 2) for the nav blinker.
|
|
if TESLA_BLINKERS and CP_IQ.flags & TeslaFlagsIQ.HAS_VEHICLE_BUS and Bus.adas in DBC[CP.carFingerprint]:
|
|
parsers[Bus.cam] = CANParser(DBC[CP.carFingerprint][Bus.adas], [("DAS_bodyControls", 2)], CANBUS.autopilot_party)
|
|
return parsers
|