initial commit

This commit is contained in:
2025-03-28 12:39:52 -07:00
commit 017ebda429
5 changed files with 174 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*~

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
UPS software for Geekworm X728
==============================
Install prerequisites:
```sudo apt update && sudo apt install -y python3 python3-smbus python3-pip && sudo pip3 install --break-system-packages rpi-lgpio```
Install the software:
```sudo cp x728ups x728off /usr/local/bin && sudo cp x728ups.service /lib/systemd/system && sudo systemctl daemon-reload```
Enable the software:
```sudo systemctl enable x728ups.service && sudo systemctl start x728ups.service```
Monitor the daemon while it's running:
```journalctl -fu x728ups```
Ways to do a clean shutdown
---------------------------
* ```sudo x728off``` (don't use ```shutdown -h now```)
* send SIGUSR1 (this is what x728off does)
* hold down the button for 4 seconds
* unplug power and wait for the battery to get to 20% remaining
Ways to reboot
--------------
* the old standby: ```shutdown -r now```
* hold down the button for 1 second
Kill the buzzer when on battery
-------------------------------
* send SIGUSR2: ```sudo pkill -USR2 -u root -f x728ups```
Notes
-----
Tested on a Raspberry Pi 4B running Armbian Bookworm, with a Geekworm X728 v2.5.

3
x728off Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
source /etc/profile
sudo pkill -USR1 -u root -f x728ups

112
x728ups Executable file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env python
import struct
import RPi.GPIO as GPIO
import smbus
import time
import subprocess
import sys
import signal
GPIO_BUZZER_PIN=20 # controls the buzzer
GPIO_ON_BATT=6 # set when running on battery
GPIO_PWR_CTRL=26 # toggle this for 2 seconds before shutdown, or else the board won't respond to the first power-on event
GPIO_BTN=5 # button signal: 500ms pulse for reboot, stays high for shutdown
GPIO_BOOT=12 # enable the button
I2C_ADDR=0x36 # the chip that reads battery charge state is here
CRIT_BATT=20 # shut off at this percentage
class X728UPS:
def __init__(self):
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_BUZZER_PIN, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(GPIO_PWR_CTRL, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(GPIO_ON_BATT, GPIO.IN)
GPIO.setup(GPIO_BOOT, GPIO.OUT, initial=GPIO.HIGH)
GPIO.setup(GPIO_BTN, GPIO.IN)
GPIO.add_event_detect(GPIO_BTN, GPIO.RISING, callback=lambda ch: self.ButtonCallback())
signal.signal(signal.SIGUSR1, self.Shutdown)
self.buzzer_on=True
signal.signal(signal.SIGUSR2, self.ToggleBuzzer)
def ToggleBuzzer(self, signum, frame):
self.buzzer_on=not self.buzzer_on
signal.signal(signal.SIGUSR2, self.ToggleBuzzer)
def Shutdown(self, signum, frame):
print("shutdown requested by SIGUSR1", flush=True)
GPIO.output(GPIO_PWR_CTRL, GPIO.HIGH)
time.sleep(2)
GPIO.output(GPIO_PWR_CTRL, GPIO.LOW)
subprocess.run(["shutdown", "-h", "now"])
sys.exit()
def ButtonCallback(self):
if GPIO.input(GPIO_BTN):
t=0
while GPIO.input(GPIO_BTN) and t<1000:
time.sleep(0.01)
t=t+10
if t>=200 and t<=600: # nominal 500ms pulse for reboot, or stays high for shutdown until powered off
print("reboot requested", flush=True)
time.sleep(1) # so the message above gets logged
subprocess.run(["shutdown", "-r", "now"])
sys.exit()
else:
print("shutdown requested", flush=True)
GPIO.output(GPIO_PWR_CTRL, GPIO.HIGH)
time.sleep(2)
GPIO.output(GPIO_PWR_CTRL, GPIO.LOW)
subprocess.run(["shutdown", "-h", "now"])
sys.exit()
def readVoltage(self, bus):
address = I2C_ADDR
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(self, bus):
address = I2C_ADDR
read = bus.read_word_data(address, 4)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
capacity = swapped/256
return capacity
def main(self):
bus=smbus.SMBus(1)
while True:
v=self.readVoltage(bus)
c=self.readCapacity(bus)
if GPIO.input(GPIO_ON_BATT):
p="OB"
else:
p="OL"
if c<CRIT_BATT:
p="%s LB" % p
p="%s %1.2f %i" % (p, v, c)
print(p, flush=True)
if p.find("OB")!=-1: # on battery?
if p.find("LB")!=-1: # low battery?
print("low battery shutdown", flush=True)
if self.buzzer_on:
GPIO.output(GPIO_BUZZER_PIN, GPIO.HIGH)
GPIO.output(GPIO_PWR_CTRL, GPIO.HIGH)
time.sleep(2)
GPIO.output(GPIO_PWR_CTRL, GPIO.LOW)
subprocess.run(["shutdown", "-h", "now"])
while True: # wait forever
time.sleep(1)
else: # on battery, but not low
if self.buzzer_on:
GPIO.output(GPIO_BUZZER_PIN, GPIO.HIGH)
time.sleep(.25)
GPIO.output(GPIO_BUZZER_PIN, GPIO.LOW)
time.sleep(4.75)
else: # online
time.sleep(5)
app=X728UPS()
app.main()

16
x728ups.service Normal file
View File

@@ -0,0 +1,16 @@
[Unit]
Description=X728 UPS
[Service]
Type=simple
ExecStart=/usr/local/bin/x728ups
Restart=always
StandardError=journal
StandardOutput=journal
StandardInput=null
User=root
Group=root
[Install]
WantedBy=multi-user.target