fw-fanctrl is a Python CLI service for Linux that controls Framework Laptop fan speed according to a configurable speed/temperature curve. Developed by community member Jean (GitHub: TamtamHero), it was originally created in April 2022 to address the overly aggressive default fan behaviour on the Framework Laptop 13 Intel, which many users found too loud for light workloads12.
Under the hood, fw-fanctrl communicates with Framework's Embedded Controller (EC), overriding the built-in fan curve with user-defined strategies. As of v1.1.0 (June 2026), it uses framework_tool (Framework's official framework-system Rust crate) for EC access, replacing the earlier ectool dependency. It is compatible with all Framework Laptop 13 and 16 models, both AMD and Intel CPUs, with or without a discrete GPU2. When the service is stopped or paused, fans revert to their default behaviour2.
How It Works
fw-fanctrl reads CPU temperature sensors, computes a moving average to smooth out spikes, interpolates the corresponding fan speed from the active strategy's speed curve, and sends the duty percentage to the EC. The service runs as a systemd daemon and communicates with CLI commands through a Unix socket2.
Temperature Pipeline
- Sensor reading — CPU temperature is read from
lm-sensors / hwmon
- Moving average — temperature is averaged over a configurable window (default 20 seconds) to prevent rapid fan oscillation
- Curve interpolation — the averaged temperature is mapped to a fan speed percentage via the active strategy's speed curve
- EC command — fan duty is set via
framework_tool (v1.1.0+), or ectool fanduty on older versions
Features
| Feature | Details |
|---|
| Language | Python (≥ 3.12) |
| EC communication | framework_tool (v1.1.0+), or ectool (DHowett's build) on older versions |
| Speed curve | Configurable multi-point temperature→speed mapping (0.01°C precision) |
| Strategies | Multiple named fan profiles, switchable at runtime via CLI |
| AC/battery profiles | Separate strategies for charging vs discharging |
| Moving average | Configurable smoothing window (1–100 seconds) |
| Update frequency | Configurable adjustment interval (1–10 seconds) |
| Suspend support | Fan properly stopped on sleep/resume |
| Service | systemd daemon with socket-based IPC |
| Supported models | FW13 Intel (11th–13th gen), FW13 AMD (7040, AI 300), FW16 AMD (with/without dGPU) |
Installation
Dependencies
| Dependency | Version | Notes |
|---|
| Linux kernel | ≥ 6.11.x | Required for EC access |
| Python | ≥ 3.12.x | |
| framework_tool | ≥ 0.6.x | Framework's official EC tool, used from v1.1.0 (June 2026) |
| ectool | DHowett build #899 | Used by versions before v1.1.0; still works as fallback |
| lm-sensors | any | CPU temperature readings |
Install Script (Generic Linux)
git clone https://github.com/TamtamHero/fw-fanctrl.git
cd fw-fanctrl
sudo ./install.sh
The install script installs the Python package, creates the systemd service, and enables it2.
Distribution Packages
Configuration
Configuration is stored in /etc/fw-fanctrl/config.json3.
Built-in Strategies
The default configuration includes several strategies ranked from quietest to most aggressive3:
| Strategy | Description |
|---|
lazy | Very quiet; fan off until high temperature |
quiet | Low fan speeds with slow ramp-up |
medium | Balanced comfort and cooling |
agile | Faster response to temperature changes |
very-agile | Rapid fan response |
Custom Strategy Example
{
"strategies": {
"my-custom": {
"fanSpeedUpdateFrequency": 5,
"movingAverageInterval": 30,
"speedCurve": [
{ "temp": 0, "speed": 0 },
{ "temp": 45, "speed": 0 },
{ "temp": 55, "speed": 15 },
{ "temp": 65, "speed": 30 },
{ "temp": 75, "speed": 60 },
{ "temp": 85, "speed": 100 }
]
}
},
"defaultStrategy": "my-custom",
"strategyOnDischarging": "lazy"
}
Strategy Parameters
| Parameter | Default | Range | Description |
|---|
speedCurve | (required) | ≥ 1 point | Array of {temp, speed} objects defining the fan curve |
fanSpeedUpdateFrequency | 5 | 1–10 | Seconds between fan speed adjustments |
movingAverageInterval | 20 | 1–100 | Seconds over which temperature is averaged |
AC/Battery Profiles
The defaultStrategy is used on AC power, while strategyOnDischarging activates automatically on battery. This allows quieter operation when mobile (more heat, less noise, better battery) and more aggressive cooling when plugged in3.
CLI Commands
fw-fanctrl lazy # Switch to "lazy" strategy temporarily
fw-fanctrl medium # Switch to "medium" strategy
fw-fanctrl reload # Reload config from disk
fw-fanctrl pause # Pause service (fans revert to EC default)
fw-fanctrl resume # Resume service
fw-fanctrl reset # Reset to default strategy from config
Version History
| Version | Date | Changes |
|---|
| 1.0.0 | February 2025 | Major rewrite; socket-based IPC, JSON config schema, strategy live-switching |
| 1.0.1 | April 2025 | Auto tag and release CI |
| 1.0.3 | April 2025 | Script installation path fixes |
| 1.0.4 | January 2026 | AUR package update |
| 1.0.5 | June 3, 2026 | Sleep-service init fix, improved sleep/resume handling |
| 1.1.0 | June 6, 2026 | Replaced ectool with framework_tool for EC access; removed DHowett ectool dependency |
Development Milestones
- April 2022 — Initial release as ~100-line Python script for FW13 Intel1
- October 2022 — Live strategy switching via CLI, suspend/resume fix1
- November 2022 — 12th-gen Intel CPU temperature fix1
- June 2023 — Cinnamon applet by not-a-feature1
- March 2024 — AMD and Framework 16 support (dual-fan, dGPU), contributed by hasechris1
- June 2024 — GNOME Shell extension by joax; cross-DE tray UI by Wai1
- February 2025 — v1.0.0 release with major rewrite (socket IPC, JSON schema, modular architecture)
- June 2026 — v1.1.0 release: migrated from ectool to Framework's official
framework_tool
Third-Party Projects
Multiple community projects have been built on top of fw-fanctrl to provide GUI frontends2:
Technical Details
| Detail | Value |
|---|
| License | BSD 3-Clause2 |
| Stars | 4572 |
| Forks | 652 |
| Contributors | 20 (top: TamtamHero, leopoldhub, Svenum, emrebicer)2 |
| Releases | 6 (latest: v1.1.0, June 6, 2026)2 |
| Created | April 4, 20222 |
| Community thread | 27 replies, 14,000+ views (thread #17208)1 |