Add files via upload

This commit is contained in:
sbshop1
2022-02-15 11:23:44 +05:30
committed by GitHub
parent 64cf680686
commit 1d345668d8
2 changed files with 465 additions and 0 deletions

187
INA219_UPS.py Normal file
View 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
View 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
oclock, 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)