SYS.ACTIVE
Tharana-dev
CPU: 12% | MEM: 44%
[ SYSTEMS / TELEMETRY ]

Decoding Thermals

CPU temps, fan speeds, and the WSL barrier.

The next step in my Telemetry Engine was Thermals. Since Linux directly exposes hardware sensors, I planned to build the Linux implementation first, and tackle Windows/macOS later.

You already know that Linux exposes hardware through the /sys directory. For power, we used /sys/class/power_supply/. For thermal sensors, Linux uses /sys/class/hwmon/ (Hardware Monitoring).

The Hardware Maze

Inside /sys/class/hwmon/, you will find folders named hwmon0, hwmon1, hwmon2, etc.

But here is the catch: The numbers are not fixed. On one laptop, hwmon0 might be the CPU. On another, it might be a hard drive or a motherboard sensor. If you hardcode hwmon0 in your script, your app will break on a different machine.

How do we know which folder is which? Every hwmonX folder contains a file called name. If you read that file, it tells you exactly what driver it is:

  • coretemp (Intel CPU)
  • k10temp (AMD CPU)
  • amdgpu or i915 (GPU)
  • thinkpad or acpitz (Laptop motherboards)

The Files Inside

Once you find the right folder, you read the raw values:

  • Temperature: temp1_input (Returns millidegrees. E.g., 45000 means 45.0°C).
  • Fan Speed: fan1_input (Returns RPM).

Just like /proc, /sys is a virtual filesystem. The kernel drivers for your motherboard update these text files every second. Reading temp1_input is a direct read from the hardware, which is why it's so fast and has zero dependencies.

The Trap of Absolute Paths

While writing the script, I hit a FileNotFoundError immediately. The reason? I wrote the path as 'sys/class/hwmon/'.

Important: The base path must be '/sys/class/hwmon/' with a leading slash. Without the slash, Python looks for a folder named sys inside your current directory, instead of the root of the filesystem.

The WSL Barrier

After fixing the path, I ran the script inside WSL (Windows Subsystem for Linux) to see my CPU temperature. Instead of a temperature, I got this output:

--- hwmon0 contents: ['uevent', 'power', 'device', 'subsystem', 'name'] ---
name: AC1

--- hwmon1 contents: ['uevent', 'in0_input', 'power', 'device', 'power1_input', 'subsystem', 'name'] ---
in0_input: 5000
power1_input: 0
name: BAT1

There was no coretemp. There was no temp1_input. I only saw the battery and AC adapter.

Why? Because WSL is a virtual machine running inside Windows. The Windows host does not give WSL direct access to the physical motherboard's thermal sensors. Because of this, WSL only exposes virtualized ACPI devices. The thermal sensors are hidden behind the hypervisor.

You cannot read hardware thermal sensors from inside a Windows Subsystem for Linux environment. You need a bare-metal Linux installation to test thermal telemetry.

The Code: Safely Reading Thermals

If a desktop computer does not have any fans plugged into the motherboard headers, trying to open fan1_input will throw a FileNotFoundError. We must wrap every read in a try/except block.

import os

base_path = '/sys/class/hwmon/'

# 1. Find all hwmon folders dynamically
hw_folders = [f for f in os.listdir(base_path) if f.startswith('hwmon')]

cpu_temp, cpu_fan = "N/A", "N/A"
igpu_temp, igpu_fan = "N/A", "N/A"
dgpu_temp, dgpu_fan = "N/A", "N/A"

for folder in hw_folders:
    folder_path = os.path.join(base_path, folder)
    
    # 2. Read the name file to identify the component
    try:
        with open(os.path.join(folder_path, "name"), 'r') as f:
            data = f.read().strip()
    except (FileNotFoundError, PermissionError):
        continue # Skip if we can't read the name

    # 3. Safely read temperature
    current_temp = "N/A"
    sensor_path = os.path.join(folder_path, "temp1_input")
    try:
        with open(sensor_path, "r") as f:
            # Convert millidegrees to Celsius
            current_temp = f"{int(f.read().strip()) / 1000.0}°C"
    except (FileNotFoundError, PermissionError, ValueError):
        current_temp = "N/A"

    # 4. Safely read fan speed
    current_fan = "N/A"
    fan_path = os.path.join(folder_path, "fan1_input")
    try:
        with open(fan_path, "r") as f:
            # Fan speed is directly in RPM, no division needed
            current_fan = f"{f.read().strip()} RPM"
    except (FileNotFoundError, PermissionError, ValueError):
        current_fan = "N/A"

    # 5. Route the data to the correct component
    if data in ('coretemp', 'k10temp'):
        cpu_temp, cpu_fan = current_temp, current_fan
    elif data in ('amdgpu', 'i915'):
        igpu_temp, igpu_fan = current_temp, current_fan
    elif data in ('nouveau', 'nvidia_wmi'):
        dgpu_temp, dgpu_fan = current_temp, current_fan

# 6. Print the dashboard
print(f"CPU:    {cpu_temp:<10} | Fan: {cpu_fan}")
print(f"iGPU:   {igpu_temp:<10} | Fan: {igpu_fan}")
print(f"NVIDIA: {dgpu_temp:<10} | Fan: {dgpu_fan}")

This code safely iterates through every sensor on the motherboard, identifies it by its driver name, and gracefully handles missing files for temperatures and fans.

Next up: Windows and macOS thermal fallbacks (using psutil or OS-specific libraries).

Tharana Nimsara

✅ VERIFIED AUTHOR

SHA256: c4d8a2...f1b3

ESC
tharana@dev: ~

Welcome to TharanaOS v1.0.0

Type 'help' to see available commands.

➜ ~
Hello, user.
(o_o)