C3/C3X Aggressive Fan Curve + Toggle

C3 and C3X Cooling Curve. 10 - 16 C cooler than stock curve.
This commit is contained in:
whoisdomi
2026-09-02 15:48:07 -05:00
parent b7775991bf
commit 72077b2f20
6 changed files with 38 additions and 10 deletions
+1
View File
@@ -441,6 +441,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"IncreaseFollowingSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"AggressiveCoolingEnabled", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"IncreaseThermalLimits", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"IssueReported", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
{"KonikDongleId", {PERSISTENT, STRING, "", "", 0}},
@@ -4652,6 +4652,17 @@
"parent_key": "DeviceManagement",
"settings_tier": "simple"
},
{
"key": "AggressiveCoolingEnabled",
"label": "C3/C3X Aggressive Cooling",
"description": "Runs the fan on a lower, more aggressive temperature curve: the fan ramps up sooner and reacts faster to rising temperature, keeping the device meaningfully cooler on hot days at the cost of more fan noise.",
"picker_description": "Cooler-running, louder fan curve for comma 3/3X.",
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "DeviceManagement",
"settings_tier": "simple",
"requires_capability": "IsTiciOrTizi"
},
{
"key": "UseKonikServer",
"label": "Use Konik Server",
+1
View File
@@ -1004,6 +1004,7 @@ class StarPilotVariables:
)
toggle.device_shutdown_time = device_shutdown_seconds(device_shutdown_hours)
toggle.increase_thermal_limits = self.get_value("IncreaseThermalLimits", condition=device_management)
toggle.aggressive_cooling = self.get_value("AggressiveCoolingEnabled", condition=device_management)
toggle.low_voltage_shutdown = self.get_value("LowVoltageShutdown", cast=float, condition=device_management, min=VBATT_PAUSE_CHARGING, max=12.5)
# Keep force-onroad desktop simulations from polluting logs, but never disable
# loggerd/encoderd on real devices because that breaks route continuity/uploads.
@@ -4147,6 +4147,9 @@ def _get_vehicle_parked():
except Exception:
return False
def _get_is_tici_or_tizi():
return HARDWARE.get_device_type() in ("tici", "tizi")
def _get_alpha_longitudinal_available():
cp_bytes = _safe_params_get_live_raw("CarParamsPersistent")
if not cp_bytes:
@@ -6206,6 +6209,7 @@ def setup(app):
result["VehicleParked"] = _get_vehicle_parked()
result["AlphaLongitudinalAvailable"] = _get_alpha_longitudinal_available()
result["HasRivianAngleHarness"] = _get_has_rivian_angle_harness()
result["IsTiciOrTizi"] = _get_is_tici_or_tizi()
for key in ("CalibratedLateralAcceleration", "CalibrationProgress"):
try:
+20 -9
View File
@@ -7,13 +7,18 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.pid import PIDController
from openpilot.system.hardware import HARDWARE
# raise fan setpoint on tici/tizi to reduce noise
# after raising LMH threshold in AGNOS 18.1 to prevent CPU throttling
OFFSET = 0 if HARDWARE.get_device_type() == "mici" else 5
# comma 4 (mici) always uses the stock curve; comma 3/3X (tici/tizi) can opt into a more
# aggressive, cooler-targeting curve via the "C3/C3X Aggressive Cooling" toggle
IS_MICI = HARDWARE.get_device_type() == "mici"
# original comma/sunnypilot curve: quieter on tici/tizi (higher setpoint) than on mici
STOCK_CURVE = dict(offset=0, k_p=0, ff_low=60.0, ff_high=100.0) if IS_MICI else \
dict(offset=5, k_p=0, ff_low=65.0, ff_high=105.0)
AGGRESSIVE_CURVE = dict(offset=-5, k_p=1.0, ff_low=55.0, ff_high=80.0)
class BaseFanController(ABC):
@abstractmethod
def update(self, cur_temp: float, ignition: bool) -> int:
def update(self, cur_temp: float, ignition: bool, aggressive_cooling: bool = False) -> int:
pass
@@ -23,20 +28,26 @@ class TiciFanController(BaseFanController):
cloudlog.info("Setting up TICI fan handler")
self.last_ignition = False
self.controller = PIDController(k_p=0, k_i=4e-3, rate=(1 / DT_HW))
self.last_aggressive_cooling = False
self.controller = PIDController(k_p=STOCK_CURVE["k_p"], k_i=4e-3, rate=(1 / DT_HW))
def update(self, cur_temp: float, ignition: bool, aggressive_cooling: bool = False) -> int:
use_aggressive = aggressive_cooling and not IS_MICI
curve = AGGRESSIVE_CURVE if use_aggressive else STOCK_CURVE
def update(self, cur_temp: float, ignition: bool) -> int:
self.controller.pos_limit = 100 if ignition else 30
self.controller.neg_limit = 30 if ignition else 0
self.controller._k_p = [[0], [curve["k_p"]]]
if ignition != self.last_ignition:
if ignition != self.last_ignition or use_aggressive != self.last_aggressive_cooling:
self.controller.reset()
error = cur_temp - (75 + OFFSET)
error = cur_temp - (75 + curve["offset"])
fan_pwr_out = int(self.controller.update(
error=error,
feedforward=np.interp(cur_temp, [60.0 + OFFSET, 100.0 + OFFSET], [0, 100])
feedforward=np.interp(cur_temp, [curve["ff_low"], curve["ff_high"]], [0, 100])
))
self.last_ignition = ignition
self.last_aggressive_cooling = use_aggressive
return fan_pwr_out
+1 -1
View File
@@ -460,7 +460,7 @@ def hardware_thread(end_event, hw_queue) -> None:
msg.deviceState.maxTempC = all_comp_temp
if fan_controller is not None:
msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"])
msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"], starpilot_toggles.aggressive_cooling)
# StarPilot variables
if starpilot_toggles.increase_thermal_limits: