#!/usr/bin/env python3 import time from iqpilot.common.constants import CV from iqpilot.common.params import Params from iqpilot.common.swaglog import cloudlog from iqpilot.common.k3_slc_log import k3_slc_log from iqpilot.selfdrive.controls.lib.speed_limit_controller import SpeedLimitController CRUISING_SPEED = 7 class SLCVCruise: def __init__(self): self.params = Params() self.slc = SpeedLimitController(self.params) self._last_debug_log_t = 0.0 self._last_debug_signature = None # Exposed SLC state (for UI/logging) self.controller_enabled = False self.mode_assist = False self.slc_offset = 0 self.slc_target = 0 self.slc_source = "None" self.slc_unconfirmed = 0 self.slc_overridden_speed = 0 self.slc_active_target = 0 self.slc_active_source = "None" self._user_max_speed = 0.0 self.slc_experimental_mode = False self.pending_events = [] @property def assist_state(self): return getattr(self.slc, 'assist_state', None) @property def slc_a_target(self): return float(getattr(self.slc, 'output_a_target', 0.0)) def _maybe_log_debug(self, slc_params, apply_enabled, v_cruise, v_ego, dashboard_speed_limit, applied_target, returned_v_cruise): map_speed_limit = float(getattr(self.slc, "map_speed_limit", 0.0) or 0.0) mapbox_limit = float(getattr(self.slc, "mapbox_limit", 0.0) or 0.0) next_speed_limit = float(getattr(self.slc, "next_speed_limit", 0.0) or 0.0) gps_valid = bool(getattr(self.slc, "gps_valid", False)) signature = ( bool(slc_params["speed_limit_controller"]), bool(slc_params["show_speed_limits"]), self.slc.target, self.slc.source, self.slc.active_target, self.slc.active_source, map_speed_limit, mapbox_limit, next_speed_limit, self.slc.overridden_speed, bool(apply_enabled), float(applied_target), float(returned_v_cruise), ) now_mono = time.monotonic() if signature == self._last_debug_signature and now_mono - self._last_debug_log_t < 5.0: return self._last_debug_signature = signature self._last_debug_log_t = now_mono message = ( "SLC debug: " f"mode={int(self.params.get('IQSpeedAssistMode', return_default=True))} " f"controller={slc_params['speed_limit_controller']} " f"show={slc_params['show_speed_limits']} " f"apply_enabled={bool(apply_enabled)} " f"dashboard={round(float(dashboard_speed_limit), 2)} " f"map_data={round(map_speed_limit, 2)} " f"mapbox={round(mapbox_limit, 2)} " f"next_map={round(next_speed_limit, 2)} " f"selected_source={self.slc.source} " f"selected_target={round(float(self.slc.target), 2)} " f"active_source={self.slc.active_source} " f"active_target={round(float(self.slc.active_target), 2)} " f"offset={round(float(self.slc_offset), 2)} " f"override={round(float(self.slc.overridden_speed), 2)} " f"gps_valid={gps_valid} " f"applied_target={round(float(applied_target), 2)} " f"returned_v_cruise={round(float(returned_v_cruise), 2)} " f"v_cruise={round(float(v_cruise), 2)} " f"v_ego={round(float(v_ego), 2)}" ) cloudlog.info(message) k3_slc_log(message) def _get_slc_params(self): """ Load SLC parameters from Params. Returns: Dictionary of SLC configuration parameters """ def get_param_bool(key, default=False): value = self.params.get_bool(key) return value if value is not None else default def get_param_float(key, default=0.0): value = self.params.get(key) if value is None: return default if isinstance(value, bytes): try: return float(value.decode('utf-8')) except (ValueError, AttributeError): return default return float(value) def get_param_str(key, default=""): value = self.params.get(key) if value is None: return default if isinstance(value, bytes): return value.decode('utf-8') return str(value) slc_policy = int(get_param_str("SLCPolicy", "1")) override_method = int(get_param_str("SLCOverrideMethod", "0")) override_manual = (override_method == 0) override_set_speed = (override_method == 1) speed_limit_mode = int(get_param_str("IQSpeedAssistMode", "1")) # default: SpeedLimitMode.information speed_limit_controller = get_param_bool("SpeedLimitController") show_speed_limits = get_param_bool("ShowSpeedLimits") if speed_limit_mode == 0: # SpeedLimitMode.off speed_limit_controller = False show_speed_limits = False elif speed_limit_mode == 3: # SpeedLimitMode.control speed_limit_controller = True show_speed_limits = False else: speed_limit_controller = False show_speed_limits = True return { "speed_limit_controller": speed_limit_controller, "speed_limit_mode": speed_limit_mode, "show_speed_limits": show_speed_limits, "slc_policy": slc_policy, "slc_auto_confirm": get_param_bool("SLCAutoConfirm"), "speed_limit_confirmation_higher": get_param_bool("SpeedLimitConfirmationHigher"), "speed_limit_confirmation_lower": get_param_bool("SpeedLimitConfirmationLower"), "map_speed_lookahead_higher": get_param_float("MapSpeedLookaheadHigher", 5.0), "map_speed_lookahead_lower": get_param_float("MapSpeedLookaheadLower", 5.0), "slc_fallback_experimental_mode": get_param_bool("SLCFallbackExperimentalMode"), "slc_fallback_set_speed": get_param_bool("SLCFallbackSetSpeed"), "slc_fallback_previous_speed_limit": get_param_bool("SLCFallbackPreviousSpeedLimit"), "speed_limit_controller_override_manual": override_manual, "speed_limit_controller_override_set_speed": override_set_speed, "slc_online_filler": get_param_bool("SLCOnlineFiller"), "is_metric": get_param_bool("IsMetric"), "construction_zone_assist": get_param_bool("ConstructionZoneAssist"), "construction_zone_speed": get_param_float("ConstructionZoneSpeed", 60.0), } @staticmethod def _allow_auto_raise(slc_params): # Reuse the existing "confirm higher" toggle as the gate: # disabled confirm => allow SLC to raise cruise to a higher accepted limit. return not slc_params["speed_limit_confirmation_higher"] def update(self, apply_enabled, now, time_validated, v_cruise, v_ego, sm): slc_params = self._get_slc_params() self.controller_enabled = bool(slc_params["speed_limit_controller"]) self.mode_assist = int(slc_params["speed_limit_mode"]) == 3 # SpeedLimitMode.control is_metric = slc_params["is_metric"] v_cruise_cluster = max(sm["carState"].vCruiseCluster * CV.KPH_TO_MS, v_cruise) v_cruise_diff = v_cruise_cluster - v_cruise v_ego_cluster = max(sm["carState"].vEgoCluster, v_ego) v_ego_diff = v_ego_cluster - v_ego car_state_iq = sm["iqCarState"] dashboard_speed_limit = car_state_iq.speedLimit if hasattr(car_state_iq, "speedLimit") else 0 if apply_enabled: if self._user_max_speed <= 0.0: self._user_max_speed = v_cruise_cluster elif v_cruise_cluster > self._user_max_speed: self._user_max_speed = v_cruise_cluster else: self._user_max_speed = 0.0 if slc_params["speed_limit_controller"]: self.slc.update_limits(dashboard_speed_limit, now, time_validated, v_cruise, v_ego, sm, slc_params) self.pending_events = list(getattr(self.slc, 'pending_events', [])) self.slc.update_override(v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm, slc_params, is_metric) self.slc_offset = 0 if self.slc.source == "Construction" else self.slc.get_offset(is_metric) self.slc_target = self.slc.target self.slc_source = self.slc.source self.slc_active_target = self.slc.active_target self.slc_active_source = self.slc.active_source self.slc_unconfirmed = self.slc.unconfirmed_speed_limit self.slc_overridden_speed = self.slc.overridden_speed elif slc_params["show_speed_limits"]: self.slc.update_limits(dashboard_speed_limit, now, time_validated, v_cruise, v_ego, sm, slc_params) self.pending_events = [] self.slc_offset = 0 self.slc_target = self.slc.target self.slc_source = self.slc.source self.slc_active_target = self.slc.active_target self.slc_active_source = self.slc.active_source self.slc_unconfirmed = self.slc.unconfirmed_speed_limit self.slc_overridden_speed = 0 else: self.pending_events = [] self.slc_offset = 0 self.slc_target = 0 self.slc_source = "None" self.slc_active_target = 0 self.slc_active_source = "None" self.slc_unconfirmed = 0 self.slc_overridden_speed = 0 self.slc_experimental_mode = bool( slc_params["speed_limit_controller"] and slc_params["slc_fallback_experimental_mode"] and self.slc_target <= 0 ) applied_target = 0.0 if slc_params["speed_limit_controller"] and apply_enabled: slc_target_with_offset = max(self.slc_overridden_speed, self.slc_target + self.slc_offset) allow_auto_raise = self._allow_auto_raise(slc_params) if self._user_max_speed > 0.0 and not allow_auto_raise: slc_target_with_offset = min(slc_target_with_offset, self._user_max_speed) slc_cruise_target = slc_target_with_offset - v_ego_diff if slc_cruise_target >= CRUISING_SPEED: applied_target = slc_cruise_target if allow_auto_raise and self.slc_source != "Construction": v_cruise = slc_cruise_target else: v_cruise = min(v_cruise, slc_cruise_target) self._maybe_log_debug(slc_params, apply_enabled, v_cruise, v_ego, dashboard_speed_limit, applied_target, v_cruise) return v_cruise