A safety-focused Linux fan-control daemon and local web dashboard for compatible Clevo/Tongfang systems.
Important
Unofficial community project. This software is not affiliated with, endorsed by, sponsored by, or supported by Clevo, Tongfang, any laptop reseller, or any kernel-driver vendor. Use it at your own risk.
Fan Control provides automatic temperature curves, direct manual control, live CPU/GPU telemetry, and a responsive local dashboard. It is deliberately small, dependency-free, and bound to localhost.
- Silent, Balanced, Performance, custom-curve, manual, and firmware-auto modes
- Independent or linked CPU/GPU fan targets
- Critical-temperature override that bypasses the user noise cap
- Curve hysteresis to prevent rapid speed hunting
- Automatic handoff to firmware when temperature data becomes unavailable
- Live hwmon sensors with an NVIDIA
nvidia-smifallback - Thirty-minute CPU temperature, GPU temperature, and fan-duty history
- Persistent configuration with atomic writes
- Responsive dark/light dashboard and optional browser alerts
- Serialized EC access and clean daemon/dashboard ownership handoff
- Hardware-free demo mode for safe evaluation and UI development
Fan Control supports two hardware backends on Linux. It auto-detects which
one is present; use --backend or FAN_CONTROL_BACKEND to override.
| Requirement | Details |
|---|---|
| Hardware | Clevo/Tongfang-based system with a compatible EC interface |
| Backend | tuxedo_io (ioctls on a /dev/*_io device) or clevo_acpi (sysfs) |
| clevo_acpi | Requires clevo-acpi-dkms with fan duty attributes |
| Runtime | Python 3.10 or newer; standard library only |
| Privileges | Root access for EC reads and writes |
| Service manager | systemd for the included background service |
| NVIDIA telemetry | Optional; requires a working nvidia-smi command |
The tuxedo_io backend drives duty through ioctls on a character device
matching /dev/*_io. The clevo_acpi backend drives per-fan duty through
plain sysfs files under /sys/class/leds/clevo-acpi::kbd_backlight/device/,
and relies on that driver's kernel-side watchdog to return control to
firmware auto if the controlling process stops. The sysfs interface comes
from clevo-acpi-dkms
(GPL-2.0-or-later); its semantics were verified against that driver and the
clevo-control-panel
(GPL-3.0) reference daemon.
Hardware compatibility varies by model and firmware. Start with demo mode, then verify sensor readings and fan response before enabling the service.
Fan Control treats thermal control as a safety-critical path:
- Duty is authored as a percentage 0-100. The
tuxedo_iobackend maps this onto its native raw 0-198 domain at the edge, keeping the known-safe cap of 198; values near 200 are known to behave unpredictably on affected firmware. - At
critical_temp, both fans are commanded to 100% even when a lower noise cap is configured. - After three invalid temperature readings, the daemon returns control to the system firmware until valid telemetry returns.
- On the
clevo_acpibackend, the kernel-side watchdog independently releases to firmware auto if the controlling process stops renewing a manual override, so a crashed or killed daemon cannot leave a fan stuck at a stale speed. - The dashboard and daemon never intentionally own the EC interface at the same time.
Caution
Confirm the reported temperatures and physical fan response on your exact machine. Incorrect low-level fan control can cause overheating or hardware damage.
Clone the repository and install the two executables, the backend module, and the service unit:
git clone https://github.com/vindeckyy/fan-control.git
cd fan-control
sudo install -Dm755 fan-daemon.py /usr/local/bin/fan-daemon
sudo install -Dm755 fan-gui.py /usr/local/bin/fan-gui
sudo install -Dm644 fan_backend.py /usr/local/bin/fan_backend.py
sudo install -Dm644 fan-daemon.service /etc/systemd/system/fan-daemon.service
sudo systemctl daemon-reload
sudo systemctl enable --now fan-daemonCheck the service after installation:
systemctl status fan-daemon
journalctl -u fan-daemon -n 50 --no-pagerOpen the local dashboard:
sudo fan-guiThe dashboard listens only on http://127.0.0.1:4444. While it is open, it
temporarily stops the daemon and takes ownership of the EC interface. Closing
the dashboard releases control and restarts the daemon.
Run the daemon directly with a built-in profile:
sudo fan-daemon --profile balancedPreview the complete dashboard without root or compatible hardware:
FAN_CONTROL_CONFIG=/tmp/fan-control-demo.json fan-gui --demoPreview daemon decisions without opening an EC device:
fan-daemon --dry-run --profile silentBoth programs read /etc/fan-control.json. The dashboard writes this file
atomically when settings change.
{
"profile": "balanced",
"max_duty": 100,
"hysteresis": 5,
"critical_temp": 95
}| Key | Default | Purpose |
|---|---|---|
profile |
balanced |
Active automatic curve |
curve |
built-in | Custom [temperature, duty] points |
max_duty |
100 |
Normal-operation noise cap, as a percentage |
hysteresis |
5 |
Minimum duty change before curve updates |
critical_temp |
95 |
Temperature that forces maximum safe duty |
Backend selection is automatic: clevo-acpi sysfs is preferred when its fan
attributes exist, otherwise tuxedo_io on the sole /dev/*_io device. Set
FAN_CONTROL_BACKEND or pass --backend to override. For tuxedo_io, set
FAN_CONTROL_DEVICE or pass --device when multiple candidates exist:
sudo FAN_CONTROL_BACKEND=clevo_acpi fan-gui
sudo FAN_CONTROL_DEVICE=/dev/example_io fan-guihwmon / NVIDIA telemetry ──► temperature selection ──► curve + safety policy
│
▼
┌────── clevo_acpi sysfs ───────┐ ◄── duty target ──┘
│ │
backend ┼──── tuxedo_io ioctls ─────────┤ ◄── percent duty
│ │
└──── demo (simulated) ──────────┘
│
▼
firmware auto ◄── ownership handoff ◄── watchdog / release
fan_backend.pydefines the backend abstraction and three implementations.fan-daemon.pyowns automatic background control.fan-gui.pyserves the local dashboard and interactive control API.fan-daemon.servicestarts the daemon at boot.test_fan_control.pycovers interpolation, safety bounds, curve validation, backend discovery, duty conversion, legacy config migration, dashboard contracts, and NVIDIA parsing.
Run the built-in diagnostic to see which backend is available:
sudo fan-daemon --diagnoseFor tuxedo_io, confirm a character device exists:
ls -l /dev/*_ioFor clevo_acpi, confirm the fan attributes exist:
ls /sys/class/leds/clevo-acpi::kbd_backlight/device/fan*_manual_dutyCheck whether another process is using port 4444, then run the dashboard in
a terminal to retain the error message:
ss -ltnp 'sport = :4444'
sudo fan-gui --no-browserVerify that the driver can report temperature:
nvidia-smi --query-gpu=index,temperature.gpu,name --format=csv,noheader,nounitsThe project intentionally uses only the Python standard library.
python3 -m py_compile fan_backend.py fan-daemon.py fan-gui.py test_fan_control.py
python3 -m unittest -vSee CONTRIBUTING.md before proposing hardware-facing changes. Security issues should follow SECURITY.md.
Clevo and Tongfang names are used only to describe hardware compatibility. All product names and trademarks belong to their respective owners. This repository provides no manufacturer warranty, certification, or support.

