diff --git a/CLAUDE.md b/CLAUDE.md index 4787d30..2cd4057 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,7 +101,7 @@ ClearPilot uses memory params (`/dev/shm/params/d/`) for UI toggles that should - **Python access**: Use `Params("/dev/shm/params")` - **Defaults**: Set in `manager_init()` via `Params("/dev/shm/params").put(key, value)` - **UI toggles**: Use `ToggleControl` with manual `toggleFlipped` lambda that writes via `Params("/dev/shm/params")`. Do NOT use `ParamControl` for memory params — it reads/writes persistent params only -- **Current memory params**: `TelemetryEnabled` (default "0"), `VpnEnabled` (default "1"), `ModelStandby` (default "0"), `ScreenDisplayMode`, `DashcamState` (default "stopped"), `DashcamFrames` (default "0") +- **Current memory params**: `TelemetryEnabled` (default "0"), `VpnEnabled` (default "1"), `ModelStandby` (default "0"), `ScreenDisplayMode`, `DashcamState` (default "stopped"), `DashcamFrames` (default "0"), `DashcamShutdown` (default "0") - **IMPORTANT — method names differ between C++ and Python**: C++ uses camelCase (`putBool`, `getBool`, `getInt`), Python uses snake_case (`put_bool`, `get_bool`, `get_int`). This is a common source of silent failures — the wrong casing compiles/runs but doesn't work. ### Building Native (C++) Processes @@ -311,8 +311,7 @@ The OMX encoder (`selfdrive/frogpilot/screenrecorder/omx_encoder.cc`) was ported ### Params -- `DashcamDebug` — when `"1"`, dashcamd runs even without car connected (for bench testing) -- `DashcamShutdown` — set by thermald before power-off, dashcamd acks by clearing it +- `DashcamShutdown` (memory param) — set by thermald before power-off, dashcamd acks by clearing it - `DashcamState` (memory param) — "stopped", "waiting", or "recording" — published every 5s - `DashcamFrames` (memory param) — per-trip encoded frame count, resets each trip — published every 5s @@ -524,7 +523,7 @@ Power On ### ClearPilot Processes -- `dashcamd` — raw camera dashcam recording (runs onroad or with DashcamDebug flag) +- `dashcamd` — raw camera dashcam recording (always runs; manages its own trip lifecycle) ### GPS diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 2ce1378..88b1251 100755 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -12,6 +12,9 @@ struct FrogPilotCarControl @0x81c2f05a394cf4af { alwaysOnLateral @0 :Bool; speedLimitChanged @1 :Bool; trafficModeActive @2 :Bool; + # CLEARPILOT: migrated from paramsMemory to avoid file-read syscalls in modeld/UI/carcontroller + latRequested @3 :Bool; # controlsd's request to enable lat before ramp-up delay + noLatLaneChange @4 :Bool; # lat is temporarily suppressed during a lane change } struct FrogPilotCarState @0xaedffd8f31e7b55d { diff --git a/common/params.cc b/common/params.cc index 2c7920e..6b1c8d1 100755 --- a/common/params.cc +++ b/common/params.cc @@ -109,7 +109,6 @@ std::unordered_map keys = { {"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"CurrentBootlog", PERSISTENT}, {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, - {"DashcamDebug", PERSISTENT}, {"DashcamFrames", PERSISTENT}, {"DashcamState", PERSISTENT}, {"DashcamShutdown", CLEAR_ON_MANAGER_START}, @@ -201,7 +200,6 @@ std::unordered_map keys = { {"TelemetryEnabled", PERSISTENT}, {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, - {"LatRequested", PERSISTENT}, {"ModelFps", PERSISTENT}, {"ModelStandby", PERSISTENT}, {"ModelStandbyTs", PERSISTENT}, @@ -510,8 +508,6 @@ std::unordered_map keys = { {"WheelIcon", PERSISTENT}, {"WheelSpeed", PERSISTENT}, - // Clearpilot - {"no_lat_lane_change", PERSISTENT}, }; } // namespace diff --git a/common/realtime.py b/common/realtime.py index aad4baa..b676cd4 100755 --- a/common/realtime.py +++ b/common/realtime.py @@ -12,7 +12,7 @@ from openpilot.system.hardware import PC # time step for each process DT_CTRL = 0.01 # controlsd DT_MDL = 0.05 # model -DT_TRML = 0.5 # thermald and manager +DT_TRML = 0.25 # thermald and manager — 4 Hz DT_DMON = 0.05 # driver monitoring diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index dc65f9d..3190cbf 100755 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -8,7 +8,6 @@ from openpilot.selfdrive.car.hyundai import hyundaicanfd, hyundaican from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR from openpilot.selfdrive.car.interfaces import CarControllerBase -from openpilot.common.params import Params VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -57,9 +56,6 @@ class CarController(CarControllerBase): self.car_fingerprint = CP.carFingerprint self.last_button_frame = 0 - # CLEARPILOT: cache Params instance — create_steering_messages was building - # a new one every 10ms (100Hz), causing needless allocation + file opens - self.params_memory = Params("/dev/shm/params") def update(self, CC, CS, now_nanos, frogpilot_variables): actuators = CC.actuators @@ -116,7 +112,8 @@ class CarController(CarControllerBase): hda2_long = hda2 and self.CP.openpilotLongitudinalControl # steering control - no_lat_lane_change = self.params_memory.get_int("no_lat_lane_change", 1) + # CLEARPILOT: no_lat_lane_change comes via frogpilot_variables (set by controlsd in-process) + no_lat_lane_change = int(getattr(frogpilot_variables, 'no_lat_lane_change', False)) can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_steer, no_lat_lane_change)) # prevent LFA from activating on HDA2 by sending "no lane lines detected" to ADAS ECU diff --git a/selfdrive/clearpilot/dashcamd b/selfdrive/clearpilot/dashcamd index 654c3f4..9a756bc 100755 Binary files a/selfdrive/clearpilot/dashcamd and b/selfdrive/clearpilot/dashcamd differ diff --git a/selfdrive/clearpilot/dashcamd.cc b/selfdrive/clearpilot/dashcamd.cc index e43f437..97f84fa 100644 --- a/selfdrive/clearpilot/dashcamd.cc +++ b/selfdrive/clearpilot/dashcamd.cc @@ -279,12 +279,12 @@ int main(int argc, char *argv[]) { // Check for graceful shutdown request (every ~1 second) if (++param_check_counter >= CAMERA_FPS) { param_check_counter = 0; - if (params.getBool("DashcamShutdown")) { + if (params_memory.getBool("DashcamShutdown")) { LOGW("dashcamd: shutdown requested"); if (state == RECORDING || state == IDLE_TIMEOUT) { close_trip(); } - params.putBool("DashcamShutdown", false); + params_memory.putBool("DashcamShutdown", false); LOGW("dashcamd: shutdown ack sent, exiting"); break; } diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 6c9d745..7b44026 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -172,9 +172,8 @@ class Controls: self.not_running_prev = None self.lat_requested_ts = 0 # CLEARPILOT: timestamp when lat was first requested (ramp-up delay + comm issue suppression) self.prev_lat_requested = False - self.prev_no_lat_lane_change = None # CLEARPILOT: gate no_lat_lane_change write on change # CLEARPILOT: gate frogpilotCarControl send on change (3 static bools, was publishing 100Hz) - self._prev_fpcc = (None, None, None) + self._prev_fpcc = (None, None, None, None, None) self._last_fpcc_send_frame = 0 self.steer_limited = False self.desired_curvature = 0.0 @@ -702,12 +701,12 @@ class Controls: # CLEARPILOT: tell modeld early so it can ramp to 20fps, then delay latActive by # 250ms (5 frames at 20fps) so downstream services stabilize before steering engages. + # latRequested is now on frogpilotCarControl (cereal) — see update_frogpilot_variables. now_ts = time.monotonic() - if lat_requested != self.prev_lat_requested: - self.params_memory.put_bool("LatRequested", lat_requested) - if lat_requested: - self.lat_requested_ts = now_ts + if lat_requested and not self.prev_lat_requested: + self.lat_requested_ts = now_ts self.prev_lat_requested = lat_requested + self.FPCC.latRequested = lat_requested CC.latActive = lat_requested and (now_ts - self.lat_requested_ts) >= 0.25 CC.longActive = self.enabled and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl @@ -726,10 +725,10 @@ class Controls: no_lat_lane_change = model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change if no_lat_lane_change: CC.latActive = False - # CLEARPILOT: only write on change — per-cycle atomic writes were eating ~15% CPU - if no_lat_lane_change != self.prev_no_lat_lane_change: - self.params_memory.put_bool("no_lat_lane_change", no_lat_lane_change) - self.prev_no_lat_lane_change = no_lat_lane_change + # CLEARPILOT: noLatLaneChange now lives on frogpilotCarControl (see update_frogpilot_variables). + # Also mirrored to frogpilot_variables so in-process carcontroller reads it without IPC. + self.FPCC.noLatLaneChange = no_lat_lane_change + self.frogpilot_variables.no_lat_lane_change = no_lat_lane_change if CS.leftBlinker or CS.rightBlinker: self.last_blinker_frame = self.sm.frame @@ -1211,9 +1210,11 @@ class Controls: self.FPCC.trafficModeActive = self.frogpilot_variables.traffic_mode and self.params_memory.get_bool("TrafficModeActive") - # CLEARPILOT: send only when one of the 3 fields changes, with 1Hz keepalive + # CLEARPILOT: send only when any field changes, with 1Hz keepalive # Saves ~7ms/sec of capnp+zmq work (was sending 100Hz despite static contents) - fpcc_tuple = (self.FPCC.alwaysOnLateral, self.FPCC.speedLimitChanged, self.FPCC.trafficModeActive) + # latRequested and noLatLaneChange are edge-triggered too (rare flips) + fpcc_tuple = (self.FPCC.alwaysOnLateral, self.FPCC.speedLimitChanged, self.FPCC.trafficModeActive, + self.FPCC.latRequested, self.FPCC.noLatLaneChange) if fpcc_tuple != self._prev_fpcc or (self.sm.frame - self._last_fpcc_send_frame) >= 100: fpcc_send = messaging.new_message('frogpilotCarControl') fpcc_send.valid = CS.canValid diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 953acea..94710fe 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -89,7 +89,7 @@ def manager_init(frogpilot_functions) -> None: params_memory.put("VpnEnabled", "1") params_memory.put("DashcamFrames", "0") params_memory.put("DashcamState", "stopped") - params_memory.put("LatRequested", "0") + params_memory.put("DashcamShutdown", "0") params_memory.put("ModelFps", "20") params_memory.put("ModelStandby", "0") params_memory.put("ShutdownTouchReset", "0") @@ -178,7 +178,6 @@ def manager_init(frogpilot_functions) -> None: ("DisableOpenpilotLongitudinal", "0"), ("DisableVTSCSmoothing", "0"), ("DisengageVolume", "100"), - ("DashcamDebug", "1"), ("TelemetryEnabled", "0"), ("DragonPilotTune", "0"), ("DriverCamera", "0"), diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 7b95f24..61e829b 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -169,7 +169,7 @@ def main(demo=False): # messaging pm = PubMaster(["modelV2", "cameraOdometry"]) - sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction", "carControl", "liveTracks", "frogpilotPlan"]) + sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction", "carControl", "frogpilotCarControl", "liveTracks", "frogpilotPlan"]) publish_state = PublishState() params = Params() @@ -245,10 +245,12 @@ def main(demo=False): # Full 20fps: lat requested or lane changing # Reduced 4fps: not lat requested, not standstill (driving without cruise) # Standby 0fps: standstill - # Uses LatRequested (not carControl.latActive) so modeld ramps to 20fps BEFORE + # Uses FPCC.latRequested (not carControl.latActive) so modeld ramps to 20fps BEFORE # controlsd actually engages steering — gives downstream services time to stabilize. - lat_active = params_memory.get_bool("LatRequested") - lane_changing = params_memory.get_bool("no_lat_lane_change") + # CLEARPILOT: both fields migrated from paramsMemory to cereal (no per-cycle file reads) + fpcc = sm['frogpilotCarControl'] + lat_active = fpcc.latRequested + lane_changing = fpcc.noLatLaneChange standstill = sm['carState'].standstill calibrating = sm['liveCalibration'].calStatus != log.LiveCalibrationData.Status.calibrated full_rate = lat_active or lane_changing or calibrating diff --git a/selfdrive/thermald/fan_controller.py b/selfdrive/thermald/fan_controller.py index ca1e09a..fde6e28 100755 --- a/selfdrive/thermald/fan_controller.py +++ b/selfdrive/thermald/fan_controller.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import os from abc import ABC, abstractmethod from openpilot.common.realtime import DT_TRML @@ -7,11 +6,11 @@ from openpilot.common.numpy_fast import interp from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.pid import PIDController -BENCH_MODE = os.environ.get("BENCH_MODE") == "1" class BaseFanController(ABC): @abstractmethod - def update(self, cur_temp: float, ignition: bool, standstill: bool = False) -> int: + def update(self, cur_temp: float, ignition: bool, standstill: bool = False, + is_parked: bool = True, cruise_engaged: bool = False) -> int: pass @@ -23,29 +22,27 @@ class TiciFanController(BaseFanController): self.last_ignition = False self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_TRML)) - def update(self, cur_temp: float, ignition: bool, standstill: bool = False) -> int: - # CLEARPILOT: bench mode always uses normal onroad fan range (30-100%) - if BENCH_MODE and ignition: + def update(self, cur_temp: float, ignition: bool, standstill: bool = False, + is_parked: bool = True, cruise_engaged: bool = False) -> int: + # CLEARPILOT fan range rules: + # parked → 0-100% (full, no floor) + # in drive + cruise engaged (any speed, inc standstill) → 30-100% + # in drive + cruise off + standstill → 10-100% + # in drive + cruise off + moving → 30-100% + # In the PID output, neg_limit is how negative it can go (= max fan as %), + # pos_limit is how positive (= negative of min fan %). + if is_parked: + self.controller.neg_limit = -100 + self.controller.pos_limit = 0 + elif cruise_engaged: self.controller.neg_limit = -100 self.controller.pos_limit = -30 - # CLEARPILOT: at standstill below 74°C, clamp to 0-30% (quiet) - # at standstill above 74°C, allow full 0-100% range - elif ignition and standstill and cur_temp < 74: - self.controller.neg_limit = -30 - self.controller.pos_limit = 0 - elif ignition and standstill: + elif standstill: self.controller.neg_limit = -100 - self.controller.pos_limit = 0 - 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 + self.controller.pos_limit = -10 else: - self.controller.neg_limit = -30 - self.controller.pos_limit = 0 + self.controller.neg_limit = -100 + self.controller.pos_limit = -30 if ignition != self.last_ignition: self.controller.reset() diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 15338b5..d235054 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -292,8 +292,18 @@ def thermald_thread(end_event, hw_queue) -> None: msg.deviceState.maxTempC = all_comp_temp if fan_controller is not None: - standstill = sm['carState'].standstill if sm.seen['carState'] else False - msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"], standstill) + # CLEARPILOT: fan rules based on gear (parked vs drive) and cruise-engaged state + if sm.seen['carState']: + cs = sm['carState'] + standstill = cs.standstill + is_parked = cs.gearShifter == log.CarState.GearShifter.park + else: + standstill = False + is_parked = True # default safe: assume parked, no fan floor + cruise_engaged = sm['controlsState'].enabled if sm.seen['controlsState'] else False + msg.deviceState.fanSpeedPercentDesired = fan_controller.update( + all_comp_temp, onroad_conditions["ignition"], standstill, + is_parked=is_parked, cruise_engaged=cruise_engaged) is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: @@ -422,10 +432,10 @@ def thermald_thread(end_event, hw_queue) -> None: if power_monitor.should_shutdown(onroad_conditions["ignition"], in_car, off_ts, started_seen): cloudlog.warning(f"shutting device down, offroad since {off_ts}") # CLEARPILOT: signal dashcamd to close recording gracefully before power-off - params.put_bool("DashcamShutdown", True) + params_memory.put_bool("DashcamShutdown", True) deadline = time.monotonic() + 15.0 while time.monotonic() < deadline: - if not params.get_bool("DashcamShutdown"): + if not params_memory.get_bool("DashcamShutdown"): cloudlog.info("dashcamd shutdown ack received") break time.sleep(0.5) diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 2a996b3..1f1910b 100755 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -79,9 +79,10 @@ void OnroadWindow::updateState(const UIState &s) { nvg->update(); // CLEARPILOT: force repaint every frame for HUD elements QColor bgColor = bg_colors[s.status]; - if (paramsMemory.getBool("no_lat_lane_change") == 1) { + // CLEARPILOT: read from frogpilotCarControl cereal message (was paramsMemory) + if ((*s.sm)["frogpilotCarControl"].getFrogpilotCarControl().getNoLatLaneChange()) { bgColor = bg_colors[STATUS_DISENGAGED]; - } + } if (bg != bgColor) { bg = bgColor; @@ -753,6 +754,7 @@ void AnnotatedCameraWidget::drawHealthMetrics(QPainter &p) { auto cs = sm["controlsState"].getControlsState(); auto ds = sm["deviceState"].getDeviceState(); auto mv = sm["modelV2"].getModelV2(); + auto ps = sm["peripheralState"].getPeripheralState(); float lag_ms = cs.getCumLagMs(); float drop_pct = mv.getFrameDropPerc(); @@ -760,6 +762,8 @@ void AnnotatedCameraWidget::drawHealthMetrics(QPainter &p) { int mem_pct = ds.getMemoryUsagePercent(); int cpu_pct = 0; for (auto v : ds.getCpuUsagePercent()) cpu_pct = std::max(cpu_pct, (int)v); + // Actual fan (not commanded): scale RPM to 0-100 using 6500 RPM as full scale + int fan_pct = std::min(100, (int)(ps.getFanSpeedRpm() * 100 / 6500)); auto color_for = [](float v, float warn, float crit) { if (v >= crit) return QColor(0xff, 0x50, 0x50); // red @@ -774,6 +778,7 @@ void AnnotatedCameraWidget::drawHealthMetrics(QPainter &p) { {"TEMP", QString::number((int)temp_c), color_for(temp_c, 75.f, 88.f)}, {"CPU", QString::number(cpu_pct), color_for((float)cpu_pct, 75.f, 90.f)}, {"MEM", QString::number(mem_pct), color_for((float)mem_pct, 70.f, 85.f)}, + {"FAN", QString::number(fan_pct), QColor(0xff, 0xff, 0xff)}, }; p.save(); @@ -885,7 +890,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint center lane path // QColor bg_colors[CHANGE_LANE_PATH_COLOR]; - bool is_no_lat_lane_change = paramsMemory.getBool("no_lat_lane_change"); + // CLEARPILOT: read from frogpilotCarControl cereal message (was paramsMemory) + bool is_no_lat_lane_change = sm["frogpilotCarControl"].getFrogpilotCarControl().getNoLatLaneChange(); QColor center_lane_color; diff --git a/selfdrive/ui/qt/spinner b/selfdrive/ui/qt/spinner index 2abccd2..ab69bdc 100755 Binary files a/selfdrive/ui/qt/spinner and b/selfdrive/ui/qt/spinner differ diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 0be42bd..b438c22 100755 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -118,8 +118,10 @@ void update_model(UIState *s, } // update path + // CLEARPILOT: read from frogpilotCarControl cereal message (was paramsMemory) + bool no_lat_lane_change = (*s->sm)["frogpilotCarControl"].getFrogpilotCarControl().getNoLatLaneChange(); float path; - if (paramsMemory.getBool("no_lat_lane_change")) { + if (no_lat_lane_change) { path = (float)LANE_CHANGE_NO_LAT_PATH_WIDTH / 20; // Release: better calc for EU users } else { path = (float)CENTER_LANE_WIDTH / 20; // Release: better calc for EU users @@ -439,7 +441,7 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", - "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", + "pandaStates", "peripheralState", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "uiPlan", "liveTorqueParameters", "frogpilotCarControl", "frogpilotDeviceState", "frogpilotPlan", "gpsLocation",