EP-0136: Difference between revisions
Line 211: | Line 211: | ||
===How To Collect Data=== | ===How To Collect Data=== | ||
* Data Collection | |||
Data collection will only collect information on abnormal voltage changes and battery charging and discharging status during equipment operation through UPS in order to provide better after-sales service, and will not collect your private information. <br> | |||
Please rest assured that every UPS we sell will Have a unique serial number so that we can provide battery quality analysis and feedback.<br> | |||
It submits your UPS Pro operating status information to the server provided by us through a python script.<br> | |||
1. Create a directory:<br> | |||
<pre>mkdir ~/bin</pre><br> | |||
2. Create python script files: upsplus.py and upsplus_iot.py <br> | |||
** code of upsplus.py | |||
<pre> | |||
import os | |||
import time | |||
import smbus2 | |||
import logging | |||
from ina219 import INA219,DeviceRangeError | |||
# Define I2C bus | |||
DEVICE_BUS = 1 | |||
# Define device i2c slave address. | |||
DEVICE_ADDR = 0x17 | |||
# Set the threshold of UPS automatic power-off to prevent damage caused by battery over-discharge, unit: mV. | |||
PROTECT_VOLT = 3700 | |||
# Set the sample period, Unit: min default: 2 min. | |||
SAMPLE_TIME = 2 | |||
# Instance INA219 and getting information from it. | |||
ina = INA219(0.00725, address=0x40) | |||
ina.configure() | |||
print("-"*60) | |||
print("------Current information of the detected Raspberry Pi------") | |||
print("-"*60) | |||
print("Raspberry Pi Supply Voltage: %.3f V" % ina.voltage()) | |||
print("Raspberry Pi Current Current Consumption: %.3f V" % ina.current()) | |||
print("Raspberry Pi Current Power Consumption: %.3f V" % ina.current()) | |||
print("-"*60) | |||
# Batteries information | |||
ina = INA219(0.005, address=0x45) | |||
ina.configure() | |||
print("-------------------Batteries information-------------------") | |||
print("-"*60) | |||
print("Voltage of Batteries: %.3f V" % ina.voltage()) | |||
try: | |||
if ina.current() > 0: | |||
print("Battery Current (Charging) Rate: %.3f mA"% (ina.current())) | |||
print("Current Battery Power Supplement: %.3f mW"% ina.power()) | |||
else: | |||
print("Battery Current (discharge) Rate: %.3f mA"% (0-ina.current())) | |||
print("Current Battery Power Consumption: %.3f mW"% ina.power()) | |||
print("-"*60) | |||
except DeviceRangeError: | |||
print("-"*60) | |||
print('Battery power is too high.') | |||
# Raspberry Pi Communicates with MCU via i2c protocol. | |||
bus = smbus2.SMBus(DEVICE_BUS) | |||
aReceiveBuf = [] | |||
aReceiveBuf.append(0x00) | |||
# Read register and add the data to the list: aReceiveBuf | |||
for i in range(1, 255): | |||
aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) | |||
# Enable Back-to-AC fucntion. | |||
# Enable: write 1 to register 0x25 | |||
# Disable: write 0 to register 0x25 | |||
bus.write_byte_data(DEVICE_ADDR, 25, 1) | |||
# Reset Protect voltage | |||
bus.write_byte_data(DEVICE_ADDR, 17, PROTECT_VOLT & 0xFF) | |||
bus.write_byte_data(DEVICE_ADDR, 18, (PROTECT_VOLT >> 8)& 0xFF) | |||
print("Successfully set the protection voltage to: %d mV" % PROTECT_VOLT) | |||
if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000: | |||
print('-'*60) | |||
print('Currently charging via Type C Port.') | |||
elif (aReceiveBuf[10] << 8 | aReceiveBuf[9])> 4000: | |||
print('-'*60) | |||
print('Currently charging via Micro USB Port.') | |||
else: | |||
print('-'*60) | |||
print('Currently not charging.') | |||
# Consider shutting down to save data or send notifications | |||
if ina.voltage() < (PROTECT_VOLT + 200): | |||
print('-'*60) | |||
print('The battery is going to dead! Ready to shut down!') | |||
# It will cut off power when initialized shutdown sequence. | |||
bus.write_byte_data(DEVICE_ADDR, 24,240) | |||
os.system("sudo sync && sudo halt") | |||
while True: | |||
time.sleep(10) | |||
</pre> | |||
** code of upsplus_iot.py # This is Data collection code. | |||
<pre> | |||
import time | |||
import smbus | |||
import requests | |||
from ina219 import INA219,DeviceRangeError | |||
DEVICE_BUS = 1 | |||
DEVICE_ADDR = 0x17 | |||
PROTECT_VOLT = 3700 | |||
SAMPLE_TIME = 2 | |||
FEED_URL = "https://api.thekoziolfoundation.com/feed" | |||
DATA = dict() | |||
ina = INA219(0.00725,address=0x40) | |||
ina.configure() | |||
DATA['PiVccVolt'] = ina.voltage() | |||
DATA['PiIddAmps'] = ina.current() | |||
ina = INA219(0.005,address=0x45) | |||
ina.configure() | |||
DATA['BatVccVolt'] = ina.voltage() | |||
try: | |||
DATA['BatIddAmps'] = ina.current() | |||
except DeviceRangeError: | |||
DATA['BatIddAmps'] = 16000 | |||
bus = smbus.SMBus(DEVICE_BUS) | |||
aReceiveBuf = [] | |||
aReceiveBuf.append(0x00) | |||
for i in range(1,255): | |||
aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) | |||
DATA['McuVccVolt'] = aReceiveBuf[2] << 8 | aReceiveBuf[1] | |||
DATA['BatPinCVolt'] = aReceiveBuf[6] << 8 | aReceiveBuf[5] | |||
DATA['ChargeTypeCVolt'] = aReceiveBuf[8] << 8 | aReceiveBuf[7] | |||
DATA['ChargeMicroVolt'] = aReceiveBuf[10] << 8 | aReceiveBuf[9] | |||
DATA['BatTemperature'] = aReceiveBuf[12] << 8 | aReceiveBuf[11] | |||
DATA['BatFullVolt'] = aReceiveBuf[14] << 8 | aReceiveBuf[13] | |||
DATA['BatEmptyVolt'] = aReceiveBuf[16] << 8 | aReceiveBuf[15] | |||
DATA['BatProtectVolt'] = aReceiveBuf[18] << 8 | aReceiveBuf[17] | |||
DATA['SampleTime'] = aReceiveBuf[22] << 8 | aReceiveBuf[21] | |||
DATA['AutoPowerOn'] = aReceiveBuf[24] | |||
DATA['OnlineTime'] = aReceiveBuf[31] << 24 | aReceiveBuf[30] << 16 | aReceiveBuf[29] << 8 | aReceiveBuf[28] | |||
DATA['FullTime'] = aReceiveBuf[35] << 24 | aReceiveBuf[34] << 16 | aReceiveBuf[33] << 8 | aReceiveBuf[32] | |||
DATA['OneshotTime'] = aReceiveBuf[39] << 24 | aReceiveBuf[38] << 16 | aReceiveBuf[37] << 8 | aReceiveBuf[36] | |||
DATA['Version'] = aReceiveBuf[41] << 8 | aReceiveBuf[40] | |||
DATA['UID0'] = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) | |||
DATA['UID1'] = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) | |||
DATA['UID2'] = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) | |||
print(DATA) | |||
r = requests.post(FEED_URL, data=DATA) | |||
print(r.text) | |||
</pre> | |||
* The execution frequency is recommended to be once a minute via crond service. You can edit crontab via: | |||
<pre> crontab -e</pre> | |||
add following code: | |||
<pre> | |||
* * * * * /usr/bin/python3 /home/pi/bin/upsPlus.py | |||
* * * * * /usr/bin/python3 /home/pi/bin/upsPlus_iot.py | |||
</pre> | |||
After saving it, check it via: | |||
<pre>crontab -l </pre> | |||
===How To Setup RTC=== | ===How To Setup RTC=== |
Revision as of 17:34, 6 January 2021
UPS Plus
Descriptions
UPS Plus is a new generation of UPS power management module.
Features
- Easy to install
- Enhanced power management
- Remote OTA firmware upgrade
- Programmable BACK-TO-AC auto power up
- Programmable sample period
- I2C communication
- Standalone RTC
- Support stacked battery design
Specifications
- Current/voltage monitoring of Raspberry Pi power supply port.
- Battery terminal current/voltage monitoring, supports two-way monitoring of charge and discharge.
- Independent RTC function.
- OTA function (supports forced upgrade mode and active upgrade mode).
- Power estimation
Note: At least one full charge and discharge cycle must be completed!)
- Adjustable sampling period.
- Support FCP, AFC, SFCP fast charge protocol.
- Support BC1.2 charging protocol.
- Battery temperature monitoring
Note: the forced temperature protection cannot be turned off, threshold: 65 degrees!
- Programmable Power Voltage Detector(PVD) function, Default value: 3.6V
- Incoming call self-start function.
- Power down memory function.
- Programmable shutdown / forced restart.
- Running time statistics
- Electrostatic protection.
- Linear compensation range discharge capacity: 5V 4.5A.
- Non-linear compensation range discharge capacity: 5V 8A.
- Charging capacity 4.5V 5A/5V 2.5A/9V 2A/12V 1.5A.
- Stacked battery design
NOTE: Stacked power board need to be special accessories.Can not support None official accessories.
- Use I2C communication without occupying additional ports.
- Support 4.2V 4.35V 4.4V 4.5V lithium battery
Note: Different types of batteries cannot be mixed!
- After-sales data telemetry.
Register Mapping
USB Plus V5.0 Register Mapping Chart
- 0x17 - Operation Mode
Address | Function | Range | Unit |
---|---|---|---|
0x01 - 0x02 | Voltage of UPS's MCU | 2400 - 3600 | mV |
0x03 - 0x04 | Voltage of Pogopin's Bottom | 0 - 5500 | mV |
0x05 - 0x06 | Voltage of Batteries' Terminal | 0 - 4500 | mV |
0x07 - 0x08 | Voltage of USB-C Charging Port | 0 - 13500 | mV |
0x09 - 0x0A | Voltage of MicroUSB Charging Port | 0 - 13500 | mV |
0x0B - 0x0C | Batteries Temperature | -20 - 65 | ℃ |
0x0D - 0x0E | Full Voltage | 0 - 4500 | mV |
0x0F - 0x10 | Empty Voltage | 0 - 4500 | mV |
0x11 - 0x12 | Protection Voltage | 0 - 4500 | mV |
0x13 - 0x14 | Battery Remaining | 0 - 100 | % |
0x15 - 0x16 | Sample Period | 1 - 1440 | Min |
0x17 | Power Status/Operation Mode | 0/1 | Bool |
0x18 | Shutdown Countdown | 0/1 - 255 | Bool/Sec |
0x19 | Back-To-AC Auto Power up | 0/1 | Bool |
0x1A | Restart Countdown | 0/1 - 255 | Bool/Sec |
0x1B | Reset to Factory Defaults | 0/1 | Bool |
0x1C - 0x1F | Cumulative Running Time | 0 - 2147483647 | Sec |
0x20 - 0x23 | Accumulated Charging Time | 0 - 2147483647 | Sec |
0x24 - 0x27 | Running Time | 0 - 2147483647 | Sec |
0x28 - 0x29 | Version | 1 | Fixed |
0x2A - 0xEF | [Reserved] | NA | NA |
0xF0 - 0xFB | Serial Number | Device UID | Fixed |
0xFC - 0xFF | Factory Testing | NA | NA |
- 0x18 - OTA Firmware Upgrade Mode
Address | Function |
---|---|
0x01 - 0x10 | Encrypted buffer |
0x11 - 0xEF | [Reserved] |
0xF0 - 0xFB | Serial Number |
0xFC - 0xFF | Factory Test |
Gallery
PCB Drawing
Function Area Display
How To Assemble
How To Use
Automatic Shutdown Protection
- Download the latest Raspbian OS image from: [ https://www.raspberrypi.org/software/operating-systems/ ]
- Unzip it and flash it to MicroSD card(TF card) with Etcher imaging tool. [ https://www.balena.io/etcher/ ]
- Follow the installation method to assemble the Raspberry Pi and UPS Plus.
- After booting up and make sure Raspberry Pi can access internet.
- Open a terminal by press "Ctrl+ALT+T" or just click terminal icon on the task bar.
curl -Lso- https://git.io/JLygb
or
curl -Lso- https://raw.githubusercontent.com/geeekpi/upsplus/main/install.sh | bash
- When encountering low battery, it will automatically shut down and turn off the UPS and it will restart when AC power comes.
How To Update UPS Firmware via OTA
- Enter the OTA firmware upgrade mode:
Method 1
NOTE: Do not power off or disconnect the network during the upgrade process. If the upgrade fails, the UPS Pro will not work normally.
- Power off the Raspberry Pi.
- Cut off the external charging power (MicroUSB and USB-C)
- Take out all the batteries.
- Press and hold the UPS Pro switch key and insert the battery into the battery compartment.
At this time, the device will be forced to enter OTA mode.
The function of the switch button will no longer be available at this time.
The Raspberry Pi will be running and please execute the following python script in the system terminal to complete the upgrade.
- Create a new file named: "OTA.py" via this command:
nano OTA.py
- Copy following code into this file:
import os import time import json import smbus import requests # Enter OTA method # Step 1) i2cset -y 1 0x17 50 127 b # Step 2) Shut down the Raspberry Pi normally, unplug all power/batteries, and reinstall. DEVICE_BUS = 1 DEVICE_ADDR = 0x18 UPDATE_URL = "https://api.thekoziolfoundation.com/update" bus = smbus.SMBus(DEVICE_BUS) aReceiveBuf = [] for i in range(240,252): aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) UID0 = "%08X" % (aReceiveBuf[3] << 24 | aReceiveBuf[2] << 16 | aReceiveBuf[1] << 8 | aReceiveBuf[0]) UID1 = "%08X" % (aReceiveBuf[7] << 24 | aReceiveBuf[6] << 16 | aReceiveBuf[5] << 8 | aReceiveBuf[4]) UID2 = "%08X" % (aReceiveBuf[11] << 24 | aReceiveBuf[10] << 16 | aReceiveBuf[9] << 8 | aReceiveBuf[8]) r = requests.post(UPDATE_URL, data={"UID0":UID0,"UID1":UID1,"UID2":UID2}) r = json.loads(r.text) if r['code'] != 0: print('Firmware acquisition failed, reason:' + r['reason']) exit(r['code']) else: print('The firmware is successfully obtained, and the firmware is being downloaded and updated.') req = requests.get(r['url']) f = open("/tmp/firmware.bin", "wb") f.write(req.content) print("Get the firmware successfully!") print('The firmware starts to be upgraded, please keep the power on, interruption in the middle will cause unrecoverable failure of the UPS!') with open('/tmp/firmware.bin','rb') as f: while True: data = f.read(16) for i in range(len(list(data))): bus.write_byte_data(0x18, i + 1,data[i]) bus.write_byte_data(0x18,50,250) time.sleep(0.1) print('.',end='',flush=True) if len(list(data)) == 0: bus.write_byte_data(0x18,50,0) print('.',flush=True) print('The firmware upgrade is complete, please disconnect all power/batteries and reinstall to use the new firmware.') os.system("sudo halt") while True: time.sleep(10)
- Save it by pressing `CTRL+X` and pressing 'Y'.
- Execute it:
python3 OTA.py
The first time it runs, it may prompt that the device is not registered.If it is a legal device, just wait a few seconds and try again.
- UPS Pro Will be turned off after upgrading, Please unplug the power supply, remove the batteries from UPS Pro.
- Insert the batteries back to UPS Pro and then connect power supply and turn it on by press power switch.
Method 2
- Open a terminal and typing:
i2cset -y 1 0x17 50 127 b
- Shutdown Raspberry Pi and remove all batteries and power supply.
- Insert batteries back into the battery slot.
- Execute OTA.py python script.
- UPS Pro Will be turned off after upgrading, Please unplug the power supply, remove the batteries from UPS Pro.
- Insert the batteries back to UPS Pro and then connect power supply and turn it on by press power switch.
How To Enable Auto-Shutdown Protection Function
How To Collect Data
- Data Collection
Data collection will only collect information on abnormal voltage changes and battery charging and discharging status during equipment operation through UPS in order to provide better after-sales service, and will not collect your private information.
Please rest assured that every UPS we sell will Have a unique serial number so that we can provide battery quality analysis and feedback.
It submits your UPS Pro operating status information to the server provided by us through a python script.
1. Create a directory:
mkdir ~/bin
2. Create python script files: upsplus.py and upsplus_iot.py
- code of upsplus.py
import os import time import smbus2 import logging from ina219 import INA219,DeviceRangeError # Define I2C bus DEVICE_BUS = 1 # Define device i2c slave address. DEVICE_ADDR = 0x17 # Set the threshold of UPS automatic power-off to prevent damage caused by battery over-discharge, unit: mV. PROTECT_VOLT = 3700 # Set the sample period, Unit: min default: 2 min. SAMPLE_TIME = 2 # Instance INA219 and getting information from it. ina = INA219(0.00725, address=0x40) ina.configure() print("-"*60) print("------Current information of the detected Raspberry Pi------") print("-"*60) print("Raspberry Pi Supply Voltage: %.3f V" % ina.voltage()) print("Raspberry Pi Current Current Consumption: %.3f V" % ina.current()) print("Raspberry Pi Current Power Consumption: %.3f V" % ina.current()) print("-"*60) # Batteries information ina = INA219(0.005, address=0x45) ina.configure() print("-------------------Batteries information-------------------") print("-"*60) print("Voltage of Batteries: %.3f V" % ina.voltage()) try: if ina.current() > 0: print("Battery Current (Charging) Rate: %.3f mA"% (ina.current())) print("Current Battery Power Supplement: %.3f mW"% ina.power()) else: print("Battery Current (discharge) Rate: %.3f mA"% (0-ina.current())) print("Current Battery Power Consumption: %.3f mW"% ina.power()) print("-"*60) except DeviceRangeError: print("-"*60) print('Battery power is too high.') # Raspberry Pi Communicates with MCU via i2c protocol. bus = smbus2.SMBus(DEVICE_BUS) aReceiveBuf = [] aReceiveBuf.append(0x00) # Read register and add the data to the list: aReceiveBuf for i in range(1, 255): aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) # Enable Back-to-AC fucntion. # Enable: write 1 to register 0x25 # Disable: write 0 to register 0x25 bus.write_byte_data(DEVICE_ADDR, 25, 1) # Reset Protect voltage bus.write_byte_data(DEVICE_ADDR, 17, PROTECT_VOLT & 0xFF) bus.write_byte_data(DEVICE_ADDR, 18, (PROTECT_VOLT >> 8)& 0xFF) print("Successfully set the protection voltage to: %d mV" % PROTECT_VOLT) if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000: print('-'*60) print('Currently charging via Type C Port.') elif (aReceiveBuf[10] << 8 | aReceiveBuf[9])> 4000: print('-'*60) print('Currently charging via Micro USB Port.') else: print('-'*60) print('Currently not charging.') # Consider shutting down to save data or send notifications if ina.voltage() < (PROTECT_VOLT + 200): print('-'*60) print('The battery is going to dead! Ready to shut down!') # It will cut off power when initialized shutdown sequence. bus.write_byte_data(DEVICE_ADDR, 24,240) os.system("sudo sync && sudo halt") while True: time.sleep(10)
- code of upsplus_iot.py # This is Data collection code.
import time import smbus import requests from ina219 import INA219,DeviceRangeError DEVICE_BUS = 1 DEVICE_ADDR = 0x17 PROTECT_VOLT = 3700 SAMPLE_TIME = 2 FEED_URL = "https://api.thekoziolfoundation.com/feed" DATA = dict() ina = INA219(0.00725,address=0x40) ina.configure() DATA['PiVccVolt'] = ina.voltage() DATA['PiIddAmps'] = ina.current() ina = INA219(0.005,address=0x45) ina.configure() DATA['BatVccVolt'] = ina.voltage() try: DATA['BatIddAmps'] = ina.current() except DeviceRangeError: DATA['BatIddAmps'] = 16000 bus = smbus.SMBus(DEVICE_BUS) aReceiveBuf = [] aReceiveBuf.append(0x00) for i in range(1,255): aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) DATA['McuVccVolt'] = aReceiveBuf[2] << 8 | aReceiveBuf[1] DATA['BatPinCVolt'] = aReceiveBuf[6] << 8 | aReceiveBuf[5] DATA['ChargeTypeCVolt'] = aReceiveBuf[8] << 8 | aReceiveBuf[7] DATA['ChargeMicroVolt'] = aReceiveBuf[10] << 8 | aReceiveBuf[9] DATA['BatTemperature'] = aReceiveBuf[12] << 8 | aReceiveBuf[11] DATA['BatFullVolt'] = aReceiveBuf[14] << 8 | aReceiveBuf[13] DATA['BatEmptyVolt'] = aReceiveBuf[16] << 8 | aReceiveBuf[15] DATA['BatProtectVolt'] = aReceiveBuf[18] << 8 | aReceiveBuf[17] DATA['SampleTime'] = aReceiveBuf[22] << 8 | aReceiveBuf[21] DATA['AutoPowerOn'] = aReceiveBuf[24] DATA['OnlineTime'] = aReceiveBuf[31] << 24 | aReceiveBuf[30] << 16 | aReceiveBuf[29] << 8 | aReceiveBuf[28] DATA['FullTime'] = aReceiveBuf[35] << 24 | aReceiveBuf[34] << 16 | aReceiveBuf[33] << 8 | aReceiveBuf[32] DATA['OneshotTime'] = aReceiveBuf[39] << 24 | aReceiveBuf[38] << 16 | aReceiveBuf[37] << 8 | aReceiveBuf[36] DATA['Version'] = aReceiveBuf[41] << 8 | aReceiveBuf[40] DATA['UID0'] = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) DATA['UID1'] = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) DATA['UID2'] = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) print(DATA) r = requests.post(FEED_URL, data=DATA) print(r.text)
- The execution frequency is recommended to be once a minute via crond service. You can edit crontab via:
crontab -e
add following code:
* * * * * /usr/bin/python3 /home/pi/bin/upsPlus.py * * * * * /usr/bin/python3 /home/pi/bin/upsPlus_iot.py
After saving it, check it via:
crontab -l
How To Setup RTC
How To Estimate Battery Power
How To Adjust The Sampling Period
How To Monitor Battery Temperature
How To Enable UPS function
How To Configure Programmable Shutdown/Force Restart
FAQ
- Q: Why does the battery light go off sometime and lights up in a while?
- A: This is because the power chip performs battery re-sampling, and the purpose is that the data of inferior batteries is inaccurate during the sampling process.
- Q: Why is the power cut off every once in a while?
- A: Please check the battery charging current, the data discharge direction or the charging direction. If the load is too large, the charging may not be enough, which will cause this problem.
- Q: What kind of wall charger should I use?
- A: If the load is normal, it is recommended to use an ordinary 5V@2A charging head. If you need to carry a slightly higher load, it is recommended to use a fast charging source. We support FCP, AFC, SFCP protocols Fast charging source.
- Q: Can I directly input 9V and 12V to the USB port?
- A: No, if you must do this, you must remove the DP, DM and other related detection pins, and ensure that the power supply is stable.
- Q: I heard howling, why is this?
- A: Because of the no-load protection mechanism, the howling will disappear after the load is installed.
Keywords
- UPS Plus, GeeekPi UPS V5, UPS for Raspberry Pi