diff --git a/common/params.cc b/common/params.cc index ac9c14e..b05b46b 100755 --- a/common/params.cc +++ b/common/params.cc @@ -194,12 +194,14 @@ std::unordered_map keys = { {"RecordFrontLock", PERSISTENT}, // for the internal fleet {"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"RouteCount", PERSISTENT}, + {"ShutdownTouchReset", PERSISTENT}, {"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"SshEnabled", PERSISTENT}, {"TermsVersion", PERSISTENT}, {"TelemetryEnabled", PERSISTENT}, {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, + {"ModelFps", PERSISTENT}, {"ModelStandby", PERSISTENT}, {"ModelStandbyTs", PERSISTENT}, {"UbloxAvailable", PERSISTENT}, diff --git a/selfdrive/clearpilot/dashcamd b/selfdrive/clearpilot/dashcamd index 1ab5a52..828d6f5 100755 Binary files a/selfdrive/clearpilot/dashcamd and b/selfdrive/clearpilot/dashcamd differ diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index e86e0c5..22bb698 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -170,6 +170,8 @@ class Controls: self.current_alert_types = [ET.PERMANENT] self.logged_comm_issue = None self.not_running_prev = None + self.last_lat_engage_ts = 0 # CLEARPILOT: timestamp of most recent lat engage transition + self.prev_lat_active = False self.steer_limited = False self.desired_curvature = 0.0 self.experimental_mode = False @@ -455,7 +457,16 @@ class Controls: standby_ts = float(self.params_memory.get("ModelStandbyTs") or "0") except (ValueError, TypeError): standby_ts = 0 - model_suppress = (time.monotonic() - standby_ts) < 2.0 + now = time.monotonic() + model_suppress = (now - standby_ts) < 2.0 + + # CLEARPILOT: detect lat engage rising edge and suppress commIssue for 2s after + # Model FPS jumps from 4 to 20 on engage, causing transient freq check failures + lat_active_now = self.CC.latActive + if lat_active_now and not self.prev_lat_active: + self.last_lat_engage_ts = now + self.prev_lat_active = lat_active_now + lat_engage_suppress = (now - self.last_lat_engage_ts) < 2.0 not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning} if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES): @@ -486,7 +497,7 @@ class Controls: # generic catch-all. ideally, a more specific event should be added above instead has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE)) no_system_errors = (not has_disable_events) or (len(self.events) == num_events) - if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors and not model_suppress: + if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors and not model_suppress and not lat_engage_suppress: if not self.sm.all_alive(): self.events.add(EventName.commIssue) elif not self.sm.all_freq_ok(): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index bb61270..d86108a 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -143,6 +143,9 @@ class LongitudinalPlanner: self.params = Params() self.params_memory = Params("/dev/shm/params") + # CLEARPILOT: track model FPS for dynamic dt adjustment + self.model_fps = 20 + self.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS self.release = get_short_branch() == "clearpilot" @@ -173,6 +176,19 @@ class LongitudinalPlanner: return x, v, a, j def update(self, sm): + # CLEARPILOT: read actual model FPS and adjust dt accordingly + model_fps_raw = self.params_memory.get("ModelFps") + if model_fps_raw is not None: + try: + fps = int(model_fps_raw) + if fps > 0 and fps != self.model_fps: + self.model_fps = fps + self.dt = 1.0 / fps + self.v_desired_filter.dt = self.dt + self.v_desired_filter.update_alpha(2.0) # rc=2.0, same as __init__ + except (ValueError, ZeroDivisionError): + pass + self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' v_ego = sm['carState'].vEgo @@ -239,7 +255,10 @@ class LongitudinalPlanner: self.j_desired_trajectory = np.interp(ModelConstants.T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution) # TODO counter is only needed because radar is glitchy, remove once radar is gone - self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill + # CLEARPILOT: scale crash_cnt threshold by FPS — at 20fps need 3 frames (0.15s), + # at 4fps need 1 frame (0.25s already exceeds 0.15s window) + fcw_threshold = max(0, int(0.15 * self.model_fps) - 1) # 20fps->2, 4fps->0 + self.fcw = self.mpc.crash_cnt > fcw_threshold and not sm['carState'].standstill if self.fcw: cloudlog.info("FCW triggered") diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 7190e4d..dd6b810 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -89,7 +89,9 @@ def manager_init(frogpilot_functions) -> None: params_memory.put("VpnEnabled", "1") params_memory.put("DashcamFrames", "0") params_memory.put("DashcamState", "stopped") + params_memory.put("ModelFps", "20") params_memory.put("ModelStandby", "0") + params_memory.put("ShutdownTouchReset", "0") params_memory.put("ModelStandbyTs", "0") params_memory.put("CarIsMetric", "0") params_memory.put("ClearpilotSpeedDisplay", "") diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 88a84d1..10fc514 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -275,6 +275,8 @@ def main(demo=False): # Skip 4 out of every 5 frames (20fps -> 4fps) # Write standby timestamp so controlsd suppresses transient errors if not full_rate: + if params_memory.get("ModelFps") != b"4": + params_memory.put("ModelFps", "4") now = _time.monotonic() if now - last_standby_ts_write > 1.0: params_memory.put("ModelStandbyTs", str(now)) @@ -283,6 +285,9 @@ def main(demo=False): last_vipc_frame_id = meta_main.frame_id run_count += 1 continue + else: + if params_memory.get("ModelFps") != b"20": + params_memory.put("ModelFps", "20") desire = DH.desire is_rhd = sm["driverMonitoringState"].isRHD diff --git a/selfdrive/thermald/fan_controller.py b/selfdrive/thermald/fan_controller.py index 4a96611..ca1e09a 100755 --- a/selfdrive/thermald/fan_controller.py +++ b/selfdrive/thermald/fan_controller.py @@ -39,6 +39,10 @@ class TiciFanController(BaseFanController): elif ignition: self.controller.neg_limit = -100 self.controller.pos_limit = -15 + # CLEARPILOT: offroad but overheating (startup cooling) — full fan range + elif cur_temp >= 75: + self.controller.neg_limit = -100 + self.controller.pos_limit = 0 else: self.controller.neg_limit = -30 self.controller.pos_limit = 0 diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 7936f89..eb83e6f 100755 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -36,12 +36,9 @@ class PowerMonitoring: # Reset capacity if it's low self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh)) - # FrogPilot variables - device_management = self.params.get_bool("DeviceManagement") - device_shutdown_setting = self.params.get_int("DeviceShutdown") if device_management else 33 - # If the toggle is set for < 1 hour, configure by 15 minute increments - self.device_shutdown_time = (device_shutdown_setting - 3) * 3600 if device_shutdown_setting >= 4 else device_shutdown_setting * (60 * 15) - self.low_voltage_shutdown = self.params.get_float("LowVoltageShutdown") if device_management else VBATT_PAUSE_CHARGING + # CLEARPILOT: hardcoded 10 minute shutdown timer (not user-configurable) + self.device_shutdown_time = 600 + self.low_voltage_shutdown = VBATT_PAUSE_CHARGING # Calculation tick def calculate(self, voltage: int | None, ignition: bool): diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 42d4317..12aee3b 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -197,7 +197,9 @@ def thermald_thread(end_event, hw_queue) -> None: engaged_prev = False params = Params() + params_memory = Params("/dev/shm/params") power_monitor = PowerMonitoring() + last_touch_reset = "0" # CLEARPILOT: track last seen touch reset value HARDWARE.initialize_hardware() thermal_config = HARDWARE.get_thermal_config() @@ -409,6 +411,13 @@ def thermald_thread(end_event, hw_queue) -> None: statlog.sample("som_power_draw", som_power_draw) msg.deviceState.somPowerDrawW = som_power_draw + # CLEARPILOT: screen tap resets shutdown timer (off_ts) while offroad + touch_reset = params_memory.get("ShutdownTouchReset") + if touch_reset is not None and touch_reset != last_touch_reset and off_ts is not None: + off_ts = time.monotonic() + cloudlog.info("shutdown timer reset by screen touch") + last_touch_reset = touch_reset + # Check if we need to shut down if power_monitor.should_shutdown(onroad_conditions["ignition"], in_car, off_ts, started_seen): cloudlog.warning(f"shutting device down, offroad since {off_ts}") diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 0bc132c..7a57790 100755 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -179,13 +179,6 @@ static const char *clpSidebarBtnStyle = R"( // clpActionBtnStyle removed — no longer used -// Shutdown timer: param value -> display label -static QString shutdownLabel(int val) { - if (val == 0) return "5 mins"; - if (val <= 3) return QString::number(val * 15) + " mins"; - int hours = val - 3; - return QString::number(hours) + (hours == 1 ? " hour" : " hours"); -} ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { // Sidebar @@ -256,27 +249,6 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) { }); general_panel->addItem(resetCalibBtn); - // Shutdown Timer - int cur_shutdown = Params().getInt("DeviceShutdown"); - auto shutdownBtn = new ButtonControl("Shutdown Timer", shutdownLabel(cur_shutdown), - "How long the device stays on after the car is turned off."); - connect(shutdownBtn, &ButtonControl::clicked, [=]() { - QStringList options; - for (int i = 0; i <= 33; i++) { - options << shutdownLabel(i); - } - int current = Params().getInt("DeviceShutdown"); - QString sel = MultiOptionDialog::getSelection("Shutdown Timer", options, shutdownLabel(current), this); - if (!sel.isEmpty()) { - int idx = options.indexOf(sel); - if (idx >= 0) { - Params().putInt("DeviceShutdown", idx); - shutdownBtn->setValue(shutdownLabel(idx)); - } - } - }); - general_panel->addItem(shutdownBtn); - // Power buttons QHBoxLayout *power_layout = new QHBoxLayout(); power_layout->setSpacing(30); diff --git a/selfdrive/ui/qt/spinner b/selfdrive/ui/qt/spinner index 8a0be52..0f2b116 100755 Binary files a/selfdrive/ui/qt/spinner and b/selfdrive/ui/qt/spinner differ diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index d4d8f06..0a53514 100755 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -172,12 +172,15 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) { case QEvent::MouseButtonPress: case QEvent::MouseMove: { // CLEARPILOT: tap while screen-off (mode 3) -> wake to auto-normal (mode 0) + Params pmem{"/dev/shm/params"}; if (!device()->isAwake()) { - Params pmem{"/dev/shm/params"}; if (pmem.getInt("ScreenDisplayMode") == 3) { pmem.putInt("ScreenDisplayMode", 0); } } + // CLEARPILOT: reset shutdown timer on any screen touch + static int touch_counter = 0; + pmem.put("ShutdownTouchReset", std::to_string(++touch_counter)); // ignore events when device is awakened by resetInteractiveTimeout ignore = !device()->isAwake(); device()->resetInteractiveTimeout(uiState()->scene.screen_timeout, uiState()->scene.screen_timeout_onroad);