mirror of
https://github.com/sbcshop/UPS-Hat-RPi.git
synced 2024-12-22 21:08:15 +03:00
Add files via upload
This commit is contained in:
187
INA219_UPS.py
Normal file
187
INA219_UPS.py
Normal file
@@ -0,0 +1,187 @@
|
||||
'''
|
||||
The INA219 is a current shunt and power monitor with an I2C- or SMBUS-compatible interface.
|
||||
The device monitors both shunt voltage drop and bus supply voltage, with programmable conversion times and filtering.
|
||||
The INA219 operates from –40°C to 125°C.
|
||||
'''
|
||||
from serial import SerialException
|
||||
from oled_091 import SSD1306
|
||||
from time import sleep
|
||||
from os import path
|
||||
import smbus
|
||||
import time
|
||||
|
||||
|
||||
DIR_PATH = path.abspath(path.dirname(__file__))
|
||||
DefaultFont = path.join(DIR_PATH, "Fonts/GothamLight.ttf")
|
||||
|
||||
def info_print():
|
||||
oled_display.DirImage(path.join(DIR_PATH, "Images/SB.png"))
|
||||
oled_display.DrawRect()
|
||||
oled_display.ShowImage()
|
||||
sleep(2)
|
||||
|
||||
oled_display = SSD1306()
|
||||
info_print()
|
||||
# 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 ADC_Resolution:
|
||||
"""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
|
||||
"""
|
||||
|
||||
self._current_lsb = .1 # Current LSB = 100uA per bit
|
||||
|
||||
self._cal_value = 4096
|
||||
|
||||
self._power_lsb = .002 # Power LSB = 2mW per bit
|
||||
|
||||
|
||||
|
||||
# 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 = ADC_Resolution.ADCRES_12BIT_32S
|
||||
self.shunt_adc_resolution = ADC_Resolution.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 Shunt_Voltage_mV(self):
|
||||
self.write(_REG_CALIBRATION,self._cal_value)
|
||||
value = self.read(_REG_SHUNTVOLTAGE)
|
||||
if value > 32767:
|
||||
value -= 65535
|
||||
return value * 0.01
|
||||
|
||||
def Bus_Voltage_V(self):
|
||||
self.write(_REG_CALIBRATION,self._cal_value)
|
||||
self.read(_REG_BUSVOLTAGE)
|
||||
return (self.read(_REG_BUSVOLTAGE) >> 3) * 0.004
|
||||
|
||||
def Current_mA(self):
|
||||
value = self.read(_REG_CURRENT)
|
||||
if value > 32767:
|
||||
value -= 65535
|
||||
return value * self._current_lsb
|
||||
|
||||
def Power_Watt(self):
|
||||
self.write(_REG_CALIBRATION,self._cal_value)
|
||||
value = self.read(_REG_POWER)
|
||||
if value > 32767:
|
||||
value -= 65535
|
||||
return value * self._power_lsb
|
||||
|
||||
|
||||
|
||||
# Create an INA219 instance.
|
||||
ina219 = INA219(addr=0x42)
|
||||
|
||||
while True:
|
||||
bus_voltage = ina219.Bus_Voltage_V() # voltage on V- (load side)
|
||||
shunt_voltage = ina219.Shunt_Voltage_mV() / 1000 # voltage between V+ and V- across the shunt
|
||||
current = ina219.Current_mA() # current in mA
|
||||
power = ina219.Power_Watt() # power in 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))
|
||||
print("Load Voltage: {:6.3f} V".format(bus_voltage))
|
||||
print("Current: {:9.6f} A".format(current/1000))
|
||||
print("Power: {:6.3f} W".format(power))
|
||||
print("Percent: {:3.1f}%".format(p))
|
||||
print("")
|
||||
|
||||
# print readings in oled display
|
||||
oled_display.PrintText("Load Voltage= {:6.3f} V".format(bus_voltage),cords=(0, 0), FontSize=10)
|
||||
oled_display.PrintText("Current = {:9.6f} A".format(current/1000),cords=(0,8), FontSize=10)
|
||||
oled_display.PrintText("Power = {:6.3f} W".format(power),cords=(0,16), FontSize=10)
|
||||
oled_display.PrintText("Percent = {:3.1f}%".format(p),cords=(0,24), FontSize=10)
|
||||
oled_display.ShowImage()
|
||||
|
||||
time.sleep(2)
|
||||
278
oled_091.py
Normal file
278
oled_091.py
Normal file
@@ -0,0 +1,278 @@
|
||||
from os import path
|
||||
from sys import exit, version_info
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
try:
|
||||
from smbus import SMBus
|
||||
except ImportError:
|
||||
if version_info[0] < 3:
|
||||
exit("This library requires python-smbus\nInstall with: sudo "
|
||||
"apt-get install python-smbus")
|
||||
elif version_info[0] == 3:
|
||||
exit("This library requires python3-smbus\nInstall with: sudo "
|
||||
"apt-get install python3-smbus")
|
||||
|
||||
DIR_PATH = path.abspath(path.dirname(__file__))
|
||||
DefaultFont = (path.join(DIR_PATH, "Fonts/GothamLight.ttf"))
|
||||
|
||||
# Fundamental Command Table
|
||||
SET_CONTRAST = 0x81
|
||||
DISPLAY_ON = 0xA4
|
||||
DISPLAY_INVERT = 0xA6
|
||||
DISPLAY_ON = 0xAE # Off: sleep mode
|
||||
DISPLAY_OFF = 0xAF # On: Normal Mode
|
||||
|
||||
# Address Settting Command Table
|
||||
MEM_ADD_MODE = 0x20
|
||||
COLUMN_ADD = 0x21
|
||||
PAGE_ADD = 0x22
|
||||
|
||||
# Hardware Configuration
|
||||
DISPLAY_START_LINE = 0x40
|
||||
SEGMENT_REMAP = 0xA0
|
||||
MUX_RATIO = 0xA8
|
||||
COM_OUT_SCAN = 0xC0
|
||||
COM_SCAN_REMAP = 0xC8
|
||||
DISPLAY_OFFSET = 0xD3
|
||||
SET_COM_PIN = 0xDA
|
||||
|
||||
# Timing and Driving
|
||||
SET_CLK_DIV = 0xD5
|
||||
SET_PRE_CHARGE = 0xD9
|
||||
SET_DESELECT = 0xDB
|
||||
CHARGE_PUMP = 0x8D
|
||||
|
||||
|
||||
class i2c_interface:
|
||||
def __init__(self, address=0x3c):
|
||||
"""
|
||||
:param address: i2c address of ssd1306
|
||||
"""
|
||||
self.bus = SMBus(self.bus_id())
|
||||
self.address = address
|
||||
|
||||
def __del__(self):
|
||||
self.close_i2c()
|
||||
|
||||
def close_i2c(self):
|
||||
self.bus.close()
|
||||
|
||||
def bus_id(self):
|
||||
"""
|
||||
:return: Returns SMBUS id of Raspberry Pi
|
||||
"""
|
||||
revision = [lines[12:-1] for lines in open('/proc/cpuinfo',
|
||||
'r').readlines() if
|
||||
"Revision" in lines[:8]]
|
||||
revision = (revision + ['0000'])[0]
|
||||
return 1 if int(revision, 16) >= 4 else 0
|
||||
|
||||
def i2c_read(self, register=0):
|
||||
data = self.bus.read_byte_data(self.address, register)
|
||||
return data
|
||||
|
||||
def i2c_write(self, register=DISPLAY_START_LINE, data=0):
|
||||
# Write a byte to address, register
|
||||
self.bus.write_byte_data(self.address, register, data)
|
||||
|
||||
def i2c_write_block(self, register=DISPLAY_START_LINE, data=None):
|
||||
if data is None:
|
||||
data = [40]
|
||||
self.bus.write_i2c_block_data(self.address, register, data)
|
||||
|
||||
|
||||
class SSD1306(i2c_interface):
|
||||
def __init__(self, width=128, height=32, address=0x3c):
|
||||
i2c_interface.__init__(self, address=address)
|
||||
self.Height = height
|
||||
self.Width = width
|
||||
self.Page = height // 8
|
||||
self.address = address
|
||||
self._Image = None
|
||||
self._Image_New = None
|
||||
self.Draw = None
|
||||
self.Image_Buf = None
|
||||
|
||||
self.NewImage()
|
||||
self.InitDisplay()
|
||||
|
||||
def NewImage(self):
|
||||
self._Image = Image.new('1', (self.Width, self.Height), "WHITE")
|
||||
self.Draw = ImageDraw.Draw(self._Image)
|
||||
|
||||
def DirImage(self, filename, size=None, cords=(0, 0)):
|
||||
"""
|
||||
:param cords: Coordinates of image on display
|
||||
:param pos: X, Y positions of paste location
|
||||
:param filename: Image file path
|
||||
:param size: The requested size in pixels, as a 2-tuple: (width,
|
||||
height)
|
||||
:return: None
|
||||
"""
|
||||
self._Image_New = Image.open(filename).convert("1")
|
||||
if not size:
|
||||
size = (self.Width, self.Height)
|
||||
self._Image_New = self._Image_New.resize(size)
|
||||
|
||||
self._Image.paste(self._Image_New, box=cords)
|
||||
self.Draw = ImageDraw.Draw(self._Image)
|
||||
|
||||
def WriteCommand(self, cmd): # write command
|
||||
self.i2c_write(register=0x00, data=cmd)
|
||||
|
||||
def WriteData(self, data): # write ram
|
||||
self.i2c_write(register=DISPLAY_START_LINE, data=data)
|
||||
|
||||
def InitDisplay(self):
|
||||
self.WriteCommand(DISPLAY_ON)
|
||||
|
||||
self.WriteCommand(DISPLAY_START_LINE)
|
||||
|
||||
self.WriteCommand(0xB0) # Page Address
|
||||
|
||||
self.WriteCommand(COM_SCAN_REMAP) # Com Output Scan
|
||||
|
||||
# Contrast Setting
|
||||
self.WriteCommand(SET_CONTRAST)
|
||||
self.WriteCommand(0xFF)
|
||||
self.WriteCommand(0xA1)
|
||||
|
||||
self.WriteCommand(DISPLAY_INVERT)
|
||||
|
||||
self.WriteCommand(MUX_RATIO)
|
||||
self.WriteCommand(0x1F) # Column Start Address
|
||||
|
||||
self.WriteCommand(DISPLAY_OFFSET)
|
||||
self.WriteCommand(0x00)
|
||||
|
||||
self.WriteCommand(SET_CLK_DIV)
|
||||
self.WriteCommand(0xF0)
|
||||
|
||||
self.WriteCommand(SET_PRE_CHARGE)
|
||||
self.WriteCommand(PAGE_ADD)
|
||||
|
||||
self.WriteCommand(SET_COM_PIN)
|
||||
self.WriteCommand(0x02)
|
||||
|
||||
self.WriteCommand(SET_DESELECT)
|
||||
self.WriteCommand(0x49)
|
||||
|
||||
self.WriteCommand(CHARGE_PUMP)
|
||||
self.WriteCommand(0x14)
|
||||
|
||||
self.WriteCommand(DISPLAY_OFF)
|
||||
|
||||
def NoDisplay(self):
|
||||
for i in range(0, self.Page):
|
||||
self.WriteCommand(0xb0 + i)
|
||||
self.WriteCommand(0x00)
|
||||
self.WriteCommand(0x10)
|
||||
for j in range(0, self.Width):
|
||||
self.WriteData(0x00)
|
||||
|
||||
def WhiteDisplay(self):
|
||||
for i in range(0, self.Page):
|
||||
self.WriteCommand(0xb0 + i)
|
||||
self.WriteCommand(0x00)
|
||||
self.WriteCommand(0x10)
|
||||
for j in range(0, self.Width):
|
||||
self.WriteData(0xff)
|
||||
|
||||
def ImgBuffer(self, image):
|
||||
buf = [0xff] * (self.Page * self.Width)
|
||||
Img_Mono = image.convert('1')
|
||||
Img_Width, Img_Height = Img_Mono.size
|
||||
pixels = Img_Mono.load()
|
||||
if Img_Width == self.Width and Img_Height == self.Height:
|
||||
# Horizontal screen
|
||||
for y in range(Img_Height):
|
||||
for x in range(Img_Width):
|
||||
# Set the bits for the column of pixels at the current
|
||||
# position.
|
||||
if pixels[x, y] == 0:
|
||||
buf[x + (y // 8) * self.Width] &= ~(1 << (y % 8))
|
||||
elif Img_Width == self.Width and Img_Height == self.Height:
|
||||
# Vertical screen
|
||||
for y in range(Img_Height):
|
||||
for x in range(Img_Width):
|
||||
x_pos = y
|
||||
y_pos = self.Height - x - 1
|
||||
if pixels[x, y] == 0:
|
||||
buf[(x_pos + int(y_pos / 8) * self.Width)] &= ~(
|
||||
1 << (y % 8))
|
||||
for i in range(self.Page * self.Width):
|
||||
buf[i] = ~buf[i]
|
||||
return buf
|
||||
|
||||
def ShowImage(self):
|
||||
i_buf = self.ImgBuffer(self._Image)
|
||||
for i in range(0, self.Page):
|
||||
self.WriteCommand(0xB0 + i) # set page address
|
||||
self.WriteCommand(0x00) # set low column address
|
||||
self.WriteCommand(0x10) # set high column address
|
||||
# write data #
|
||||
for j in range(0, 128): # self.Width):
|
||||
self.WriteData(i_buf[j + self.Width * i])
|
||||
self.NewImage()
|
||||
|
||||
def PrintText(self, text, cords=(10, 5), Font=DefaultFont,
|
||||
FontSize=14):
|
||||
"""
|
||||
:param text: Text to print
|
||||
:param cords: Top left Corner (X, Y) cords
|
||||
:param Font: Font Type
|
||||
:param FontSize: Size of Font
|
||||
:return: None
|
||||
"""
|
||||
self.Draw.text(cords, text, font=ImageFont.truetype(Font, FontSize))
|
||||
|
||||
def DrawRect(self, cords=(0, 0, 127, 31)):
|
||||
"""
|
||||
:param cords: X0, X1, Y0, Y1
|
||||
:return: None
|
||||
"""
|
||||
self.Draw.rectangle(cords, outline=0)
|
||||
|
||||
def DrawPolygon(self, cords=(1, 2, 3, 4, 5, 6)):
|
||||
"""
|
||||
:param cords: Sequence of either 2-tuples like [(x, y), (x, y),
|
||||
...] or numeric values like [x, y, x, y, ...]
|
||||
:return: None
|
||||
"""
|
||||
self.Draw.polygon(cords)
|
||||
|
||||
def DrawPoint(self, cords=(64, 16, 66, 18)):
|
||||
"""
|
||||
:param cords: tuple of X, Y coordinates of Points
|
||||
:return: None
|
||||
"""
|
||||
self.Draw.point(cords)
|
||||
|
||||
def DrawLine(self, cords=(64, 16, 78, 18)):
|
||||
"""
|
||||
Draws a line between the coordinates in the xy list
|
||||
:param cords: tuple of X, Y coordinates for line
|
||||
:return: None
|
||||
"""
|
||||
self.Draw.line(cords)
|
||||
|
||||
def DrawEllipse(self, cords=(64, 16, 78, 18)):
|
||||
"""
|
||||
Draws an ellipse inside the given bounding box
|
||||
:param cords: Four points to define the bounding box
|
||||
:return: None
|
||||
"""
|
||||
self.Draw.ellipse(cords)
|
||||
|
||||
def DrawArc(self, cords=(10, 10, 120, 10), start=0, end=90):
|
||||
"""
|
||||
Draws an arc (a portion of a circle outline) between the start and
|
||||
end angles, inside the given bounding box
|
||||
:param end: Starting angle, in degrees. Angles are measured from 3
|
||||
o’clock, increasing clockwise.
|
||||
:param start: Ending angle, in degrees.
|
||||
:param cords: Four points to define the bounding box
|
||||
:return: None
|
||||
"""
|
||||
self.Draw.arc(cords, start=start, end=end)
|
||||
Reference in New Issue
Block a user