Created
November 14, 2021 22:29
-
-
Save JosiahKerley/23e8f267df557c5aa0638811543255f8 to your computer and use it in GitHub Desktop.
can the board detect when the UPS is on battery power?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
import smbus | |
import time | |
import os | |
import sys | |
import logging | |
logging.basicConfig(filename='/var/log/power-monitor.log', | |
filemode='a', | |
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', | |
datefmt='%H:%M:%S', | |
level=logging.DEBUG) | |
def info(msg): | |
print(msg) | |
logging.info(msg) | |
info("Starting power monitor") | |
# Config Register (R/W) | |
_REG_CONFIG = 0x00 | |
# SHUNT VOLTAGE REGISTER (R) | |
_REG_SHUNTVOLTAGE = 0x01 | |
# BUS VOLTAGE REGISTER (R) | |
_REG_BUSVOLTAGE = 0x02 | |
# POWER REGISTER (R) | |
_REG_POWER = 0x03 | |
# CURRENT REGISTER (R) | |
_REG_CURRENT = 0x04 | |
# CALIBRATION REGISTER (R/W) | |
_REG_CALIBRATION = 0x05 | |
class BusVoltageRange: | |
"""Constants for ``bus_voltage_range``""" | |
RANGE_16V = 0x00 # set bus voltage range to 16V | |
RANGE_32V = 0x01 # set bus voltage range to 32V (default) | |
class Gain: | |
"""Constants for ``gain``""" | |
DIV_1_40MV = 0x00 # shunt prog. gain set to 1, 40 mV range | |
DIV_2_80MV = 0x01 # shunt prog. gain set to /2, 80 mV range | |
DIV_4_160MV = 0x02 # shunt prog. gain set to /4, 160 mV range | |
DIV_8_320MV = 0x03 # shunt prog. gain set to /8, 320 mV range | |
class ADCResolution: | |
"""Constants for ``bus_adc_resolution`` or ``shunt_adc_resolution``""" | |
ADCRES_9BIT_1S = 0x00 # 9bit, 1 sample, 84us | |
ADCRES_10BIT_1S = 0x01 # 10bit, 1 sample, 148us | |
ADCRES_11BIT_1S = 0x02 # 11 bit, 1 sample, 276us | |
ADCRES_12BIT_1S = 0x03 # 12 bit, 1 sample, 532us | |
ADCRES_12BIT_2S = 0x09 # 12 bit, 2 samples, 1.06ms | |
ADCRES_12BIT_4S = 0x0A # 12 bit, 4 samples, 2.13ms | |
ADCRES_12BIT_8S = 0x0B # 12bit, 8 samples, 4.26ms | |
ADCRES_12BIT_16S = 0x0C # 12bit, 16 samples, 8.51ms | |
ADCRES_12BIT_32S = 0x0D # 12bit, 32 samples, 17.02ms | |
ADCRES_12BIT_64S = 0x0E # 12bit, 64 samples, 34.05ms | |
ADCRES_12BIT_128S = 0x0F # 12bit, 128 samples, 68.10ms | |
class Mode: | |
"""Constants for ``mode``""" | |
POWERDOW = 0x00 # power down | |
SVOLT_TRIGGERED = 0x01 # shunt voltage triggered | |
BVOLT_TRIGGERED = 0x02 # bus voltage triggered | |
SANDBVOLT_TRIGGERED = 0x03 # shunt and bus voltage triggered | |
ADCOFF = 0x04 # ADC off | |
SVOLT_CONTINUOUS = 0x05 # shunt voltage continuous | |
BVOLT_CONTINUOUS = 0x06 # bus voltage continuous | |
SANDBVOLT_CONTINUOUS = 0x07 # shunt and bus voltage continuous | |
class INA219: | |
def __init__(self, i2c_bus=1, addr=0x40): | |
self.bus = smbus.SMBus(i2c_bus); | |
self.addr = addr | |
# Set chip to known config values to start | |
self._cal_value = 0 | |
self._current_lsb = 0 | |
self._power_lsb = 0 | |
self.set_calibration_32V_2A() | |
def read(self,address): | |
data = self.bus.read_i2c_block_data(self.addr, address, 2) | |
return ((data[0] * 256 ) + data[1]) | |
def write(self,address,data): | |
temp = [0,0] | |
temp[1] = data & 0xFF | |
temp[0] =(data & 0xFF00) >> 8 | |
self.bus.write_i2c_block_data(self.addr,address,temp) | |
def set_calibration_32V_2A(self): | |
"""Configures to INA219 to be able to measure up to 32V and 2A of current. Counter | |
overflow occurs at 3.2A. | |
..note :: These calculations assume a 0.1 shunt ohm resistor is present | |
""" | |
# By default we use a pretty huge range for the input voltage, | |
# which probably isn't the most appropriate choice for system | |
# that don't use a lot of power. But all of the calculations | |
# are shown below if you want to change the settings. You will | |
# also need to change any relevant register settings, such as | |
# setting the VBUS_MAX to 16V instead of 32V, etc. | |
# VBUS_MAX = 32V (Assumes 32V, can also be set to 16V) | |
# VSHUNT_MAX = 0.32 (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04) | |
# RSHUNT = 0.1 (Resistor value in ohms) | |
# 1. Determine max possible current | |
# MaxPossible_I = VSHUNT_MAX / RSHUNT | |
# MaxPossible_I = 3.2A | |
# 2. Determine max expected current | |
# MaxExpected_I = 2.0A | |
# 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) | |
# MinimumLSB = MaxExpected_I/32767 | |
# MinimumLSB = 0.000061 (61uA per bit) | |
# MaximumLSB = MaxExpected_I/4096 | |
# MaximumLSB = 0,000488 (488uA per bit) | |
# 4. Choose an LSB between the min and max values | |
# (Preferrably a roundish number close to MinLSB) | |
# CurrentLSB = 0.0001 (100uA per bit) | |
self._current_lsb = 1 # Current LSB = 100uA per bit | |
# 5. Compute the calibration register | |
# Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) | |
# Cal = 4096 (0x1000) | |
self._cal_value = 4096 | |
# 6. Calculate the power LSB | |
# PowerLSB = 20 * CurrentLSB | |
# PowerLSB = 0.002 (2mW per bit) | |
self._power_lsb = .002 # Power LSB = 2mW per bit | |
# 7. Compute the maximum current and shunt voltage values before overflow | |
# | |
# Max_Current = Current_LSB * 32767 | |
# Max_Current = 3.2767A before overflow | |
# | |
# If Max_Current > Max_Possible_I then | |
# Max_Current_Before_Overflow = MaxPossible_I | |
# Else | |
# Max_Current_Before_Overflow = Max_Current | |
# End If | |
# | |
# Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT | |
# Max_ShuntVoltage = 0.32V | |
# | |
# If Max_ShuntVoltage >= VSHUNT_MAX | |
# Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX | |
# Else | |
# Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage | |
# End If | |
# 8. Compute the Maximum Power | |
# MaximumPower = Max_Current_Before_Overflow * VBUS_MAX | |
# MaximumPower = 3.2 * 32V | |
# MaximumPower = 102.4W | |
# Set Calibration register to 'Cal' calculated above | |
self.write(_REG_CALIBRATION,self._cal_value) | |
# Set Config register to take into account the settings above | |
self.bus_voltage_range = BusVoltageRange.RANGE_32V | |
self.gain = Gain.DIV_8_320MV | |
self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_32S | |
self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_32S | |
self.mode = Mode.SANDBVOLT_CONTINUOUS | |
self.config = self.bus_voltage_range << 13 | \ | |
self.gain << 11 | \ | |
self.bus_adc_resolution << 7 | \ | |
self.shunt_adc_resolution << 3 | \ | |
self.mode | |
self.write(_REG_CONFIG,self.config) | |
def getShuntVoltage_mV(self): | |
self.write(_REG_CALIBRATION,self._cal_value) | |
value = self.read(_REG_SHUNTVOLTAGE) | |
if value > 32767: | |
value -= 65535 | |
return value * 0.01 | |
def getBusVoltage_V(self): | |
self.write(_REG_CALIBRATION,self._cal_value) | |
self.read(_REG_BUSVOLTAGE) | |
return (self.read(_REG_BUSVOLTAGE) >> 3) * 0.004 | |
def getCurrent_mA(self): | |
value = self.read(_REG_CURRENT) | |
if value > 32767: | |
value -= 65535 | |
return value * self._current_lsb | |
def getPower_W(self): | |
self.write(_REG_CALIBRATION,self._cal_value) | |
value = self.read(_REG_POWER) | |
if value > 32767: | |
value -= 65535 | |
return value * self._power_lsb | |
if __name__=='__main__': | |
# Create an INA219 instance. | |
ina219 = INA219(addr=0x43) | |
while True: | |
bus_voltage = ina219.getBusVoltage_V() # voltage on V- (load side) | |
shunt_voltage = ina219.getShuntVoltage_mV() / 1000 # voltage between V+ and V- across the shunt | |
current = ina219.getCurrent_mA() # current in mA | |
power = ina219.getPower_W() # power in W | |
p = (bus_voltage - 3)/1.2*100 | |
if(p > 100):p = 100 | |
if(p < 0):p = 0 | |
# INA219 measure bus voltage on the load side. So PSU voltage = bus_voltage + shunt_voltage | |
#print("PSU Voltage: {:6.3f} V".format(bus_voltage + shunt_voltage)) | |
#print("Shunt Voltage: {:9.6f} V".format(shunt_voltage)) | |
info("Load Voltage: {:6.3f} V".format(bus_voltage)) | |
info("Current: {:6.3f} A".format(current/1000)) | |
info("Power: {:6.3f} W".format(power)) | |
info("Percent: {:3.1f}%".format(p)) | |
if current > 0: | |
info("Charging") | |
with open('/run/power-monitor.state', 'w') as f: | |
f.write('1') | |
elif current == 0: | |
info("Charged") | |
with open('/run/power-monitor.state', 'w') as f: | |
f.write('0') | |
else: | |
info("Discharging") | |
with open('/run/power-monitor.state', 'w') as f: | |
f.write('-1') | |
if p < 15 and os.path.isfile('/run/power-monitor.latch'): | |
try: | |
logging.warning("Low power, shutting down") | |
except: | |
pass | |
os.system('shutdown') | |
sys.exit(0) | |
else: | |
if not os.path.isfile('/run/power-monitor.latch'): | |
with open('/run/power-monitor.latch', 'w') as f: | |
f.write('1') | |
print("") | |
time.sleep(10) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment