Last active
May 10, 2026 03:41
-
-
Save IoTeacher/d7b2b8f208106e4ae58a5a9378a22e81 to your computer and use it in GitHub Desktop.
Distiller OS, Pamir.ai: Internal Fan Test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
