Files
clearpilot/selfdrive/thermald/fan_controller.py
T
brianhansonxyz 5d18ad1e72 park mode: pause self-driving processes, keep car ECU heartbeat
When the car is in park (ignition still on), pause everything that
isn't strictly needed and keep controlsd in a minimal-cycle keepalive
mode so the car's steering ECU keeps seeing the LFA/LKAS CAN-FD
messages and stays in tester mode (no steering fault on shift to drive).

controlsd:
- Detects park gear, writes ParkMode to /dev/shm/params.
- park_mode_tick(): publishes a do-nothing CarControl through
  self.card (CarController.update unconditionally appends the
  steering messages every cycle, which is the actual heartbeat).
  Still runs clearpilot_state_control so the LFA/debug button +
  ScreenDisplayMode keep working in park.
- After park→drive, stay in keepalive-tick mode until SubMaster
  reports all services healthy (an 8s hard cap as safety net).
  Avoids the burst of commIssue alerts that would otherwise fire
  while modeld/plannerd/paramsd/torqued/dmonitoringd/calibrationd/
  frogpilot_process spin back up from cold.

manager / process_config:
- New gating helpers _park_mode(), only_onroad_active,
  driverview_active, always_run_unless_parked.
- Re-gated to only_onroad_active: modeld, sensord, soundd, locationd,
  calibrationd, torqued, paramsd, plannerd, radard, speed_logicd.
- Re-gated to driverview_active: dmonitoringmodeld, dmonitoringd.
- frogpilot_process → always_run_unless_parked (preserves offroad
  behavior, only pauses when ignition+parked).
- controlsd stays plain only_onroad — it's the writer + heartbeat.
- ParkMode registered in params.cc, defaulted to "0" in manager_init.

thermald + fan_controller (broken-tree rule, ported):
- thermald subscribes to carState; passes standstill, is_parked,
  cruise_engaged into fan_controller.update.
- New fan range rules:
    parked        → 0-100% (no floor, full cooling)
    cruise on     → 30-100%
    standstill    → 10-100%
    moving        → 30-100%
    ignition off  → 0-30% (existing)

dashcamd: already park-aware via gear-driven trip lifecycle — no
change needed.

Verified: build clean. Launched on bench: park mode kicks in on
startup (gearShifter=unknown initially → eventually park),
modeld/plannerd/paramsd/torqued/calibrationd/frogpilot_process
correctly disappear from the manager process list, gpsd/dashcamd/
ui/controlsd/pandad stay alive, ParkMode=1 in /dev/shm/params.
2026-05-04 19:36:38 -05:00

61 lines
2.2 KiB
Python
Executable File

#!/usr/bin/env python3
from abc import ABC, abstractmethod
from openpilot.common.realtime import DT_TRML
from openpilot.common.numpy_fast import interp
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.controls.lib.pid import PIDController
class BaseFanController(ABC):
@abstractmethod
def update(self, cur_temp: float, ignition: bool, standstill: bool = False,
is_parked: bool = True, cruise_engaged: bool = False) -> int:
pass
class TiciFanController(BaseFanController):
def __init__(self) -> None:
super().__init__()
cloudlog.info("Setting up TICI fan handler")
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,
is_parked: bool = True, cruise_engaged: bool = False) -> int:
# CLEARPILOT fan range rules. neg_limit is the most-negative the PID can
# output (= max fan %); pos_limit is the inverse of the floor (= -min fan %).
# parked → 0-100% (full range, no floor)
# in drive + cruise engaged → 30-100%
# in drive + cruise off + standstill → 10-100%
# in drive + cruise off + moving → 30-100%
# ignition off → 0-30% (existing behavior)
if not ignition:
self.controller.neg_limit = -30
self.controller.pos_limit = 0
elif 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
elif standstill:
self.controller.neg_limit = -100
self.controller.pos_limit = -10
else:
self.controller.neg_limit = -100
self.controller.pos_limit = -30
if ignition != self.last_ignition:
self.controller.reset()
error = 70 - cur_temp
fan_pwr_out = -int(self.controller.update(
error=error,
feedforward=interp(cur_temp, [60.0, 100.0], [0, -100])
))
self.last_ignition = ignition
return fan_pwr_out