Basis-Fassung
This commit is contained in:
commit
f89f9322d8
|
@ -0,0 +1,341 @@
|
||||||
|
import smbus
|
||||||
|
import time
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
try:
|
||||||
|
from gpiozero import PWMLED
|
||||||
|
GPIOZERO = True
|
||||||
|
except ImportError as e:
|
||||||
|
print('Can not import gpiozero module.')
|
||||||
|
GPIOZERO = False
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
general main function
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(description='UPS HAT Monitoring Daemon')
|
||||||
|
parser.add_argument('-d', '--debug',
|
||||||
|
action='store_true',
|
||||||
|
help='log all messages to console')
|
||||||
|
parser.add_argument('-s', '--statistics',
|
||||||
|
action='store_true',
|
||||||
|
help='show more statistics')
|
||||||
|
parser.add_argument('-i', '--interval',
|
||||||
|
default=5,
|
||||||
|
help='check interval')
|
||||||
|
parser.add_argument('-m', '--minimum',
|
||||||
|
help='the minimum battery level in percent for the poweroff hook',
|
||||||
|
default=20)
|
||||||
|
parser.add_argument('-p', '--poweroff',
|
||||||
|
help='command for the poweroff hook')
|
||||||
|
parser.add_argument('-l', '--led',
|
||||||
|
help='GPIO Pin Number for use with a LED')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.debug:
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
##
|
||||||
|
# set log level
|
||||||
|
if args.debug or args.statistics:
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
# run the loop
|
||||||
|
mainloop(args)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def mainloop(args):
|
||||||
|
# Create an INA219 instance.
|
||||||
|
ina219 = INA219(addr=0x42)
|
||||||
|
myled = PWMLED(args.led)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# voltage on V- (load side)
|
||||||
|
bus_voltage = ina219.getBusVoltage_V()
|
||||||
|
# voltage between V+ and V- across the shunt
|
||||||
|
# shunt_voltage = ina219.getShuntVoltage_mV() / 1000
|
||||||
|
# current in mA
|
||||||
|
current = ina219.getCurrent_mA()
|
||||||
|
# power in W
|
||||||
|
power = ina219.getPower_W()
|
||||||
|
p = (bus_voltage - 6)/2.4*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))
|
||||||
|
|
||||||
|
if args.statistics:
|
||||||
|
log_stats(bus_voltage,
|
||||||
|
current,
|
||||||
|
power,
|
||||||
|
p)
|
||||||
|
|
||||||
|
# check the state and trigger hooks
|
||||||
|
check_state(current,
|
||||||
|
p,
|
||||||
|
args.minimum,
|
||||||
|
args.poweroff,
|
||||||
|
myled)
|
||||||
|
# switch_led(myled)
|
||||||
|
time.sleep(int(args.interval))
|
||||||
|
|
||||||
|
|
||||||
|
def log_stats(bus_voltage, current, power, percent):
|
||||||
|
"""
|
||||||
|
print or log statistics
|
||||||
|
"""
|
||||||
|
logging.debug("Load Voltage: {:6.3f} V".format(bus_voltage))
|
||||||
|
logging.debug("Current: {:9.6f} A".format(current/1000))
|
||||||
|
logging.debug("Power: {:6.3f} W".format(power))
|
||||||
|
logging.debug("Percent: {:3.1f}%".format(percent))
|
||||||
|
|
||||||
|
|
||||||
|
def check_state(current, percent, minimum, poweroff_cmd, myled):
|
||||||
|
"""
|
||||||
|
if current is negative and percent lower than args.minimum
|
||||||
|
run the poweroff hook command if any is set
|
||||||
|
:param current: in ampere
|
||||||
|
:param percent: battery level
|
||||||
|
"""
|
||||||
|
# run only current is negative
|
||||||
|
if current < 0:
|
||||||
|
switch_led(myled, 'unload')
|
||||||
|
if percent < float(minimum):
|
||||||
|
logging.warning(" Minimum battery level ({:3.1f}%) is reached"
|
||||||
|
.format(percent))
|
||||||
|
logging.warning(" try to run poweroff hook cmd {}"
|
||||||
|
.format(poweroff_cmd))
|
||||||
|
run_hook(poweroff_cmd)
|
||||||
|
else:
|
||||||
|
logging.info("battery level ({:3.1f}%) is okay: do nothing!".
|
||||||
|
format(percent))
|
||||||
|
else:
|
||||||
|
switch_led(myled)
|
||||||
|
|
||||||
|
|
||||||
|
def switch_led(myled, mode='load'):
|
||||||
|
if GPIOZERO:
|
||||||
|
if mode == 'load':
|
||||||
|
for i in range(10, 110, 10):
|
||||||
|
# logging.warning(i)
|
||||||
|
myled.value = float(i/100)
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
myled.off()
|
||||||
|
|
||||||
|
|
||||||
|
def run_hook(cmd):
|
||||||
|
"""
|
||||||
|
run the configed hook cmd if exists
|
||||||
|
:param cmd: path to cmd
|
||||||
|
"""
|
||||||
|
args = shlex.split(cmd)
|
||||||
|
try:
|
||||||
|
subprocess.run(args)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print('Error: {}'.format(e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue