-
CommentStreams:E1c4a47cce4f8ec607f03e24f6634e7a
Hello. I created the following Power-Management/Logging script. Maybe it is helpful to someone
- !/usr/bin/env python3
- 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
- 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
- Define the shutdown trigger file path
SHUTDOWN_TRIGGER_FILE = "/home/dockeruser/vault_con_data/commands/trigger/shutdown_vaultwarden"
- Configure logging
logging.basicConfig(
filename=LOG_FILE, level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s",
)
bus = smbus2.SMBus(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
- **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()
- **Track AC Connection & Charging State**
previous_ac_state = None previous_charging_state = None
- **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)