├── Full-featured-demo-code.py ├── LICENSE ├── OTA_firmware_upgrade.py ├── README.md ├── docs └── UPS_Hardware_Firmware.md ├── install.sh ├── uninstall.sh ├── upsplus.py └── upsplus_iot.py /Full-featured-demo-code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Description: 4 | '''This is the demo code for all the functions of UPS Plus. 5 | Advanced users can select the functions they need through the function options provided in the code below to customize and develop them to meet their needs. 6 | ''' 7 | 8 | import time 9 | import smbus2 10 | import logging 11 | from ina219 import INA219,DeviceRangeError 12 | 13 | DEVICE_BUS = 1 14 | DEVICE_ADDR = 0x17 15 | PROTECT_VOLT = 3700 16 | SAMPLE_TIME = 2 17 | 18 | ina_supply = INA219(0.00725, busnum=DEVICE_BUS, address=0x40) 19 | ina_supply.configure() 20 | supply_voltage = ina_supply.voltage() 21 | supply_current = ina_supply.current() 22 | supply_power = ina_supply.power() 23 | print("Raspberry Pi power supply voltage: %.3f V" % supply_voltage) 24 | print("Current current consumption of Raspberry Pi: %.3f mA" % supply_current) 25 | print("Current power consumption of Raspberry Pi: %.3f mW" % supply_power) 26 | 27 | 28 | ina_batt = INA219(0.005, busnum=DEVICE_BUS, address=0x45) 29 | ina_batt.configure() 30 | batt_voltage = ina_batt.voltage() 31 | batt_current = ina_batt.current() 32 | batt_power = ina_batt.power() 33 | print("Batteries Voltage: %.3f V" % batt_voltage) 34 | try: 35 | if batt_current > 0: 36 | print("Battery current (charging), rate: %.3f mA" % batt_current) 37 | print("Current battery power supplement: %.3f mW" % batt_power) 38 | else: 39 | print("Battery current (discharge), rate: %.3f mA" % batt_current) 40 | print("Current battery power consumption: %.3f mW" % batt_power) 41 | except DeviceRangeError: 42 | print('Battery power is too high.') 43 | 44 | bus = smbus2.SMBus(DEVICE_BUS) 45 | 46 | aReceiveBuf = [] 47 | aReceiveBuf.append(0x00) # Placeholder 48 | 49 | for i in range(1,255): 50 | aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) 51 | 52 | print("Current processor voltage: %d mV"% (aReceiveBuf[2] << 8 | aReceiveBuf[1])) 53 | print("Current Raspberry Pi report voltage: %d mV"% (aReceiveBuf[4] << 8 | aReceiveBuf[3])) 54 | print("Current battery port report voltage: %d mV"% (aReceiveBuf[6] << 8 | aReceiveBuf[5])) # This value is inaccurate during charging 55 | print("Current charging interface report voltage (Type C): %d mV"% (aReceiveBuf[8] << 8 | aReceiveBuf[7])) 56 | print("Current charging interface report voltage (Micro USB): %d mV"% (aReceiveBuf[10] << 8 | aReceiveBuf[9])) 57 | 58 | if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000: 59 | print('Currently charging through Type C.') 60 | elif (aReceiveBuf[10] << 8 | aReceiveBuf[9]) > 4000: 61 | print('Currently charging via Micro USB.') 62 | else: 63 | print('Currently not charging.') # Consider shutting down to save data or send notifications 64 | 65 | print("Current battery temperature (estimated): %d degC"% (aReceiveBuf[12] << 8 | aReceiveBuf[11])) # Learned from the battery internal resistance change, the longer the use, the more stable the data. 66 | print("Full battery voltage: %d mV"% (aReceiveBuf[14] << 8 | aReceiveBuf[13])) 67 | print("Battery empty voltage: %d mV"% (aReceiveBuf[16] << 8 | aReceiveBuf[15])) 68 | print("Battery protection voltage: %d mV"% (aReceiveBuf[18] << 8 | aReceiveBuf[17])) 69 | print("Battery remaining capacity: %d %%"% (aReceiveBuf[20] << 8 | aReceiveBuf[19])) # At least one complete charge and discharge cycle is passed before this value is meaningful. 70 | print("Sampling period: %d Min"% (aReceiveBuf[22] << 8 | aReceiveBuf[21])) 71 | if aReceiveBuf[23] == 1: 72 | print("Current power state: normal") 73 | else: 74 | print("Current power status: off") 75 | 76 | if aReceiveBuf[24] == 0: 77 | print('No shutdown countdown!') 78 | else: 79 | print("Shutdown countdown: %d sec"% (aReceiveBuf[24])) 80 | 81 | if aReceiveBuf[25] == 1: 82 | print("Automatically turn on when there is external power supply!") 83 | else: 84 | print("Does not automatically turn on when there is an external power supply!") 85 | if aReceiveBuf[26] == 0: 86 | print('No restart countdown!') 87 | else: 88 | print("Restart countdown: %d sec"% (aReceiveBuf[26])) 89 | 90 | print("Accumulated running time: %d sec"% (aReceiveBuf[31] << 24 | aReceiveBuf[30] << 16 | aReceiveBuf[29] << 8 | aReceiveBuf[28])) 91 | print("Accumulated charged time: %d sec"% (aReceiveBuf[35] << 24 | aReceiveBuf[34] << 16 | aReceiveBuf[33] << 8 | aReceiveBuf[32])) 92 | print("This running time: %d sec"% (aReceiveBuf[39] << 24 | aReceiveBuf[38] << 16 | aReceiveBuf[37] << 8 | aReceiveBuf[36])) 93 | print("Version number: %d "% (aReceiveBuf[41] << 8 | aReceiveBuf[40])) 94 | 95 | #The following code demonstrates resetting the protection voltage 96 | # bus.write_byte_data(DEVICE_ADDR, 17,PROTECT_VOLT & 0xFF) 97 | # bus.write_byte_data(DEVICE_ADDR, 18,(PROTECT_VOLT >> 8)& 0xFF) 98 | # print("Successfully set the protection voltage as: %d mV"% PROTECT_VOLT) 99 | 100 | #The following code demonstrates resetting the sampling period 101 | # bus.write_byte_data(DEVICE_ADDR, 21,SAMPLE_TIME & 0xFF) 102 | # bus.write_byte_data(DEVICE_ADDR, 22,(SAMPLE_TIME >> 8)& 0xFF) 103 | # print("Successfully set the sampling period as: %d Min"% SAMPLE_TIME) 104 | 105 | # Set to shut down after 240 seconds (can be reset repeatedly) 106 | # bus.write_byte_data(DEVICE_ADDR, 24,240) 107 | bus.write_byte_data(DEVICE_ADDR, 24,240) 108 | 109 | # Cancel automatic shutdown 110 | # bus.write_byte_data(DEVICE_ADDR, 24,0) 111 | 112 | # Automatically turn on when there is an external power supply (If the automatic shutdown is set, when there is an external power supply, it will shut down and restart the board.) 113 | # 1) If you want to completely shut down, please don't turn on the automatic startup when there is an external power supply. 114 | # 2) If you want to shut down the UPS yourself because of low battery power, you can shut down the UPS first, and then automatically recover when the external power supply comes. 115 | # 3) If you simply want to force restart the power, please use another method. 116 | # 4) Set to 0 to cancel automatic startup. 117 | # 5) If this automatic startup is not set, and the battery is exhausted and shut down, the system will resume work when the power is restored as much as possible, but it is not necessarily when the external power supply is plugged in. 118 | # bus.write_byte_data(DEVICE_ADDR, 25,1) 119 | bus.write_byte_data(DEVICE_ADDR, 25,1) 120 | 121 | # Force restart (simulate power plug, write the corresponding number of seconds, shut down 5 seconds before the end of the countdown, and then turn on at 0 seconds.) 122 | # bus.write_byte_data(DEVICE_ADDR, 26,30) 123 | bus.write_byte_data(DEVICE_ADDR, 26, 10) 124 | 125 | # Restore factory settings (clear memory, clear learning parameters, can not clear the cumulative running time, used for after-sales purposes.) 126 | # bus.write_byte_data(DEVICE_ADDR, 27,1) 127 | 128 | # Enter the OTA state (the user demo program should not have this thing, after setting, unplug the external power supply, unplug the battery, reinstall the battery, install the external power supply (optional), you can enter the OTA mode and upgrade the firmware.) 129 | # bus.write_byte_data(DEVICE_ADDR, 50,127) 130 | 131 | # Serial Number 132 | UID0 = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) 133 | UID1 = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) 134 | UID2 = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) 135 | print("Serial Number is:" + UID0 + "-" + UID1 + "-" + UID2 ) 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 52 information Technology., LTD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OTA_firmware_upgrade.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import time 5 | import json 6 | import smbus2 # Required: smbus2 - pip3 install smbus2 7 | import requests 8 | 9 | # How to enter into OTA mode: 10 | # Method 1) Setting register in terminal: i2cset -y 1 0x17 50 127 b 11 | # Method 2) Remove all power connections and batteries, and then hold the power button, insert the batteries. 12 | 13 | # Define device bus and address, and firmware url. 14 | DEVICE_BUS = 1 15 | DEVICE_ADDR = 0x18 16 | UPDATE_URL = "https://api.52pi.com/update" 17 | 18 | # instance of bus. 19 | bus = smbus2.SMBus(DEVICE_BUS) 20 | aReceiveBuf = [] 21 | 22 | for i in range(240, 252): 23 | aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) 24 | 25 | UID0 = "%08X" % (aReceiveBuf[3] << 24 | aReceiveBuf[2] << 16 | aReceiveBuf[1] << 8 | aReceiveBuf[0]) 26 | UID1 = "%08X" % (aReceiveBuf[7] << 24 | aReceiveBuf[6] << 16 | aReceiveBuf[5] << 8 | aReceiveBuf[4]) 27 | UID2 = "%08X" % (aReceiveBuf[11] << 24 | aReceiveBuf[10] << 16 | aReceiveBuf[9] << 8 | aReceiveBuf[8]) 28 | 29 | r = requests.post(UPDATE_URL, data={"UID0": UID0, "UID1": UID1, "UID2": UID2}) 30 | # You can also specify your version, so you can rollback/forward to the specified version 31 | # r = requests.post(UPDATE_URL, data={"UID0":UID0, "UID1":UID1, "UID2":UID2, "ver":7}) 32 | r = json.loads(r.text) 33 | if r['code'] != 0: 34 | print('Can not get the firmware due to:' + r['reason']) 35 | exit(r['code']) 36 | else: 37 | print('Pass the authentication, downloading the latest firmware...') 38 | req = requests.get(r['url']) 39 | if req.status_code == 404: 40 | print('version not found!') 41 | exit(-1) 42 | with open("/tmp/firmware.bin", "wb") as f: 43 | f.write(req.content) 44 | print("Download firmware successful.") 45 | 46 | print( 47 | "The firmware starts to be upgraded, please keep the power on, interruption in the middle will cause " 48 | "unrecoverable failure of the UPS!") 49 | with open("/tmp/firmware.bin", "rb") as f: 50 | while True: 51 | data = f.read(16) 52 | for i in range(len(list(data))): 53 | bus.write_byte_data(0x18, i + 1, data[i]) 54 | bus.write_byte_data(0x18, 50, 250) 55 | time.sleep(0.1) 56 | print('.', end='', flush=True) 57 | 58 | if len(list(data)) == 0: 59 | bus.write_byte_data(0X18, 50, 0) 60 | print('.', flush=True) 61 | print('The firmware upgrade is complete, please disconnect all power/batteries and reinstall to use ' 62 | 'the new firmware.') 63 | os.system("sudo halt") 64 | while True: 65 | time.sleep(10) 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # upsplus 2 | ## UPS Plus is a new generation of UPS power management module. 3 | It is an `improved` version of the original UPS prototype. 4 | * It has been fixed the bug that UPS could not charge and automatically power off during work time. 5 | * It can not only perform good battery power management, but also provide stable voltage output and RTC functions. 6 | * At the same time,it support for FCP, AFC, SFCP fast charge protocol, support BC1.2 charging protocol, support battery terminal current/voltage monitoring and support two-way monitoring of charge and discharge. 7 | * It can provide programmable PVD function. 8 | - Power Voltage Detector (PVD) can be used to detect if batteries voltage is below or above configured voltage. 9 | - Once this function has been enabled, it will monitoring your batteries voltage, and you can control whether or not shut down Raspberry Pi via simple bash script or python script. 10 | - This function will protect your batteries from damage caused by excessive discharge. 11 | * It can provide Adjustable data sampling Rate. 12 | - This function allows you to adjust the data sampling rate so that you can get more detailed battery information and also it will consume some power. 13 | - The data sampling information can communicate with the upper computer device through the I2C protocol. 14 | * UPS Plus supports the OTA firmware upgrade function. 15 | - Once there is a new firmware update, it is very convenient for you to upgrade firmware for UPS Plus. The firmware upgrade can be completed only by connecting to the Internet,and execute a python script. 16 | * Support battery temperature monitoring and power-down memory function. 17 | * UPS Plus can be set to automatically start the Raspberry Pi after the external power comes on. 18 | - The programmable shutdown and forced restart function will provide you with a remote power-off restart management method. 19 | - That means you don’t need to go Unplug the power cable or press the power button to cut off the power again. 20 | - You can set the program to disconnect the power supply after a few seconds after the Raspberry Pi is shut down properly. 21 | - And you can also reconnect the power supply after a forced power failure to achieve a remote power-off and restart operation. 22 | - Once it was setting up, you don't need to press power button to boot up your device which is very suitable for smart home application scenarios. 23 | ## How to use 24 | * Download Repository and execute: 25 | ```bash 26 | cd ~ 27 | curl -Lso- https://git.io/JLygb 28 | ``` 29 | When encountering low battery, it will automatically shut down and turn off the UPS and it will restart when AC power comes. 30 | ## How to upgrade firmware of UPS. 31 | * Upgrade firmware will be via `OTA` style, `OTA` means `over the air`, it allows you `update` or `upgrade` firmware via internet. 32 | - 1. Make sure Raspberry Pi can access internet. 33 | - 2. Download Repository from `GitHub`. 34 | ```bash 35 | cd ~ 36 | git clone https://github.com/geeekpi/upsplus.git 37 | cd upsplus/ 38 | python3 OTA_firmware_upgrade.py 39 | ``` 40 | When `upgrade` process is finished, it will `shutdown` your Raspberry Pi automatically, and you `need` to disconnect the charger and remove all batteries from UPS and then insert the batteries again, then press the power button to turn on the UPS. 41 | *** NOTE: Do not assemble UPS with Raspberry Pi with Batteries in it *** 42 | 43 | ## Battery List. 44 | * A list of Batteries used by UPSPlus users community. 45 | 46 | | Brand | Model | Volt | mAmp | SAMPLE_TIME | Testing | Time | 47 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | 48 | | DoublePow | ICR18650 | 3.7 | 2600 | 3 | | +180days | 49 | | GTL EvreFire | ICR18650 | 3.7 | 9900 | 3 | X | 50 | | XTAR | ICR18650 | 3.7 | 2600 | 3 | X | 51 | 52 | Don't forget replace PROTECT_VOLT variable value, from Battery Volt value. Example: Battery 3.6V = 3600 53 | 54 | ## FAQ 55 | * Q: Why does the battery light go off sometime and lights up in a while? 56 | - 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. 57 | 58 | * Q: Why is the power cut off every once in a while? 59 | - 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. 60 | * Q: What kind of wall charger should I use? 61 | - 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. 62 | * Q: Can I directly input 9V and 12V to the USB port? 63 | - 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. 64 | * Q: I heard howling, why is this? 65 | - A: Because of the no-load protection mechanism, the howling will disappear after the load is installed. 66 | -------------------------------------------------------------------------------- /docs/UPS_Hardware_Firmware.md: -------------------------------------------------------------------------------- 1 | # Firmware Version History & Update Schedule 2 | 3 | ------ 4 | Current firmware version:V7 5 | 6 | | Version | Date | 7 | | :--------: | :-----: | 8 | | V3 | - | 9 | | V7 | 2021-05-13 | 10 | | V8 | 2021-05-31 | 11 | | V9 | 2021-07-21 | 12 | | V10 | 2021-19-02 | 13 | 14 | Version V3: 15 | 16 | - This is the earliest firmware available to the public. 17 | 18 | Version V7: 19 | 20 | - Fix the issue of not able to perform shutdown operation when there is an external power supply. 21 | - [ Full Voltage and Empty Voltage ] Can be edited manually [#16][1] 22 | 23 | Version V8: 24 | 25 | - Fix the problem that the power button cannot be turned off when [Back-To-AC Auto Power up] is set. 26 | - Fix an intermittent freeze during 400kHz I2C access. 27 | 28 | Version V9: 29 | 30 | - The UPS (hardware version PCB01c) no longer uses NCE20P70G/NCE20P85G as a power supply switch because the supplier has been out of stock for a long time and there is no direct replacement, so it is replaced by other similar devices. 31 | - [Bug fix] Due to compiler optimization, some execution logic may be skipped in some cases, which does not affect the basic I2C operation, but some readings may be incorrect. 32 | - [Bug fix] The manual voltage can not be set, please note that the manual setting threshold voltage only affects the charging logic, and does not change the parameters of the battery itself. 33 | 34 | Version V10: 35 | 36 | - After 30 days of continuous and multi-faceted testing, the V10 firmware fixes a random freeze issue, mainly as a result of possible interruptions in I2C accesses causing I2C lock-ups, a known issue with ST's ST controllers, using the FAE recommended method to fix. 37 | 38 | 39 | [1]: https://github.com/geeekpi/upsplus/issues/16 "Issue #16" 40 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # UPS Plus installation script. 3 | 4 | # initializing init-functions. 5 | . /lib/lsb/init-functions 6 | sudo raspi-config nonint do_i2c 0 7 | 8 | # check if the network is working properly. 9 | log_action_msg "Welcome to 52Pi Technology UPS Plus auto-install Program!" 10 | log_action_msg "More information please visit here:" 11 | log_action_msg "-----------------------------------------------------" 12 | log_action_msg "https://wiki.52pi.com/index.php/UPS_Plus_SKU:_EP-0136" 13 | log_action_msg "-----------------------------------------------------" 14 | log_action_msg "Start the configuration environment check..." 15 | ping_result=`ping -c 4 www.github.com &> /dev/null` 16 | if [[ $ping_result -ne 0 ]]; then 17 | log_failure_msg "Network is not available!" 18 | log_warning_msg "Please check the network configuration and try again!" 19 | else 20 | log_success_msg "Network status is ok..." 21 | fi 22 | 23 | # Package check and installation 24 | install_pkgs() 25 | { 26 | `sudo apt-get -qq update` 27 | `sudo apt-get -y -qq install sudo git i2c-tools` 28 | } 29 | 30 | log_action_msg "Start the software check..." 31 | pkgs=`dpkg -l | awk '{print $2}' | egrep ^git$` 32 | if [[ $pkgs = 'git' ]]; then 33 | log_success_msg "git has been installed." 34 | else 35 | log_action_msg "Installing git package..." 36 | install_pkgs 37 | if [[ $? -eq 0 ]]; then 38 | log_success_msg "Package installation successfully." 39 | else 40 | log_failure_msg "Package installation is failed,please install git package manually or check the repository" 41 | fi 42 | fi 43 | 44 | # create python virtual environment 45 | python3 -m venv .venv 46 | source .venv/bin/activate 47 | 48 | # install pi-ina219 library. 49 | log_action_msg "Installing pi-ina219 library..." 50 | python3 -m pip install pi-ina219 51 | if [[ $? -eq 0 ]]; then 52 | log_success_msg "pi-ina219 installation successful." 53 | else 54 | log_failure_msg "pi-ina219 installation failed!" 55 | log_warning_msg "Please install it by manual: python3 -m pip install pi-ina219" 56 | fi 57 | 58 | # install smbus2 library. 59 | log_action_msg "Installing smbus2 library..." 60 | python3 -m pip install smbus2 61 | if [[ $? -eq 0 ]]; then 62 | log_success_msg "smbus2 installation successful." 63 | else 64 | log_failure_msg "smbus2 installation failed!" 65 | log_warning_msg "Please install it by manual: python3 -m pip install smbus2" 66 | fi 67 | 68 | # install requests library. 69 | log_action_msg "Installing requests library..." 70 | python3 -m pip install requests 71 | if [[ $? -eq 0 ]]; then 72 | log_success_msg "requests installation successful." 73 | else 74 | log_failure_msg "requests installation failed!" 75 | log_warning_msg "Please install it by manual: python3 -m pip install requests" 76 | fi 77 | 78 | # TODO: Create daemon service or crontab by creating python scripts. 79 | # create bin folder and create python script to detect UPS's status. 80 | log_action_msg "create $HOME/bin directory..." 81 | /bin/mkdir -p $HOME/bin 82 | export PATH=$PATH:$HOME/bin 83 | 84 | # Create python script. 85 | cat > $HOME/bin/upsPlus.py << EOF 86 | #!/usr/bin/env python3 87 | 88 | import os 89 | import time 90 | import smbus2 91 | import logging 92 | from ina219 import INA219,DeviceRangeError 93 | 94 | 95 | # Define I2C bus 96 | DEVICE_BUS = 1 97 | 98 | # Define device i2c slave address. 99 | DEVICE_ADDR = 0x17 100 | 101 | # Set the threshold of UPS automatic power-off to prevent damage caused by battery over-discharge, unit: mV. 102 | PROTECT_VOLT = 3700 103 | 104 | # Set the sample period, Unit: min default: 2 min. 105 | SAMPLE_TIME = 2 106 | 107 | # Instance INA219 and getting information from it. 108 | ina_supply = INA219(0.00725, busnum=DEVICE_BUS, address=0x40) 109 | ina_supply.configure() 110 | supply_voltage = ina_supply.voltage() 111 | supply_current = ina_supply.current() 112 | supply_power = ina_supply.power() 113 | print("-"*60) 114 | print("------Current information of the detected Raspberry Pi------") 115 | print("-"*60) 116 | print("Raspberry Pi Supply Voltage: %.3f V" % supply_voltage) 117 | print("Raspberry Pi Current Current Consumption: %.3f mA" % supply_current) 118 | print("Raspberry Pi Current Power Consumption: %.3f mW" % supply_power) 119 | print("-"*60) 120 | 121 | # Batteries information 122 | ina_batt = INA219(0.005, busnum=DEVICE_BUS, address=0x45) 123 | ina_batt.configure() 124 | batt_voltage = ina_batt.voltage() 125 | batt_current = ina_batt.current() 126 | batt_power = ina_batt.power() 127 | print("-------------------Batteries information-------------------") 128 | print("-"*60) 129 | print("Voltage of Batteries: %.3f V" % batt_voltage) 130 | try: 131 | if batt_current > 0: 132 | print("Battery Current (Charging) Rate: %.3f mA"% batt_current) 133 | print("Current Battery Power Supplement: %.3f mW"% batt_power) 134 | else: 135 | print("Battery Current (discharge) Rate: %.3f mA"% batt_current) 136 | print("Current Battery Power Consumption: %.3f mW"% batt_power) 137 | print("-"*60) 138 | except DeviceRangeError: 139 | print("-"*60) 140 | print('Battery power is too high.') 141 | 142 | # Raspberry Pi Communicates with MCU via i2c protocol. 143 | bus = smbus2.SMBus(DEVICE_BUS) 144 | 145 | aReceiveBuf = [] 146 | aReceiveBuf.append(0x00) 147 | 148 | # Read register and add the data to the list: aReceiveBuf 149 | for i in range(1, 255): 150 | aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) 151 | 152 | # Enable Back-to-AC fucntion. 153 | # Enable: write 1 to register 0x19 == 25 154 | # Disable: write 0 to register 0x19 == 25 155 | 156 | bus.write_byte_data(DEVICE_ADDR, 25, 1) 157 | 158 | # Reset Protect voltage 159 | bus.write_byte_data(DEVICE_ADDR, 17, PROTECT_VOLT & 0xFF) 160 | bus.write_byte_data(DEVICE_ADDR, 18, (PROTECT_VOLT >> 8)& 0xFF) 161 | print("Successfully set the protection voltage to: %d mV" % PROTECT_VOLT) 162 | 163 | if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000: 164 | print('-'*60) 165 | print('Currently charging via Type C Port.') 166 | elif (aReceiveBuf[10] << 8 | aReceiveBuf[9])> 4000: 167 | print('-'*60) 168 | print('Currently charging via Micro USB Port.') 169 | else: 170 | print('-'*60) 171 | print('Currently not charging.') 172 | # Consider shutting down to save data or send notifications 173 | if ((batt_voltage * 1000) < (PROTECT_VOLT + 200)): 174 | print('-'*60) 175 | print('The battery is going to dead! Ready to shut down!') 176 | # It will cut off power when initialized shutdown sequence. 177 | bus.write_byte_data(DEVICE_ADDR, 24,240) 178 | os.system("sudo sync && sudo halt") 179 | while True: 180 | time.sleep(10) 181 | EOF 182 | log_action_msg "Create python3 script in location: $HOME/bin/upsPlus.py Successful" 183 | # Upload the battery status to the data platform for subsequent technical support services 184 | # Create python file 185 | cat > $HOME/bin/upsPlus_iot.py << EOF 186 | #!/usr/bin/env python3 187 | 188 | import time 189 | import smbus2 190 | import requests 191 | from ina219 import INA219,DeviceRangeError 192 | import random 193 | 194 | DEVICE_BUS = 1 195 | DEVICE_ADDR = 0x17 196 | PROTECT_VOLT = 3700 197 | SAMPLE_TIME = 2 198 | FEED_URL = "https://api.52pi.com/feed" 199 | time.sleep(random.randint(0, 59)) 200 | 201 | DATA = dict() 202 | 203 | ina_supply = INA219(0.00725, busnum=DEVICE_BUS, address=0x40) 204 | ina_supply.configure() 205 | supply_voltage = ina_supply.voltage() 206 | supply_current = ina_supply.current() 207 | DATA['PiVccVolt'] = supply_voltage 208 | DATA['PiIddAmps'] = supply_current 209 | 210 | ina_batt = INA219(0.005, busnum=DEVICE_BUS, address=0x45) 211 | ina_batt.configure() 212 | batt_voltage = ina_batt.voltage() 213 | batt_current = ina_batt.current() 214 | DATA['BatVccVolt'] = batt_voltage 215 | try: 216 | DATA['BatIddAmps'] = batt_current 217 | except DeviceRangeError: 218 | DATA['BatIddAmps'] = 16000 219 | 220 | bus = smbus2.SMBus(DEVICE_BUS) 221 | 222 | aReceiveBuf = [] 223 | aReceiveBuf.append(0x00) # 占位符 224 | 225 | for i in range(1,255): 226 | aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) 227 | 228 | DATA['McuVccVolt'] = aReceiveBuf[2] << 8 | aReceiveBuf[1] 229 | DATA['BatPinCVolt'] = aReceiveBuf[6] << 8 | aReceiveBuf[5] 230 | DATA['ChargeTypeCVolt'] = aReceiveBuf[8] << 8 | aReceiveBuf[7] 231 | DATA['ChargeMicroVolt'] = aReceiveBuf[10] << 8 | aReceiveBuf[9] 232 | 233 | DATA['BatTemperature'] = aReceiveBuf[12] << 8 | aReceiveBuf[11] 234 | DATA['BatFullVolt'] = aReceiveBuf[14] << 8 | aReceiveBuf[13] 235 | DATA['BatEmptyVolt'] = aReceiveBuf[16] << 8 | aReceiveBuf[15] 236 | DATA['BatProtectVolt'] = aReceiveBuf[18] << 8 | aReceiveBuf[17] 237 | DATA['SampleTime'] = aReceiveBuf[22] << 8 | aReceiveBuf[21] 238 | DATA['AutoPowerOn'] = aReceiveBuf[25] 239 | 240 | DATA['OnlineTime'] = aReceiveBuf[31] << 24 | aReceiveBuf[30] << 16 | aReceiveBuf[29] << 8 | aReceiveBuf[28] 241 | DATA['FullTime'] = aReceiveBuf[35] << 24 | aReceiveBuf[34] << 16 | aReceiveBuf[33] << 8 | aReceiveBuf[32] 242 | DATA['OneshotTime'] = aReceiveBuf[39] << 24 | aReceiveBuf[38] << 16 | aReceiveBuf[37] << 8 | aReceiveBuf[36] 243 | DATA['Version'] = aReceiveBuf[41] << 8 | aReceiveBuf[40] 244 | 245 | DATA['UID0'] = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) 246 | DATA['UID1'] = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) 247 | DATA['UID2'] = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) 248 | 249 | print(DATA) 250 | r = requests.post(FEED_URL, data=DATA) 251 | print(r.text) 252 | 253 | EOF 254 | log_success_msg "Create UPS Plus IoT customer service python script successful" 255 | # Add script to crontab 256 | log_action_msg "Add into general crontab list." 257 | 258 | (crontab -l 2>/dev/null; echo "* * * * * $HOME/.venv/bin/python3 $HOME/bin/upsPlus.py > /tmp/upsPlus.log") | crontab - 259 | (crontab -l 2>/dev/null; echo "* * * * * $HOME/.venv/bin/python3 $HOME/bin/upsPlus_iot.py > /tmp/upsPlus_iot.log") | crontab - 260 | sudo systemctl restart cron 261 | 262 | if [[ $? -eq 0 ]]; then 263 | log_action_msg "crontab has been created successful!" 264 | else 265 | log_failure_msg "Create crontab failed!!" 266 | log_warning_msg "Please create crontab manually." 267 | log_action_msg "Usage: crontab -e" 268 | fi 269 | 270 | # Testing and Greetings 271 | if [[ -e $HOME/bin/upsPlus.py ]]; then 272 | python3 $HOME/bin/upsPlus.py 273 | if [[ $? -eq 0 ]]; then 274 | log_success_msg "UPS Plus Installation is Complete!" 275 | log_action_msg "-----------------More Information--------------------" 276 | log_action_msg "https://wiki.52pi.com/index.php/UPS_Plus_SKU:_EP-0136" 277 | log_action_msg "-----------------------------------------------------" 278 | else 279 | log_failure_msg "UPS Plus Installation is Incomplete!" 280 | log_action_msg "Please visit wiki for more information:" 281 | log_action_msg "-----------------------------------------------------" 282 | log_action_msg "https://wiki.52pi.com/index.php/UPS_Plus_SKU:_EP-0136" 283 | log_action_msg "-----------------------------------------------------" 284 | fi 285 | fi 286 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # uninstall shell script 3 | . /lib/lsb/init-functions 4 | 5 | # Remove crontab 6 | log_action_msg "Remove crontab " 7 | crontab -l | grep -v 'upsPlus' | crontab - 8 | cron_result=$(crontab -l | grep -c -i 'upsPlus') 9 | if [[ $cron_result -gt 0 ]]; then 10 | log_failure_msg "Can not remove crontab, please do it manually." 11 | else 12 | log_success_msg "Crontab for upsPlus has been removed." 13 | fi 14 | # Remove $HOME/bin/upsPlus* 15 | 16 | if ! rm -f "$HOME"/bin/upsPlus*; then 17 | log_failure_msg "Can not remove $HOME/bin/upsPlus.*, please remove it manully." 18 | else 19 | log_success_msg "Remove $HOME/bin/upsPlus.* successful." 20 | fi 21 | # TODO: Greetings 22 | log_success_msg "52Pi UPS Plus python script has been removed successful" 23 | log_action_msg "------------------More Information---------------------" 24 | log_action_msg "https://wiki.52pi.com/index.php/UPS_Plus_SKU:_EP-0136" 25 | log_action_msg "----------------Thanks from 52Pi Team------------------" 26 | -------------------------------------------------------------------------------- /upsplus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # '''Enable Auto-Shutdown Protection Function ''' 4 | import os 5 | import time 6 | import smbus2 7 | import logging 8 | from ina219 import INA219,DeviceRangeError 9 | 10 | 11 | # Define I2C bus 12 | DEVICE_BUS = 1 13 | 14 | # Define device i2c slave address. 15 | DEVICE_ADDR = 0x17 16 | 17 | # Set the threshold of UPS automatic power-off to prevent damage caused by battery over-discharge, unit: mV. 18 | PROTECT_VOLT = 3500 19 | 20 | # Set the sample period, Unit: min default: 2 min. 21 | SAMPLE_TIME = 2 22 | 23 | # Instance INA219 and getting information from it. 24 | ina_supply = INA219(0.00725, busnum=DEVICE_BUS, address=0x40) 25 | ina_supply.configure() 26 | supply_voltage = ina_supply.voltage() 27 | supply_current = ina_supply.current() 28 | supply_power = ina_supply.power() 29 | print("-"*60) 30 | print("------Current information of the detected Raspberry Pi------") 31 | print("-"*60) 32 | print("Raspberry Pi Supply Voltage: %.3f V" % supply_voltage) 33 | print("Raspberry Pi Current Current Consumption: %.3f mA" % supply_current) 34 | print("Raspberry Pi Current Power Consumption: %.3f mW" % supply_power) 35 | print("-"*60) 36 | 37 | # Batteries information 38 | ina_batt = INA219(0.005, busnum=DEVICE_BUS, address=0x45) 39 | ina_batt.configure() 40 | batt_voltage = ina_batt.voltage() 41 | batt_current = ina_batt.current() 42 | batt_power = ina_batt.power() 43 | print("-------------------Batteries information-------------------") 44 | print("-"*60) 45 | print("Voltage of Batteries: %.3f V" % batt_voltage) 46 | try: 47 | if batt_current > 0: 48 | print("Battery Current (Charging) Rate: %.3f mA"% batt_current) 49 | print("Current Battery Power Supplement: %.3f mW"% batt_power) 50 | else: 51 | print("Battery Current (discharge) Rate: %.3f mA"% batt_current) 52 | print("Current Battery Power Consumption: %.3f mW"% batt_power) 53 | print("-"*60) 54 | except DeviceRangeError: 55 | print("-"*60) 56 | print('Battery power is too high.') 57 | 58 | # Raspberry Pi Communicates with MCU via i2c protocol. 59 | bus = smbus2.SMBus(DEVICE_BUS) 60 | 61 | aReceiveBuf = [] 62 | aReceiveBuf.append(0x00) 63 | 64 | # Read register and add the data to the list: aReceiveBuf 65 | for i in range(1, 255): 66 | aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) 67 | 68 | # Enable Back-to-AC fucntion. 69 | # Enable: write 1 to register 0x19 == 25 70 | # Disable: write 0 to register 0x19 == 25 71 | 72 | bus.write_byte_data(DEVICE_ADDR, 25, 1) 73 | 74 | # Reset Protect voltage 75 | bus.write_byte_data(DEVICE_ADDR, 17, PROTECT_VOLT & 0xFF) 76 | bus.write_byte_data(DEVICE_ADDR, 18, (PROTECT_VOLT >> 8)& 0xFF) 77 | print("Successfully set the protection voltage to: %d mV" % PROTECT_VOLT) 78 | 79 | UID0 = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) 80 | UID1 = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) 81 | UID2 = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) 82 | print('UID:' + UID0 + '/' + UID1 + '/' + UID2) 83 | 84 | if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000: 85 | print('-'*60) 86 | print('Currently charging via Type C Port.') 87 | elif (aReceiveBuf[10] << 8 | aReceiveBuf[9])> 4000: 88 | print('-'*60) 89 | print('Currently charging via Micro USB Port.') 90 | else: 91 | print('-'*60) 92 | print('Currently not charging.') 93 | # Consider shutting down to save data or send notifications 94 | if (str(batt_voltage)) == ("0.0"): 95 | print("Bad battery voltage value") 96 | if (str(batt_voltage)) != ("0.0"): 97 | if ((batt_voltage * 1000) < (PROTECT_VOLT + 200)): 98 | print('-'*60) 99 | print('The battery is going to dead! Ready to shut down!') 100 | # It will cut off power when initialized shutdown sequence. 101 | bus.write_byte_data(DEVICE_ADDR, 24,240) 102 | os.system("sudo sync && sudo halt") 103 | while True: 104 | time.sleep(10) 105 | 106 | -------------------------------------------------------------------------------- /upsplus_iot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ''' Update the status of batteries to IoT platform ''' 4 | import time 5 | import smbus2 6 | import requests 7 | from ina219 import INA219,DeviceRangeError 8 | import random 9 | 10 | DEVICE_BUS = 1 11 | DEVICE_ADDR = 0x17 12 | PROTECT_VOLT = 3700 13 | SAMPLE_TIME = 2 14 | FEED_URL = "https://api.52pi.com/feed" 15 | time.sleep(random.randint(0, 59)) 16 | 17 | DATA = dict() 18 | 19 | ina_supply = INA219(0.00725, busnum=DEVICE_BUS, address=0x40) 20 | ina_supply.configure() 21 | supply_voltage = ina_supply.voltage() 22 | supply_current = ina_supply.current() 23 | DATA['PiVccVolt'] = supply_voltage 24 | DATA['PiIddAmps'] = supply_current 25 | 26 | ina_batt = INA219(0.005, busnum=DEVICE_BUS, address=0x45) 27 | ina_batt.configure() 28 | batt_voltage = ina_batt.voltage() 29 | batt_current = ina_batt.current() 30 | DATA['BatVccVolt'] = batt_voltage 31 | try: 32 | DATA['BatIddAmps'] = batt_current 33 | except DeviceRangeError: 34 | DATA['BatIddAmps'] = 16000 35 | 36 | bus = smbus2.SMBus(DEVICE_BUS) 37 | 38 | aReceiveBuf = [] 39 | aReceiveBuf.append(0x00) 40 | 41 | for i in range(1,255): 42 | aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i)) 43 | 44 | DATA['McuVccVolt'] = aReceiveBuf[2] << 8 | aReceiveBuf[1] 45 | DATA['BatPinCVolt'] = aReceiveBuf[6] << 8 | aReceiveBuf[5] 46 | DATA['ChargeTypeCVolt'] = aReceiveBuf[8] << 8 | aReceiveBuf[7] 47 | DATA['ChargeMicroVolt'] = aReceiveBuf[10] << 8 | aReceiveBuf[9] 48 | 49 | DATA['BatTemperature'] = aReceiveBuf[12] << 8 | aReceiveBuf[11] 50 | DATA['BatFullVolt'] = aReceiveBuf[14] << 8 | aReceiveBuf[13] 51 | DATA['BatEmptyVolt'] = aReceiveBuf[16] << 8 | aReceiveBuf[15] 52 | DATA['BatProtectVolt'] = aReceiveBuf[18] << 8 | aReceiveBuf[17] 53 | DATA['SampleTime'] = aReceiveBuf[22] << 8 | aReceiveBuf[21] 54 | DATA['AutoPowerOn'] = aReceiveBuf[25] 55 | 56 | DATA['OnlineTime'] = aReceiveBuf[31] << 24 | aReceiveBuf[30] << 16 | aReceiveBuf[29] << 8 | aReceiveBuf[28] 57 | DATA['FullTime'] = aReceiveBuf[35] << 24 | aReceiveBuf[34] << 16 | aReceiveBuf[33] << 8 | aReceiveBuf[32] 58 | DATA['OneshotTime'] = aReceiveBuf[39] << 24 | aReceiveBuf[38] << 16 | aReceiveBuf[37] << 8 | aReceiveBuf[36] 59 | DATA['Version'] = aReceiveBuf[41] << 8 | aReceiveBuf[40] 60 | 61 | DATA['UID0'] = "%08X" % (aReceiveBuf[243] << 24 | aReceiveBuf[242] << 16 | aReceiveBuf[241] << 8 | aReceiveBuf[240]) 62 | DATA['UID1'] = "%08X" % (aReceiveBuf[247] << 24 | aReceiveBuf[246] << 16 | aReceiveBuf[245] << 8 | aReceiveBuf[244]) 63 | DATA['UID2'] = "%08X" % (aReceiveBuf[251] << 24 | aReceiveBuf[250] << 16 | aReceiveBuf[249] << 8 | aReceiveBuf[248]) 64 | 65 | print(DATA) 66 | r = requests.post(FEED_URL, data=DATA) 67 | print(r.text) 68 | --------------------------------------------------------------------------------