#!/usr/bin/env python3 """ Battery Monitor for Stormux Gaming Image Monitors battery levels and provides warnings at 10% and 5%, with automatic shutdown at 3% to prevent data loss. Only activates if a real battery is detected to avoid false alarms. """ import os import sys import time import subprocess import logging from pathlib import Path # Set up logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('/var/log/battery_monitor.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) class BatteryMonitor: def __init__(self): # Hardcoded settings - no config file needed self.enabled = True self.warning_10_percent = True self.warning_5_percent = True self.shutdown_3_percent = True self.check_interval = 30 self.speech_enabled = True self.warned_10 = False self.warned_5 = False def has_battery(self): """Check if system has a real battery""" power_supply_dir = Path('/sys/class/power_supply') if not power_supply_dir.exists(): return False for item in power_supply_dir.iterdir(): type_file = item / 'type' if type_file.exists(): try: if type_file.read_text().strip() == 'Battery': logger.info(f"Battery detected: {item.name}") return True except Exception: continue logger.info("No battery detected") return False def get_battery_level(self): """Get current battery percentage""" power_supply_dir = Path('/sys/class/power_supply') for item in power_supply_dir.iterdir(): type_file = item / 'type' capacity_file = item / 'capacity' if (type_file.exists() and capacity_file.exists()): try: if type_file.read_text().strip() == 'Battery': capacity = int(capacity_file.read_text().strip()) return capacity except Exception: continue return None def is_on_ac_power(self): """Check if system is plugged into AC power""" power_supply_dir = Path('/sys/class/power_supply') for item in power_supply_dir.iterdir(): type_file = item / 'type' online_file = item / 'online' if (type_file.exists() and online_file.exists()): try: device_type = type_file.read_text().strip() if device_type in ['ADP', 'Mains', 'AC']: online = int(online_file.read_text().strip()) return online == 1 except Exception: continue return False def speak(self, message): """Use speech-dispatcher to speak a message""" if not self.speech_enabled: return try: subprocess.run(['spd-say', '-w', message], check=True) except Exception as e: logger.error(f"Speech error: {e}") def play_urgent_sound(self): """Play urgent warning sound using SoX""" try: # Blocking sound command as specified cmd = [ 'play', '-n', 'synth', '2', 'sine', '700:1000', 'sine', '900:1200', 'fade', '0', '2', '0', 'remix', '-', 'norm', '-12', 'repeat', '3', 'overdrive', 'reverb', 'speed', '4' ] subprocess.run(cmd, check=True) except Exception as e: logger.error(f"Sound error: {e}") def handle_low_battery(self, level): """Handle low battery warnings and actions""" if level <= 3: if self.shutdown_3_percent: logger.critical(f"Battery at {level}% - initiating shutdown") self.speak("Critical battery level. System shutting down now.") time.sleep(2) # Give speech time to complete subprocess.run(['sudo', 'poweroff'], check=True) return elif level <= 5 and not self.warned_5: if self.warning_5_percent: logger.warning(f"Battery at {level}% - urgent warning") self.play_urgent_sound() self.speak("Extremely low battery. Computer will shut down soon.") self.warned_5 = True elif level <= 10 and not self.warned_10: if self.warning_10_percent: logger.warning(f"Battery at {level}% - first warning") self.speak("Low battery warning. Please connect power adapter.") self.warned_10 = True def reset_warnings_if_charging(self): """Reset warning flags if system is charging""" if self.is_on_ac_power(): if self.warned_10 or self.warned_5: logger.info("AC power connected - resetting warning flags") self.warned_10 = False self.warned_5 = False def monitor(self): """Main monitoring loop""" if not self.enabled: logger.info("Battery monitoring disabled") return if not self.has_battery(): logger.info("No battery detected - monitoring disabled") return logger.info("Starting battery monitoring") while True: try: level = self.get_battery_level() if level is not None: logger.debug(f"Battery level: {level}%") self.reset_warnings_if_charging() self.handle_low_battery(level) else: logger.warning("Could not read battery level") time.sleep(self.check_interval) except KeyboardInterrupt: logger.info("Battery monitoring stopped by user") break except Exception as e: logger.error(f"Monitoring error: {e}") time.sleep(self.check_interval) def main(): if os.geteuid() != 0: print("Warning: Running as non-root user. Shutdown functionality will require sudo.") monitor = BatteryMonitor() monitor.monitor() if __name__ == '__main__': main()