Skip to content

Instantly share code, notes, and snippets.

@IoTeacher
Last active May 10, 2026 03:41
Show Gist options
  • Select an option

  • Save IoTeacher/d7b2b8f208106e4ae58a5a9378a22e81 to your computer and use it in GitHub Desktop.

Select an option

Save IoTeacher/d7b2b8f208106e4ae58a5a9378a22e81 to your computer and use it in GitHub Desktop.
Distiller OS, Pamir.ai: Internal Fan Test
#!/usr/bin/env python3
"""
Manual fan and tachometer test for Distiller device
Tests PWM control and RPM sensor reading
"""
import os
import time
import subprocess
from pathlib import Path
# Fan control paths
FAN_INPUT = "/sys/class/hwmon/hwmon3/fan1_input"
PWM_CONTROL = "/sys/class/hwmon/hwmon3/pwm1"
PWM_ENABLE = "/sys/class/hwmon/hwmon3/pwm1_enable"
# Temperature and sensor paths
TEMP_INPUT = "/sys/class/hwmon/hwmon3/temp1_input"
IN_VOLTAGE = "/sys/class/hwmon/hwmon3/in0_input"
HWMON_PATH = "/sys/class/hwmon/hwmon3"
def read_sysfs(path):
"""Read sysfs value"""
try:
with open(path, 'r') as f:
return f.read().strip()
except Exception as e:
return f"ERROR: {e}"
def write_sysfs(path, value):
"""Write sysfs value (requires sudo)"""
try:
with open(path, 'w') as f:
f.write(str(value))
return True
except PermissionError:
return False
except Exception as e:
print(f"Write error: {e}")
return False
def read_temperature():
"""Read temperature in Celsius from hwmon3"""
try:
temp_raw = read_sysfs(TEMP_INPUT)
if temp_raw.startswith("ERROR"):
return None
temp_celsius = int(temp_raw) / 1000.0
return temp_celsius
except:
return None
def read_voltage():
"""Read voltage in millivolts from hwmon3"""
try:
volt_raw = read_sysfs(IN_VOLTAGE)
if volt_raw.startswith("ERROR"):
return None
volt_mv = int(volt_raw)
return volt_mv / 1000.0
except:
return None
def discover_sensors():
"""Discover all available sensors in hwmon3"""
sensors = {}
hwmon_dir = Path(HWMON_PATH)
if hwmon_dir.exists():
for item in hwmon_dir.glob("*_input"):
try:
sensor_name = item.name
value = read_sysfs(str(item))
if not value.startswith("ERROR"):
sensors[sensor_name] = value
except:
pass
return sensors
def print_header(title):
"""Print a section header"""
width = 70
print(f"\n┌{'─' * (width - 2)}┐")
print(f"│{title:^{width - 2}}│")
print(f"└{'─' * (width - 2)}┘")
def print_section(label):
"""Print a subsection label"""
print(f"\n ▸ {label}")
print(f" " + "─" * 66)
def print_metric(label, value, unit="", icon=" ", bar=None, bar_length=25):
"""Print a formatted metric line"""
if bar is not None:
bar_filled = "▓" * bar
bar_empty = "░" * (bar_length - bar)
print(f" {icon} {label:<18} {value:>8} {unit:<4} │{bar_filled}{bar_empty}│")
else:
print(f" {icon} {label:<18} {value:>8} {unit:<4}")
def print_dashboard(rpm, temp, voltage, pwm_enable, pwm_value):
"""Print ASCII dashboard with sensor data"""
print_header("⚙ SENSOR DASHBOARD")
# Temperature
if temp is not None:
temp_pct = min(int(temp / 80 * 25), 25)
status_icon = "🔴" if temp > 80 else "🟡" if temp > 60 else "🟢"
print_metric("Temperature", f"{temp:.1f}", "°C", status_icon, temp_pct, 25)
else:
print_metric("Temperature", "N/A", "", "❌")
# Fan RPM
try:
rpm_val = int(rpm) if rpm.isdigit() else 0
rpm_pct = min(int(rpm_val / 4000 * 25), 25) if rpm_val > 0 else 0
rpm_icon = "✓" if rpm_val > 0 else "✗"
print_metric("Fan RPM", f"{rpm_val:>5d}", "RPM", rpm_icon, rpm_pct, 25)
except:
print_metric("Fan RPM", "ERROR", "", "✗")
# Voltage
if voltage is not None:
print_metric("Voltage", f"{voltage:.2f}", "V", "⚡")
else:
print_metric("Voltage", "N/A", "", "❌")
# PWM Control
pwm_state = "ON " if pwm_enable == "1" else "OFF"
pwm_icon = "▶" if pwm_enable == "1" else "⏸"
try:
pwm_pct = int(pwm_value) * 100 // 255 if pwm_value.isdigit() else 0
except:
pwm_pct = 0
pwm_bar = min(int(pwm_pct / 4), 25)
print_metric("PWM Control", f"{pwm_pct:3d}%", f"[{pwm_state}]", pwm_icon, pwm_bar, 25)
print(f"\n └{'─' * 66}┘")
def test_fan():
"""Test fan control and tachometer"""
print("\n")
print_header("🌡 DISTILLER FAN & TACHOMETER TEST")
# Check if running as root
is_sudo = os.geteuid() == 0
status = "✓ Running with elevated privileges (sudo)" if is_sudo else "⚠️ Running without sudo - PWM control unavailable"
print(f"\n {status}\n")
# 1. Check current state
print_section("CURRENT FAN STATE")
rpm = read_sysfs(FAN_INPUT)
pwm_enable = read_sysfs(PWM_ENABLE)
pwm_value = read_sysfs(PWM_CONTROL)
temp = read_temperature()
voltage = read_voltage()
print_metric("Fan Input", rpm, "RPM")
print_metric("PWM Enable", pwm_enable, "")
print_metric("PWM Value", pwm_value, "")
# Display dashboard
print_dashboard(rpm, temp, voltage, pwm_enable, pwm_value)
# 2. Read tachometer without PWM
print_section("TACHOMETER TEST (No PWM)")
rpm_values = []
for i in range(5):
rpm = read_sysfs(FAN_INPUT)
rpm_values.append(rpm)
print(f" Reading {i+1}: {rpm:>5s} RPM")
time.sleep(0.5)
tach_status = "⚠️ Sensor not responding" if all(v == "0" for v in rpm_values) else "✓ Sensor responding"
print(f" Result: {tach_status}\n")
# 3. PWM control test (requires sudo)
if is_sudo:
print_section("PWM CONTROL TEST")
# Enable PWM
pwm_ok = write_sysfs(PWM_ENABLE, 1)
print(f" Enable PWM: {'✓' if pwm_ok else '✗'}")
# Test PWM levels
for level in [0, 127, 255]:
if write_sysfs(PWM_CONTROL, level):
time.sleep(1)
rpm_read = read_sysfs(FAN_INPUT)
enable_state = read_sysfs(PWM_ENABLE)
pct = level * 100 // 255
print(f" Level {pct:3d}%: RPM = {rpm_read:>5s} RPM")
else:
print(f" Level {level:3d}: ✗ Permission denied")
# Disable PWM
pwm_disable_ok = write_sysfs(PWM_ENABLE, 0)
print(f" Disable PWM: {'✓' if pwm_disable_ok else '✗'}\n")
# 4. Device tree check
print_section("DEVICE TREE")
dt_path = Path("/sys/class/hwmon/hwmon3/of_node")
if dt_path.exists():
print(f" ✓ Device tree node exists")
try:
result = subprocess.run(
["dtc", "-I", "fs", "-O", "dts", "/sys/class/hwmon/hwmon3/of_node"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
print(f" ✓ Device tree accessible via dtc\n")
except:
print()
else:
print(f" ✗ Device tree node not found\n")
# 5. Sensor Discovery
print_section("AVAILABLE SENSORS")
sensors = discover_sensors()
if sensors:
for sensor_name, sensor_value in sorted(sensors.items()):
print(f" • {sensor_name:25s}: {sensor_value:>10s}")
print()
else:
print(" (No additional sensors detected)\n")
# 6. Diagnosis Summary with Dashboard
print_section("DIAGNOSIS SUMMARY")
print_dashboard(rpm, temp, voltage, pwm_enable, pwm_value)
# Analysis
print_section("ANALYSIS")
if all(v == "0" for v in rpm_values):
print(" ❌ ISSUE: Tachometer not responding (always reads 0)\n")
print(" Possible causes:")
print(" • Fan connector loose or disconnected")
print(" • Tachometer wire broken/damaged")
print(" • hwmon3 driver issue")
print(" • Fan not spinning (needs PWM enabled)\n")
print(" Recommended actions:")
print(" 1. Check physical fan connector")
print(" 2. Enable PWM and test with PWM control enabled")
print(" 3. Check kernel logs: dmesg | grep -i fan")
print(" 4. Verify cooling driver: lsmod | grep -i cooling")
else:
print(" ✅ Tachometer is responding correctly\n")
# Temperature health check
if temp is not None:
if temp > 80:
print(f" 🔴 WARNING: High temperature ({temp:.1f}°C) - Thermal throttling may occur")
elif temp > 60:
print(f" 🟡 CAUTION: Elevated temperature ({temp:.1f}°C) - Monitor closely")
else:
print(f" 🟢 Temperature normal ({temp:.1f}°C) - Optimal operating range")
else:
print(f" ⚠️ Temperature sensor unavailable")
print()
if __name__ == "__main__":
test_fan()
@IoTeacher
Copy link
Copy Markdown
Author

IMG_5813 IMG_5879

@IoTeacher
Copy link
Copy Markdown
Author

IoTeacher commented May 9, 2026

IMG_5814

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment