Raspberry Pi UPSPACK V3 setup

  Uncategorized
Read the English manual: https://github.com/rcdrones/UPSPACK_V3/blob/master/README_en.md

# clone the github repo
sudo apt update && sudo apt install git -y
cd ~
git clone https://github.com/rcdrones/UPSPACK_V3.git
cd ~/UPSPACK_V3/UPS_GUI_py

# try to run the UPS GUI demo python script:
python UPS_GUI_demo.py 
Traceback (most recent call last):
  File "/home/pi/UPSPACK_V3/UPS_GUI_py/UPS_GUI_demo.py", line 3, in <module>
    from upspackv2 import *
  File "/home/pi/UPSPACK_V3/UPS_GUI_py/upspackv2.py", line 3, in <module>
    import serial
ModuleNotFoundError: No module named 'serial'

# install missing dependencies
sudo apt install python3-pip
pip install pyserial

# you  have to create an environment
cd ~/UPSPACK_V3/
python3 -m venv venv
source venv/bin/activate

# install the dependencies again
pip install pyserial
pip install RPi.GPIO
sudo apt install python3-tk -y

cd ~/UPSPACK_V3/UPS_GUI_py/
python UPS_GUI_demo.py

# the next errors show up if you don't have /dev/serial0 -> ttyAMA0 set up. Let's set up the rest.

sudo nano /boot/firmware/config.txt

# at the bottom of the file, under the [all] section, add:
enable_uart=1
dtoverlay=disable-bt
# save, exit, and sudo reboot

ls -l /dev
# serial0 -> ttyAMA0 should exist

cd ~/UPSPACK_V3
source venv/bin/activate
cd UPS_GUI_py
python UPS_GUI_demo.py

# if you see error "no $DISPLAY environment variable", then exit, and use -X
ssh -X pi@server
cd ~/UPSPACK_V3
source venv/bin/activate
cd UPS_GUI_py
python UPS_GUI_demo.py
# if no window pops up, set up the hardware first:

See: https://github.com/rcdrones/UPSPACK_V3/blob/master/README_en.md

ls -l /dev and check that you can see serial0


# ssh -X to the server again
cd UPSPACK_V3
source venv/bin/activate
cd UPS_GUI_py
python UPS_GUI_demo.py

It works! Disconnect the power to the UPS and you will see the Power disconnected message change.

If you see error: “could not open port /dev/ttyAMA0: [Errno 13] Permission denied: ‘/dev/ttyAMA0′”, then you need to run the program with sudo and specify with -E the environment which we’ve already been using:

cd ~/UPSPACK_V3/UPS_GUI_py/
sudo -E ~/UPSPACK_V3/venv/bin/python UPS_GUI_demo.py

With power disconnected:

So, at least their app works, which you can then re-write for your own needs.

Reading the voltage on a BP-TCA-12/2510 SN battery, rated at 4V 4400mAh 17.6 Wh:

Raspberry Pi Zero W (wifi) with no other load
92% battery at 7:00 hrs
44% battery at 23:00 hrs
draw was about 48% in 16 hours, or 3% battery use per hour.

Battery level read from the UPS:

Script to run it: run.sh, upspackv2.py and monitor.py

In crontab -e:
@reboot INFLUXDB_TOKEN="secret-token-here...==" /home/pi/voltagemonitor/run.sh

The main run.sh file:

#!/bin/bash
# make sure to edit your users's ~/.bashrc and add:
# export INFLUXDB_TOKEN="token-here"
# and
# source ~/.bashrc
cd /home/pi/voltagemonitor
sudo INFLUXDB_TOKEN=$INFLUXDB_TOKEN venv/bin/python monitor.py > log.txt 2>&1




A copy of this modified library in /home/pi/voltagemonitor/upspackv2.py :
#!/usr/bin/python3

import serial
import re
import RPi.GPIO as GPIO
import os,sys
import time
from datetime import datetime

def print2(*args, **kwargs):
    formatted_time = datetime.now().strftime("%Y.%m.%d_%H:%M:%S:%f")[:-3]
    print(f"{formatted_time}", *args, **kwargs)

class UPS2:
    def __init__(self,port):
        self.ser  = serial.Serial(port,9600)

    def get_data(self,nums):
        try:
            while True:

                if not self.ser or not self.ser.is_open:
                    print2("Error: Serial connection lost. Attempting to reconnect...",file=sys.stderr)
                    try:
                        self.ser.open()
                    except serial.SerialException as e:
                        print2(f"Error: Failed to reopen serial connection: {e}",file=sys.stderr)
                        return None

                self.count = self.ser.inWaiting()
                if self.count !=0:
                    self.recv = self.ser.read(nums)
                    return self.recv
                else:
                    time.sleep(0.9)
        except (OSError, serial.SerialException) as e:
            print2(f"Error: Failed to read from serial, error is: {e}",file=sys.stderr)
            return None

    def decode_uart(self):

        self.uart_string = self.get_data(100)

        if self.uart_string is None:
            print2("Error: uart_string is None, there is no data from serial",file=sys.stderr)
            return None

        else:
            print2("*** begin uart_string ***",file=sys.stderr)
            print2(self.uart_string,file=sys.stderr)
            print2("*** end uart_string ***",file=sys.stderr)

            if b'\x00' in self.uart_string:
                print2("Error: Received a bad serial string with null bytes.",file=sys.stderr)
                return None
            else:
                # Continue processing if the string is valid
                self.data = self.uart_string.decode('ascii','ignore')

                # ensure the data is not null
                if not self.data:
                    print2("No data available to search",file=sys.stderr)
                    return None

                #self.pattern = r'\$ (.*?) \$'
                self.pattern = r'SmartUPS.*?\$'
                self.result = re.findall(self.pattern,self.data,re.S)

                if not self.result:
                     print2("Error: Pattern not found in serial data",file=sys.stderr)
                     return None

                self.tmp = self.result[0]

                self.pattern = r'SmartUPS (.*?),'
                self.version = re.findall(self.pattern,self.tmp)

                self.pattern = r',Vin (.*?),'
                self.vin = re.findall(self.pattern,self.tmp)

                self.pattern = r'BATCAP (.*?),'
                self.batcap = re.findall(self.pattern,self.tmp)

                #self.pattern = r',Vout (.*)'
                self.pattern = r',Vout (\d+) \$'
                self.vout = re.findall(self.pattern,self.tmp)

                print2("Parsed: " + self.version[0]+" "+self.vin[0]+" "+self.batcap[0]+" "+self.vout[0],file=sys.stderr)

                return self.version[0],self.vin[0],self.batcap[0],self.vout[0]

    def cleanup(self):
        """Closes the serial connection."""
        if self.ser and self.ser.is_open:
            self.ser.close()
            print2("Serial connection closed.",file=sys.stderr)

class UPS2_IO:
    def __init__(self,bcm_io=18):
        self.shutdown_check_pin = bcm_io
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.shutdown_check_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        GPIO.add_event_detect(self.shutdown_check_pin, GPIO.FALLING, callback= self.RPI_shutdown,bouncetime=1000)


    def RPI_shutdown(self,channel):
        print2("detect bat LOW, system will shutdown in 10s!")
        for i in range(10,0,-1):
            print2(i,end = ' ',flush=True)
            time.sleep(1)
            
        print2("\nexecute System shudown!\n")
        os.system("sudo shutdown -t now")
        sys.exit()
    

    def cleanup():
        print2("clean up GPIO.")
        GPIO.cleanup() 

if __name__ == "__main__":
    print2("This is UPS v2 class file")

The main monitor.py


#!/usr/bin/python3

from upspackv2 import *
import re
import serial
import time
from datetime import datetime
from influx_writer import InfluxDBWriter

ups = UPS2("/dev/ttyAMA0")

def print2(*args, **kwargs):
    # Get the current time formatted with milliseconds
    formatted_time = datetime.now().strftime("%Y.%m.%d_%H:%M:%S:%f")[:-3]
    # Print the timestamp followed by the original arguments
    print(f"{formatted_time}", *args, **kwargs)

def reflash_data():

    try:
        serialread = ups.decode_uart()
        if serialread is None:
            print2("got bad serial data from the UPS, skipping...",file=sys.stderr)
        else:
            version, vin, batcap, vout = serialread

            # Check if any of the returned values are None
            if version is None or vin is None or batcap is None or vout is None:
                print2("Failed to retrieve valid data from UPS.",file=sys.stderr)
            else:
                # NG = not good, no power in, only UPS power
                if vin == "NG":
                    ext_pwr="n"
                else:
                    ext_pwr="y"

                batcap_int = int(batcap)
                if batcap_int< 30:
                    if batcap_int == 1:
                        cur_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
                        stop_time = "\nHalt time :"+cur_time
                        with open("log.txt","a+") as f:
                            f.write(stop_time)
                        #os.system("sudo shutdown -t now")
                        print2("would shut down now", file=sys.stderr)
                        sys.exit()

                print2("SENDING: ups,device=sensorpi pwr=" + ext_pwr + " batpct=" + str(batcap) + " mV=" +str(vout) + " " + str(time.time_ns()),file=sys.stderr)
                try:
                    influx_writer.write_data("ups,device=sensorpi pwr=" + ext_pwr + " batpct=" + str(batcap) + " mV=" +str(vout) + " " + str(time.time_ns()))
                except Exception as e:
                    print2("Failed to write data to InfluxDB:", e, file=sys.stderr)

    except serial.SerialException as e:
        print2("Exception while reading from uart serial",file=sys.stderr)
    finally:
        ups.cleanup()


