initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*~
|
||||
42
README.md
Normal file
42
README.md
Normal 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
3
x728off
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /etc/profile
|
||||
sudo pkill -USR1 -u root -f x728ups
|
||||
112
x728ups
Executable file
112
x728ups
Executable 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
16
x728ups.service
Normal 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
|
||||
Reference in New Issue
Block a user