mirror of
https://github.com/sbcshop/UPS-Hat-RPi.git
synced 2024-12-22 21:08:15 +03:00
279 lines
8.8 KiB
Python
279 lines
8.8 KiB
Python
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)
|