feat(sensors): temperature-driven fan controller with per-fan AC/Battery profiles#3198
Closed
Morteza-Rastgoo wants to merge 7 commits into
Closed
feat(sensors): temperature-driven fan controller with per-fan AC/Battery profiles#3198Morteza-Rastgoo wants to merge 7 commits into
Morteza-Rastgoo wants to merge 7 commits into
Conversation
…ource profiles Adds a proportional fan speed controller that keeps CPU temperature near a user-defined target, with separate settings for AC adapter and battery power sources. Architecture: - fanTempController.swift – FanTempController singleton (proportional ramp, IOKit power-source detection, 1 s tick gate, 100 RPM hysteresis, per-fan min/max clamping). Engine is inert when disabled — zero overhead in default config. - fanTempControllerSettings.swift – Settings panel with two sections (AC Adapter / Battery), each offering an enable toggle and a target-temperature slider (30–85 °C). - main.swift – registerFans() after reader init; processTick() in usageCallback; releaseAll() in willTerminate (restores automatic mode). - settings.swift – appends FanTempControllerSettingsView below existing sensor prefs. - popup.swift – FanView.setControlledByTempController() greys out manual slider and mode buttons while the engine is active, with a tooltip pointing to the controller. Compared to the curve-based approach in exelban#3191 this feature uses a single target-temperature knob (simpler UX) and adds power-source awareness: a cooler target on battery preserves charge while a higher threshold on AC lets users push performance without the fans running harder than necessary when idle. Tested on macOS 26.4.1, Apple Silicon M4 Max. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix SensorGroup.cpu → .CPU (enum case is uppercase) - Change acSettings/battSettings to var to allow mutation - Guard mode11be with swift(>=6.0) for SDK compatibility Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds FanTempControllerPopupView directly in the Sensors popup, below the fans section. The panel shows: - A header row '🌡 Fan Temp Control' with live CPU temp (e.g. 'CPU 72°C') - An '⚡ AC Adapter' row: enable toggle + target temp stepper - A '🔋 Battery' row: enable toggle + target temp stepper Steppers cover 30–85°C in 1-degree steps and write directly to FanTempController.shared, which ramps fan speed proportionally when the CPU exceeds the target. No navigation to Settings needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the separate popup panel with a proper 'Temp' mode button integrated directly into each fan's [Automatic | Manual | Temp] row. When Temp is selected: - SMC is set to automatic; FanTempController takes over - Two temperature sliders appear (⚡ AC Adapter, 🔋 Battery), 30–85°C - Controller ramps fan proportionally: at target temp = min RPM, 25°C above target = max RPM; hysteresis 100 RPM - Settings persist across launches per-fan in UserDefaults - Switching back to Auto or Manual releases SMC control immediately Also removed FanTempControllerSettings (settings panel) since all controls are now inline in the popup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause: isActive() returns false until a setFanSpeed/setFanMode call has been made (XPC connection is lazy). Replaced with isInstalled which checks the helper binary on disk directly. Also: - Show 'CPU: XX°C' in the temp slider view so users know what target to set - CPU temp turns orange when above target (fans will ramp) - Default targets lowered from 55/60°C to 50°C for both profiles Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New behavior: - HOT (CPU > target): fans blast to max within 3°C overshoot — immediate - COOL (CPU ≤ target): reduce by max 200 RPM per 500ms tick (~20s to full idle) - Tick interval halved to 500ms for faster thermal response This replaces the linear 25°C ramp with an asymmetric strategy: fast thermal protection, comfortable gradual wind-down. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…race, aggressive ramp
- Control signal changed from Airport (TW0P) to exhaust airflow wing sensors
(TaLW/TaRW), falling back to TaLP/TaRF then CPU max. These directly measure
the air that heats the user's lap instead of a single internal chip.
- Add TaLW / TaRW ('Airflow left/right wing') to values.swift sensor list.
- Popup temp-mode header now shows 'L: 32°C R: 32°C' (both vent temps) instead
of a single Airport readout; label turns orange when either side exceeds target.
- Remove proportional RPM ramp — controller now blasts fans to max the instant
temp > target (aggressive cool-down), then steps down 200 RPM per 500 ms tick.
- Fix critical race condition: controller no longer calls setFanMode(automatic)
for non-temp fans, which was silently overriding popup's Manual/Auto buttons.
- Use live fan.maxSpeed on every tick (never cached); matches turbo-button behaviour.
- 500 ms tick gate with newRPM != prev guard prevents flooding the helper's
serial smcQueue and keeps popup button latency near zero.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Author
|
🙂 |
marxo126
added a commit
to marxo126/stats
that referenced
this pull request
May 11, 2026
Adds the two pieces from Morteza-Rastgoo's PR exelban#3198 most relevant to laptop use: airflow vent sensors + power-source detection. Telemetry only — driver still die-temp by default. - values.swift: TaLW + TaRW (Airflow left/right wing) added alongside existing TaLP/TaRF. Apple Silicon platforms. - engine: vent_max derived from sensors with group=.sensor and name containing 'Airflow'. Power source detected via IOPSCopyPowerSourcesInfo. - telemetry CSV gains vent_max_temp, power_source columns. - Driver logic unchanged — die temp (max CPU/GPU) still primary. Vent telemetry gathered first to evaluate whether vent-driven mode is worth implementing for this hardware (M4 Mac, Mac16,5).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
An opt-in Fan Temperature Controller built into the Sensors popup. Instead of manually setting RPM, you pick a target exhaust temperature and the controller keeps fans as fast as needed to stay cool — with separate targets for AC Adapter and Battery, configurable per fan.
When disabled the engine is completely inert; existing manual/automatic fan behaviour is unchanged.
Key differences from #3191
How it works
Each fan in the Sensors popup gains a third mode button: Temp. Clicking it reveals two sliders (⚡ AC and 🔋 Battery), each controlling a target exhaust temperature from 30–85 °C.
Control signal — exhaust airflow vents (not CPU core temp):
TaLW/TaRW— left/right wing vent sensors (~32–34 °C at idle on M4 Max)TaLP/TaRFthen max CPU core tempAlgorithm:
controlTemp > target→ instantly set max RPM (forced mode via SMC)controlTemp ≤ target→ step down 200 RPM every 500 ms until automatic mode is restorednewRPM != prevguard prevent flooding the helper's serialsmcQueuesetFanModefor fans not in Temp mode — eliminates a race that would silently override manual speedwillTerminaterestores automatic mode on all controlled fansFiles changed
Modules/Sensors/fanTempController.swiftModules/Sensors/popup.swiftModules/Sensors/main.swiftprocessTick/releaseAllinto Sensors module lifecycle (+8 LOC)Modules/Sensors/values.swiftTaLW/TaRW(Airflow left/right wing) to Apple Silicon sensor list (+2 lines)Stats.xcodeproj/project.pbxprojfanTempController.swiftTesting
Verified on macOS 26.4.1, Apple Silicon M2: