feat: 4Hz fan control with gear/cruise-aware clamps; move hot signals to cereal

Fan control rework (thermald → 4Hz):
- DT_TRML 0.5s → 0.25s (thermald loop + fan PID now at 4Hz)
- New clamp rules based on (gear, cruise_engaged, standstill):
    parked                                → 0-100%
    in drive + cruise engaged (any speed) → 30-100%
    in drive + cruise off + standstill    → 10-100%
    in drive + cruise off + moving        → 30-100%
- thermald now reads gearShifter (via carState) and controlsState.enabled,
  passes them to fan_controller.update()
- Removed BENCH_MODE special case — new rules cover bench automatically
- Removed ignition-based branches — gear is the correct signal

System health overlay:
- Subscribed UI to peripheralState so we can read fanSpeedRpm
- Added FAN row: actual fan% (RPM / 65) to sit alongside LAG/DROP/TEMP/CPU/MEM.
  Shows the real fan output vs. what the PID is asking for.

Migrate hot signals from paramsMemory to cereal (frogpilotCarControl):
- Added latRequested @3 and noLatLaneChange @4 to FrogPilotCarControl schema
- controlsd sets FPCC.latRequested / FPCC.noLatLaneChange (send-on-change
  already gates the IPC)
- modeld reads from sm['frogpilotCarControl'] (added to its subscribers)
  instead of paramsMemory (saves ~20 file-read syscalls/sec)
- carcontroller reads from frogpilot_variables (set in-process by controlsd)
  instead of paramsMemory (saves ~100 file-read syscalls/sec in 100Hz path).
  Dropped carcontroller's now-unused Params instance and import.
- UI (ui.cc, onroad.cc) reads from sm['frogpilotCarControl'].noLatLaneChange
- Removed LatRequested and no_lat_lane_change param registrations + defaults

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 16:01:39 -05:00
parent 25b4672fda
commit 4dae5804ab
15 changed files with 77 additions and 65 deletions

View File

@@ -101,7 +101,7 @@ ClearPilot uses memory params (`/dev/shm/params/d/`) for UI toggles that should
- **Python access**: Use `Params("/dev/shm/params")` - **Python access**: Use `Params("/dev/shm/params")`
- **Defaults**: Set in `manager_init()` via `Params("/dev/shm/params").put(key, value)` - **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 - **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. - **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 ### Building Native (C++) Processes
@@ -311,8 +311,7 @@ The OMX encoder (`selfdrive/frogpilot/screenrecorder/omx_encoder.cc`) was ported
### Params ### Params
- `DashcamDebug` — when `"1"`, dashcamd runs even without car connected (for bench testing) - `DashcamShutdown` (memory param) — set by thermald before power-off, dashcamd acks by clearing it
- `DashcamShutdown` — set by thermald before power-off, dashcamd acks by clearing it
- `DashcamState` (memory param) — "stopped", "waiting", or "recording" — published every 5s - `DashcamState` (memory param) — "stopped", "waiting", or "recording" — published every 5s
- `DashcamFrames` (memory param) — per-trip encoded frame count, resets each trip — 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 ### 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 ### GPS

View File

@@ -12,6 +12,9 @@ struct FrogPilotCarControl @0x81c2f05a394cf4af {
alwaysOnLateral @0 :Bool; alwaysOnLateral @0 :Bool;
speedLimitChanged @1 :Bool; speedLimitChanged @1 :Bool;
trafficModeActive @2 :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 { struct FrogPilotCarState @0xaedffd8f31e7b55d {

View File

@@ -109,7 +109,6 @@ std::unordered_map<std::string, uint32_t> keys = {
{"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"CurrentBootlog", PERSISTENT}, {"CurrentBootlog", PERSISTENT},
{"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"DashcamDebug", PERSISTENT},
{"DashcamFrames", PERSISTENT}, {"DashcamFrames", PERSISTENT},
{"DashcamState", PERSISTENT}, {"DashcamState", PERSISTENT},
{"DashcamShutdown", CLEAR_ON_MANAGER_START}, {"DashcamShutdown", CLEAR_ON_MANAGER_START},
@@ -201,7 +200,6 @@ std::unordered_map<std::string, uint32_t> keys = {
{"TelemetryEnabled", PERSISTENT}, {"TelemetryEnabled", PERSISTENT},
{"Timezone", PERSISTENT}, {"Timezone", PERSISTENT},
{"TrainingVersion", PERSISTENT}, {"TrainingVersion", PERSISTENT},
{"LatRequested", PERSISTENT},
{"ModelFps", PERSISTENT}, {"ModelFps", PERSISTENT},
{"ModelStandby", PERSISTENT}, {"ModelStandby", PERSISTENT},
{"ModelStandbyTs", PERSISTENT}, {"ModelStandbyTs", PERSISTENT},
@@ -510,8 +508,6 @@ std::unordered_map<std::string, uint32_t> keys = {
{"WheelIcon", PERSISTENT}, {"WheelIcon", PERSISTENT},
{"WheelSpeed", PERSISTENT}, {"WheelSpeed", PERSISTENT},
// Clearpilot
{"no_lat_lane_change", PERSISTENT},
}; };
} // namespace } // namespace

View File

@@ -12,7 +12,7 @@ from openpilot.system.hardware import PC
# time step for each process # time step for each process
DT_CTRL = 0.01 # controlsd DT_CTRL = 0.01 # controlsd
DT_MDL = 0.05 # model 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 DT_DMON = 0.05 # driver monitoring

View File

@@ -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.hyundaicanfd import CanBus
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR
from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.car.interfaces import CarControllerBase
from openpilot.common.params import Params
VisualAlert = car.CarControl.HUDControl.VisualAlert VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState LongCtrlState = car.CarControl.Actuators.LongControlState
@@ -57,9 +56,6 @@ class CarController(CarControllerBase):
self.car_fingerprint = CP.carFingerprint self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0 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): def update(self, CC, CS, now_nanos, frogpilot_variables):
actuators = CC.actuators actuators = CC.actuators
@@ -116,7 +112,8 @@ class CarController(CarControllerBase):
hda2_long = hda2 and self.CP.openpilotLongitudinalControl hda2_long = hda2 and self.CP.openpilotLongitudinalControl
# steering control # 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)) 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 # prevent LFA from activating on HDA2 by sending "no lane lines detected" to ADAS ECU

Binary file not shown.

View File

@@ -279,12 +279,12 @@ int main(int argc, char *argv[]) {
// Check for graceful shutdown request (every ~1 second) // Check for graceful shutdown request (every ~1 second)
if (++param_check_counter >= CAMERA_FPS) { if (++param_check_counter >= CAMERA_FPS) {
param_check_counter = 0; param_check_counter = 0;
if (params.getBool("DashcamShutdown")) { if (params_memory.getBool("DashcamShutdown")) {
LOGW("dashcamd: shutdown requested"); LOGW("dashcamd: shutdown requested");
if (state == RECORDING || state == IDLE_TIMEOUT) { if (state == RECORDING || state == IDLE_TIMEOUT) {
close_trip(); close_trip();
} }
params.putBool("DashcamShutdown", false); params_memory.putBool("DashcamShutdown", false);
LOGW("dashcamd: shutdown ack sent, exiting"); LOGW("dashcamd: shutdown ack sent, exiting");
break; break;
} }

View File

@@ -172,9 +172,8 @@ class Controls:
self.not_running_prev = None self.not_running_prev = None
self.lat_requested_ts = 0 # CLEARPILOT: timestamp when lat was first requested (ramp-up delay + comm issue suppression) 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_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) # 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._last_fpcc_send_frame = 0
self.steer_limited = False self.steer_limited = False
self.desired_curvature = 0.0 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 # 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. # 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() now_ts = time.monotonic()
if lat_requested != self.prev_lat_requested: if lat_requested and not self.prev_lat_requested:
self.params_memory.put_bool("LatRequested", lat_requested) self.lat_requested_ts = now_ts
if lat_requested:
self.lat_requested_ts = now_ts
self.prev_lat_requested = lat_requested 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.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 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 no_lat_lane_change = model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change
if no_lat_lane_change: if no_lat_lane_change:
CC.latActive = False CC.latActive = False
# CLEARPILOT: only write on change — per-cycle atomic writes were eating ~15% CPU # CLEARPILOT: noLatLaneChange now lives on frogpilotCarControl (see update_frogpilot_variables).
if no_lat_lane_change != self.prev_no_lat_lane_change: # Also mirrored to frogpilot_variables so in-process carcontroller reads it without IPC.
self.params_memory.put_bool("no_lat_lane_change", no_lat_lane_change) self.FPCC.noLatLaneChange = no_lat_lane_change
self.prev_no_lat_lane_change = no_lat_lane_change self.frogpilot_variables.no_lat_lane_change = no_lat_lane_change
if CS.leftBlinker or CS.rightBlinker: if CS.leftBlinker or CS.rightBlinker:
self.last_blinker_frame = self.sm.frame 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") 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) # 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: if fpcc_tuple != self._prev_fpcc or (self.sm.frame - self._last_fpcc_send_frame) >= 100:
fpcc_send = messaging.new_message('frogpilotCarControl') fpcc_send = messaging.new_message('frogpilotCarControl')
fpcc_send.valid = CS.canValid fpcc_send.valid = CS.canValid

View File

@@ -89,7 +89,7 @@ def manager_init(frogpilot_functions) -> None:
params_memory.put("VpnEnabled", "1") params_memory.put("VpnEnabled", "1")
params_memory.put("DashcamFrames", "0") params_memory.put("DashcamFrames", "0")
params_memory.put("DashcamState", "stopped") params_memory.put("DashcamState", "stopped")
params_memory.put("LatRequested", "0") params_memory.put("DashcamShutdown", "0")
params_memory.put("ModelFps", "20") params_memory.put("ModelFps", "20")
params_memory.put("ModelStandby", "0") params_memory.put("ModelStandby", "0")
params_memory.put("ShutdownTouchReset", "0") params_memory.put("ShutdownTouchReset", "0")
@@ -178,7 +178,6 @@ def manager_init(frogpilot_functions) -> None:
("DisableOpenpilotLongitudinal", "0"), ("DisableOpenpilotLongitudinal", "0"),
("DisableVTSCSmoothing", "0"), ("DisableVTSCSmoothing", "0"),
("DisengageVolume", "100"), ("DisengageVolume", "100"),
("DashcamDebug", "1"),
("TelemetryEnabled", "0"), ("TelemetryEnabled", "0"),
("DragonPilotTune", "0"), ("DragonPilotTune", "0"),
("DriverCamera", "0"), ("DriverCamera", "0"),

View File

@@ -169,7 +169,7 @@ def main(demo=False):
# messaging # messaging
pm = PubMaster(["modelV2", "cameraOdometry"]) 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() publish_state = PublishState()
params = Params() params = Params()
@@ -245,10 +245,12 @@ def main(demo=False):
# Full 20fps: lat requested or lane changing # Full 20fps: lat requested or lane changing
# Reduced 4fps: not lat requested, not standstill (driving without cruise) # Reduced 4fps: not lat requested, not standstill (driving without cruise)
# Standby 0fps: standstill # 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. # controlsd actually engages steering — gives downstream services time to stabilize.
lat_active = params_memory.get_bool("LatRequested") # CLEARPILOT: both fields migrated from paramsMemory to cereal (no per-cycle file reads)
lane_changing = params_memory.get_bool("no_lat_lane_change") fpcc = sm['frogpilotCarControl']
lat_active = fpcc.latRequested
lane_changing = fpcc.noLatLaneChange
standstill = sm['carState'].standstill standstill = sm['carState'].standstill
calibrating = sm['liveCalibration'].calStatus != log.LiveCalibrationData.Status.calibrated calibrating = sm['liveCalibration'].calStatus != log.LiveCalibrationData.Status.calibrated
full_rate = lat_active or lane_changing or calibrating full_rate = lat_active or lane_changing or calibrating

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from openpilot.common.realtime import DT_TRML 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.common.swaglog import cloudlog
from openpilot.selfdrive.controls.lib.pid import PIDController from openpilot.selfdrive.controls.lib.pid import PIDController
BENCH_MODE = os.environ.get("BENCH_MODE") == "1"
class BaseFanController(ABC): class BaseFanController(ABC):
@abstractmethod @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 pass
@@ -23,29 +22,27 @@ class TiciFanController(BaseFanController):
self.last_ignition = False self.last_ignition = False
self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_TRML)) 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: def update(self, cur_temp: float, ignition: bool, standstill: bool = False,
# CLEARPILOT: bench mode always uses normal onroad fan range (30-100%) is_parked: bool = True, cruise_engaged: bool = False) -> int:
if BENCH_MODE and ignition: # 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.neg_limit = -100
self.controller.pos_limit = -30 self.controller.pos_limit = -30
# CLEARPILOT: at standstill below 74°C, clamp to 0-30% (quiet) elif standstill:
# 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:
self.controller.neg_limit = -100 self.controller.neg_limit = -100
self.controller.pos_limit = 0 self.controller.pos_limit = -10
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: else:
self.controller.neg_limit = -30 self.controller.neg_limit = -100
self.controller.pos_limit = 0 self.controller.pos_limit = -30
if ignition != self.last_ignition: if ignition != self.last_ignition:
self.controller.reset() self.controller.reset()

View File

@@ -292,8 +292,18 @@ def thermald_thread(end_event, hw_queue) -> None:
msg.deviceState.maxTempC = all_comp_temp msg.deviceState.maxTempC = all_comp_temp
if fan_controller is not None: if fan_controller is not None:
standstill = sm['carState'].standstill if sm.seen['carState'] else False # CLEARPILOT: fan rules based on gear (parked vs drive) and cruise-engaged state
msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"], standstill) 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)) 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: 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): if power_monitor.should_shutdown(onroad_conditions["ignition"], in_car, off_ts, started_seen):
cloudlog.warning(f"shutting device down, offroad since {off_ts}") cloudlog.warning(f"shutting device down, offroad since {off_ts}")
# CLEARPILOT: signal dashcamd to close recording gracefully before power-off # 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 deadline = time.monotonic() + 15.0
while time.monotonic() < deadline: while time.monotonic() < deadline:
if not params.get_bool("DashcamShutdown"): if not params_memory.get_bool("DashcamShutdown"):
cloudlog.info("dashcamd shutdown ack received") cloudlog.info("dashcamd shutdown ack received")
break break
time.sleep(0.5) time.sleep(0.5)

View File

@@ -79,7 +79,8 @@ void OnroadWindow::updateState(const UIState &s) {
nvg->update(); // CLEARPILOT: force repaint every frame for HUD elements nvg->update(); // CLEARPILOT: force repaint every frame for HUD elements
QColor bgColor = bg_colors[s.status]; 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]; bgColor = bg_colors[STATUS_DISENGAGED];
} }
@@ -753,6 +754,7 @@ void AnnotatedCameraWidget::drawHealthMetrics(QPainter &p) {
auto cs = sm["controlsState"].getControlsState(); auto cs = sm["controlsState"].getControlsState();
auto ds = sm["deviceState"].getDeviceState(); auto ds = sm["deviceState"].getDeviceState();
auto mv = sm["modelV2"].getModelV2(); auto mv = sm["modelV2"].getModelV2();
auto ps = sm["peripheralState"].getPeripheralState();
float lag_ms = cs.getCumLagMs(); float lag_ms = cs.getCumLagMs();
float drop_pct = mv.getFrameDropPerc(); float drop_pct = mv.getFrameDropPerc();
@@ -760,6 +762,8 @@ void AnnotatedCameraWidget::drawHealthMetrics(QPainter &p) {
int mem_pct = ds.getMemoryUsagePercent(); int mem_pct = ds.getMemoryUsagePercent();
int cpu_pct = 0; int cpu_pct = 0;
for (auto v : ds.getCpuUsagePercent()) cpu_pct = std::max(cpu_pct, (int)v); 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) { auto color_for = [](float v, float warn, float crit) {
if (v >= crit) return QColor(0xff, 0x50, 0x50); // red 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)}, {"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)}, {"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)}, {"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(); p.save();
@@ -885,7 +890,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
// paint center lane path // paint center lane path
// QColor bg_colors[CHANGE_LANE_PATH_COLOR]; // 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; QColor center_lane_color;

Binary file not shown.

View File

@@ -118,8 +118,10 @@ void update_model(UIState *s,
} }
// update path // update path
// CLEARPILOT: read from frogpilotCarControl cereal message (was paramsMemory)
bool no_lat_lane_change = (*s->sm)["frogpilotCarControl"].getFrogpilotCarControl().getNoLatLaneChange();
float path; 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 path = (float)LANE_CHANGE_NO_LAT_PATH_WIDTH / 20; // Release: better calc for EU users
} else { } else {
path = (float)CENTER_LANE_WIDTH / 20; // Release: better calc for EU users 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) { UIState::UIState(QObject *parent) : QObject(parent) {
sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({ sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState",
"pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "pandaStates", "peripheralState", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2",
"wideRoadCameraState", "managerState", "uiPlan", "liveTorqueParameters", "wideRoadCameraState", "managerState", "uiPlan", "liveTorqueParameters",
"frogpilotCarControl", "frogpilotDeviceState", "frogpilotPlan", "frogpilotCarControl", "frogpilotDeviceState", "frogpilotPlan",
"gpsLocation", "gpsLocation",