fix: fan 100% on overheat, FCW fps-aware, commIssue suppress, 10min shutdown
- Fan controller: allow full 100% fan when offroad temp >= 75°C (startup cooling) - ModelFps memory param: modeld publishes actual FPS (20 or 4) so downstream consumers can adjust frame-rate-dependent logic - Longitudinal planner: dynamically adjusts dt and v_desired_filter based on ModelFps; FCW crash_cnt threshold scales with FPS to maintain consistent 0.15s trigger window at both 20fps and 4fps - controlsd: suppress commIssue alerts for 2s after lateral control engages (FPS transition from 4->20 causes transient freq check failures) - Shutdown timer: hardcoded to 10 minutes (was 45min via FrogPilot param), screen taps reset the countdown via ShutdownTouchReset memory param, removed Shutdown Timer UI selector from ClearPilot menu Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -194,12 +194,14 @@ std::unordered_map<std::string, uint32_t> 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},
|
||||
|
||||
Binary file not shown.
@@ -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():
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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", "")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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);
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
if (!device()->isAwake()) {
|
||||
Params pmem{"/dev/shm/params"};
|
||||
if (!device()->isAwake()) {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user