# set: export INFLUXDB_TOKEN="your_influxdb_token_here" in ~/.bashrc
token = os.getenv("INFLUXDB_TOKEN")
if not token:
    raise EnvironmentError("INFLUXDB_TOKEN environment variable not set.")

org = "set-your-org-here"
bucket = "set-your-bucket-name-here"
url = "https://us-east-1-1.aws.cloud2.influxdata.com"

influx_writer = InfluxDBWriter(token, org, bucket, url)

while True:
    reflash_data()
    time.sleep(30)

Example output in log.txt, which you can tail -F log.txt

...
024.11.27_00:51:45:163 b'$ SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$ SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$ '
2024.11.27_00:51:45:164 *** end uart_string ***
2024.11.27_00:51:45:165 Parsed: V3.2P GOOD 100 5250
2024.11.27_00:51:45:166 SENDING: ups,device=sensorpi pwr=y batpct=100 mV=5250 1732686705166640778
2024.11.27_00:51:45:239 Serial connection closed.
2024.11.27_00:52:15:240 Error: Serial connection lost. Attempting to reconnect...
2024.11.27_00:52:17:354 *** begin uart_string ***
2024.11.27_00:52:17:355 b'$ SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$ SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$ '
2024.11.27_00:52:17:355 *** end uart_string ***
2024.11.27_00:52:17:357 Parsed: V3.2P GOOD 100 5250
2024.11.27_00:52:17:357 SENDING: ups,device=sensorpi pwr=y batpct=100 mV=5250 1732686737357896676
2024.11.27_00:52:17:424 Serial connection closed.
2024.11.27_00:52:47:426 Error: Serial connection lost. Attempting to reconnect...
2024.11.27_00:52:48:529 Error: Failed to read from serial, error is: device reports readiness to read but returned no data (device disconnected or multiple access on port?)
2024.11.27_00:52:48:530 Error: uart_string is None, there is no data from serial
2024.11.27_00:52:48:531 got bad serial data from the UPS, skipping...
2024.11.27_00:52:48:532 Serial connection closed.
2024.11.27_00:53:18:533 Error: Serial connection lost. Attempting to reconnect...
2024.11.27_00:53:20:730 *** begin uart_string ***
2024.11.27_00:53:20:731 b'$ SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$ SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$ '
2024.11.27_00:53:20:732 *** end uart_string ***
2024.11.27_00:53:20:733 Parsed: V3.2P GOOD 100 5250
2024.11.27_00:53:20:734 SENDING: ups,device=sensorpi pwr=y batpct=100 mV=5250 1732686800734177740
2024.11.27_00:53:20:806 Serial connection closed.
2024.11.27_00:53:50:807 Error: Serial connection lost. Attempting to reconnect...
2024.11.27_00:53:53:193 *** begin uart_string ***
2024.11.27_00:53:53:194 b'PS V3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\x00\x00\x00\x00\xe0\x80x>\x0f\xf8\xf8<\xc0\x80\xf8\xf8<\x0fx>>\xff\xc0'
2024.11.27_00:53:53:194 *** end uart_string ***
2024.11.27_00:53:53:195 Error: Received a bad serial string with null bytes.
2024.11.27_00:53:53:195 got bad serial data from the UPS, skipping...
2024.11.27_00:53:53:196 Serial connection closed.
2024.11.27_00:54:23:366 Error: Serial connection lost. Attempting to reconnect...
2024.11.27_00:54:27:174 *** begin uart_string ***
2024.11.27_00:54:27:175 b'$\x00 SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$ SmartUPS V3.2P,Vin GOOD,BATCAP 100,Vout 5250 $\n$'
2024.11.27_00:54:27:176 *** end uart_string ***
2024.11.27_00:54:27:176 Error: Received a bad serial string with null bytes.
...