-

CommentStreams:E1c4a47cce4f8ec607f03e24f6634e7a

From Geekworm Wiki
Jump to navigation Jump to search

Hello. I created the following Power-Management/Logging script. Maybe it is helpful to someone


  1. !/usr/bin/env python3
  2. This script is only suitable for UPS Shield X1200, X1201, and X1202

import struct import smbus2 import time import os import logging import gpiod from subprocess import call

  1. Define the log file locations

LOG_FILE = "/var/log/ups_battery.log" # Main log with timestamps GLANCES_FILE = "/var/log/ups_battery_glances.log" # Always contains only the latest log entry MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB maximum log file size KEEP_LINES = 500 # Number of lines to retain after truncation

  1. Define the shutdown trigger file path

SHUTDOWN_TRIGGER_FILE = "/home/dockeruser/vault_con_data/commands/trigger/shutdown_vaultwarden"

  1. Configure logging

logging.basicConfig(

   filename=LOG_FILE,
   level=logging.INFO,
   format="%(asctime)s - %(levelname)s - %(message)s",

)

bus = smbus2.SMBus(1)

  1. **AC Power Detection via GPIO 6 (gpiochip4)**

PLD_PIN = 6 chip = gpiod.Chip('gpiochip4') # Use gpiochip4 (as per previous tests) pld_line = chip.get_line(PLD_PIN) pld_line.request(consumer="PLD", type=gpiod.LINE_REQ_DIR_IN)


def truncate_log_file():

   """Truncate the log file if it exceeds the max size by keeping only the last KEEP_LINES lines."""
   if os.path.exists(LOG_FILE) and os.path.getsize(LOG_FILE) > MAX_LOG_SIZE:
       try:
           with open(LOG_FILE, "r") as f:
               lines = f.readlines()[-KEEP_LINES:]  # Keep last X lines
           with open(LOG_FILE, "w") as f:
               f.writelines(lines)  # Overwrite with retained lines
           logging.info("🗑️ Log file truncated to last %d lines.", KEEP_LINES)
       except Exception as e:
           logging.error(f"Failed to truncate log file: {e}")


def log_message(message):

   """Write a message to both the log file and the terminal."""
   print(message)  # Print to console
   truncate_log_file()  # Ensure log doesn't exceed the limit before writing
   logging.info(message)  # Write to the main log file
   # Write the latest status to GLANCES_FILE, overwriting previous content
   try:
       with open(GLANCES_FILE, "w") as f:
           f.write(message + "\n")  # Overwrite previous entry with new one
   except Exception as e:
       logging.error(f"Failed to write to {GLANCES_FILE}: {e}")


def readVoltage(bus):

   address = 0x36
   read = bus.read_word_data(address, 2)
   swapped = struct.unpack("<H", struct.pack(">H", read))[0]
   voltage = swapped * 1.25 / 1000 / 16
   return voltage


def readCapacity(bus):

   address = 0x36
   read = bus.read_word_data(address, 4)
   swapped = struct.unpack("<H", struct.pack(">H", read))[0]
   capacity = swapped / 256
   return capacity


def is_ac_power_connected():

   return pld_line.get_value() == 1  # 1 = AC Plugged In, 0 = Unplugged


  1. **Charging Control via pinctrl (GPIO 16)**

def enable_charging():

   os.system("sudo pinctrl set 16 op dl")  # Enable charging
   log_message("🔋 Charging set to ENABLED.")


def disable_charging():

   os.system("sudo pinctrl set 16 op dh")  # Disable charging
   log_message("🔋 Charging set to DISABLED.")


def try_force_enable_charging():

   """Try to force enable charging if battery is too low (<40%)."""
   log_message("⚠️ Attempting to FORCE ENABLE charging (Battery <40%)")
   enable_charging()


def try_force_disable_charging():

   """Try to force disable charging if battery is too high (>70%)."""
   log_message("⚠️ Attempting to FORCE DISABLE charging (Battery >70%)")
   disable_charging()


  1. **Track AC Connection & Charging State**

previous_ac_state = None previous_charging_state = None

  1. **Check Initial Battery Level on Startup**

voltage = readVoltage(bus) capacity = readCapacity(bus) ac_power_state = is_ac_power_connected() # Detect AC power status

log_message("⚠️ POWER DETECTION STARTED (REBOOT?)")

if capacity >= 49:

   disable_charging()
   charging_enabled = False

else:

   enable_charging()
   charging_enabled = True

previous_charging_state = charging_enabled # ✅ Initialize this properly

while True:

   voltage = readVoltage(bus)
   capacity = readCapacity(bus)
   ac_power_state = is_ac_power_connected()  # Detect AC power status
   # Log AC connection status when it changes
   if ac_power_state != previous_ac_state:
       log_message("🔌 AC Power " + ("CONNECTED." if ac_power_state else "DISCONNECTED!"))
       previous_ac_state = ac_power_state
   # Forcing if wrong variable were set
   if capacity > 60:
       try_force_disable_charging()
       charging_enabled = False  # Mark charging as disabled
   elif capacity < 40:
       try_force_enable_charging()
       charging_enabled = True  # Mark charging as enabled
   else:  # Target Operation
       if capacity < 45 and not charging_enabled:  # ✅ Properly enables charging at 40-44%
           enable_charging()
           charging_enabled = True
       elif capacity >= 49 and charging_enabled:  # ✅ Properly disables charging at 50%
           disable_charging()
           charging_enabled = False


   # **Only Log Charging State When It Changes**
   if charging_enabled != previous_charging_state:
       log_message(f"⚡ Battery charging state changed: {'ENABLED' if charging_enabled else 'DISABLED'}.")
       previous_charging_state = charging_enabled  # ✅ Update this every loop
   # **Shutdown only if battery is critically low AND AC power is unplugged**
   if capacity <= 35 and not ac_power_state:
       log_message("🚨 CRITICAL: Battery LOW and AC Power is OFF! System will shut down.")
       os.system(f"touch {SHUTDOWN_TRIGGER_FILE}")
       log_message(f"📂 Shutdown trigger file created: {SHUTDOWN_TRIGGER_FILE}")
       log_message("⏳ Shutdown in 30 seconds...")
       time.sleep(30)  # Wait before shutting down
       log_message("🛑 System shutting down now.")
       call("sudo nohup shutdown -h now", shell=True)
   battery_status = f"Voltage: {voltage:.2f}V | Battery: {capacity:.0f}% | AC Power: {'Plugged in' if ac_power_state else 'Unplugged'} | Charging: {'Active' if charging_enabled and ac_power_state else 'Inactive'}"
   log_message(battery_status)
   time.sleep(2)