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)amdgpuori915(GPU)thinkpadoracpitz(Laptop motherboards)
The Files Inside
Once you find the right folder, you read the raw values:
- Temperature:
temp1_input(Returns millidegrees. E.g.,45000means 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