Files
2026-07-21 13:43:46 -05:00

121 lines
5.2 KiB
Python

"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
IQ.Pilot's controls-side extension layer. Controls mixes this in to gain the extra
sub/pub services, the IQ car-control message (radar blend, SLC set-speed sync, AOL
guidance continuity) and the lateral-engage gate, without touching stock controlsd.
"""
import time
import cereal.messaging as messaging
from cereal import log, custom
from iqdbc.car import structs
from openpilot.common.constants import CV
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.iqpilot.common.steer_delay import resolve_steer_delay
from openpilot.iqpilot.selfdrive.car.enhanced_stock_longitudinal_control import build_iq_control_params_from_plan
from openpilot.iqpilot.selfdrive.iqmodeld.models.inference_state import InferenceStateBase
from openpilot.iqpilot.selfdrive.controls.lib.helpers.blinker_pause import IQSignalPauseController
from openpilot.iqpilot.selfdrive.controls.lib.iq_dynamic.radar_manager import RadarManager
_PARAM_REFRESH_S = 3.0
_LEAD_FIELDS = ("dRel", "yRel", "vRel", "aRel", "vLead", "dPath", "vLat", "vLeadK",
"aLeadK", "fcw", "status", "aLeadTau", "modelProb", "radar", "radarTrackId")
class IQControlsLayer(InferenceStateBase):
def __init__(self, CP: structs.CarParams, params: Params):
InferenceStateBase.__init__(self)
self.CP = CP
self.params = params
self.blinker_pause_lateral = IQSignalPauseController()
cloudlog.info("IQ controls layer waiting for IQCarParams")
self.CP_IQ = messaging.log_from_bytes(params.get("IQCarParams", block=True), custom.IQCarParams)
cloudlog.info("IQ controls layer got IQCarParams")
self.iq_sub_services = ['radarState', 'iqState', 'iqPlan', 'iqNavState']
self.iq_pub_services = ['iqCarControl']
self.radar_manager = RadarManager(CP, params)
self._next_param_refresh = 0.0
self._needs_iq_lead_data = CP.brand == "hyundai"
self._maneuver_mode = params.get_bool("LateralManeuverMode")
self._sync_set_speed = self._want_set_speed_to_limit()
self._slc_limit_kph = None
self._slc_limit_pending_kph = None
def _want_set_speed_to_limit(self) -> bool:
try:
return self.params.get_bool("SLCSetSpeedToLimit")
except Exception:
return False
# --- periodic param refresh (throttled to a few Hz) --------------------------
def refresh_iq_params(self, sm: messaging.SubMaster) -> None:
now = time.monotonic()
if now - self._next_param_refresh <= _PARAM_REFRESH_S:
return
self.blinker_pause_lateral.get_params()
if self.CP.lateralTuning.which() == 'torque':
self.lat_delay = resolve_steer_delay(self.params, sm["liveDelay"].lateralDelay)
self._sync_set_speed = self._want_set_speed_to_limit()
self.radar_manager.read_params()
self._next_param_refresh = now
# --- lateral engage gate -----------------------------------------------------
def iq_lateral_allowed(self, sm: messaging.SubMaster) -> bool:
if self.blinker_pause_lateral.update(sm['carState']):
return False
aol = sm['iqState'].aol
stock_active = bool(sm['selfdriveState'].active)
if self._maneuver_mode:
return stock_active or bool(aol.available and aol.active)
if aol.available:
return bool(aol.active)
return stock_active
@staticmethod
def _lead_snapshot(ld: log.RadarState.LeadData) -> dict:
return {field: getattr(ld, field) for field in _LEAD_FIELDS}
# --- build + publish the IQ car-control message ------------------------------
def _compose_iq_carcontrol(self, sm: messaging.SubMaster) -> custom.IQCarControl:
CC_IQ = custom.IQCarControl.new_message()
lp = sm['liveParameters']
CC_IQ.angleOffsetDeg = float(getattr(lp, 'angleOffsetDeg', 0.0))
CC_IQ.aol = sm['iqState'].aol
if self._needs_iq_lead_data:
CC_IQ.leadOne = self._lead_snapshot(sm['radarState'].leadOne)
CC_IQ.leadTwo = self._lead_snapshot(sm['radarState'].leadTwo)
if self.CP.openpilotLongitudinalControl:
cruise = getattr(sm['carState'], 'cruiseState', None)
set_speed_ms = float(max(getattr(cruise, 'speedCluster', 0.0), getattr(cruise, 'speed', 0.0), 0.0))
set_speed_kph = set_speed_ms * CV.MS_TO_KPH
if self._sync_set_speed:
CC_IQ.params, self._slc_limit_kph, self._slc_limit_pending_kph = build_iq_control_params_from_plan(
self.CP, sm['iqPlan'], bool(sm['selfdriveState'].enabled), set_speed_kph,
self._slc_limit_kph, self._slc_limit_pending_kph)
else:
self._slc_limit_kph = self._slc_limit_pending_kph = None
self.radar_manager.update(CC_IQ, sm, set_speed_kph)
return CC_IQ
@staticmethod
def _emit(CC_IQ: custom.IQCarControl, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
envelope = messaging.new_message('iqCarControl')
envelope.valid = sm['carState'].canValid
envelope.iqCarControl = CC_IQ
pm.send('iqCarControl', envelope)
def publish_iq_state(self, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
fresh = sm.updated['iqState'] or sm.updated['iqPlan'] or (self._needs_iq_lead_data and sm.updated['radarState'])
if not fresh:
return
self._emit(self._compose_iq_carcontrol(sm), sm, pm)