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