openpdu-libs/usr/python2.7/site-packages/oled/device.py

411 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2016 Richard Hull and contributors
# See LICENSE.rst for details.
# Example usage:
#
# from oled.serial import i2c, spi
# from oled.device import ssd1306, sh1106
# from oled.render import canvas
# from PIL import ImageDraw
#
# serial = i2c(port=1, address=0x3C)
# device = ssd1306(serial)
#
# with canvas(device) as draw:
# draw.rectangle(device.bounding_box, outline="white", fill="black")
# draw.text(30, 40, "Hello World", fill="white")
#
# As soon as the with-block scope level is complete, the graphics primitives
# will be flushed to the device.
#
# Creating a new canvas is effectively 'carte blanche': If you want to retain
# an existing canvas, then make a reference like:
#
# c = canvas(device)
# for X in ...:
# with c as draw:
# draw.rectangle(...)
#
# As before, as soon as the with block completes, the canvas buffer is flushed
# to the device
import atexit
from oled.serial import i2c
import oled.mixin as mixin
import oled.error
import oled.const
class device(mixin.capabilities):
"""
Base class for OLED driver classes
.. warning::
Direct use of the :func:`command` and :func:`data` methods are
discouraged: Screen updates should be effected through the
:func:`display` method, or preferably with the
:class:`oled.render.canvas` context manager.
"""
def __init__(self, const=None, serial_interface=None):
self._const = const or oled.const.common
self._serial_interface = serial_interface or i2c()
atexit.register(self.cleanup)
def command(self, *cmd):
"""
Sends a command or sequence of commands through to the delegated
serial interface.
"""
self._serial_interface.command(*cmd)
def data(self, data):
"""
Sends a data byte or sequence of data bytes through to the delegated
serial interface.
"""
self._serial_interface.data(data)
def show(self):
"""
Sets the display mode ON, waking the device out of a prior
low-power sleep mode.
"""
self.command(self._const.DISPLAYON)
def hide(self):
"""
Switches the display mode OFF, putting the device in low-power
sleep mode.
"""
self.command(self._const.DISPLAYOFF)
def contrast(self, level):
"""
Switches the display contrast to the desired level, in the range
0-255. Note that setting the level to a low (or zero) value will
not necessarily dim the display to nearly off. In other words,
this method is **NOT** suitable for fade-in/out animation.
:param level: Desired contrast level in the range of 0-255.
:type level: int
"""
assert(level >= 0)
assert(level <= 255)
self.command(self._const.SETCONTRAST, level)
def cleanup(self):
self.hide()
self.clear()
self._serial_interface.cleanup()
class sh1106(device):
"""
Encapsulates the serial interface to the monochrome SH1106 OLED display
hardware. On creation, an initialization sequence is pumped to the display
to properly configure it. Further control commands can then be called to
affect the brightness and other settings.
"""
def __init__(self, serial_interface=None, width=128, height=64, rotate=0):
super(sh1106, self).__init__(oled.const.sh1106, serial_interface)
self.capabilities(width, height, rotate)
self._pages = self._h // 8
# FIXME: Delay doing anything here with alternate screen sizes
# until we are able to get a device to test with.
if width != 128 or height != 64:
raise oled.error.DeviceDisplayModeError(
"Unsupported display mode: {0} x {1}".format(width, height))
self.command(
self._const.DISPLAYOFF,
self._const.MEMORYMODE,
self._const.SETHIGHCOLUMN, 0xB0, 0xC8,
self._const.SETLOWCOLUMN, 0x10, 0x40,
self._const.SETSEGMENTREMAP,
self._const.NORMALDISPLAY,
self._const.SETMULTIPLEX, 0x3F,
self._const.DISPLAYALLON_RESUME,
self._const.SETDISPLAYOFFSET, 0x00,
self._const.SETDISPLAYCLOCKDIV, 0xF0,
self._const.SETPRECHARGE, 0x22,
self._const.SETCOMPINS, 0x12,
self._const.SETVCOMDETECT, 0x20,
self._const.CHARGEPUMP, 0x14)
self.contrast(0x7F)
self.clear()
self.show()
def display(self, image):
"""
Takes a 1-bit :py:mod:`PIL.Image` and dumps it to the SH1106
OLED display.
"""
assert(image.mode == self.mode)
assert(image.size == self.size)
image = self.preprocess(image)
set_page_address = 0xB0
image_data = image.getdata()
pixels_per_page = self.width * 8
buf = bytearray(self.width)
for y in range(0, int(self._pages * pixels_per_page), pixels_per_page):
self.command(set_page_address, 0x02, 0x10)
set_page_address += 1
offsets = [y + self.width * i for i in range(8)]
for x in range(self.width):
buf[x] = \
(image_data[x + offsets[0]] and 0x01) | \
(image_data[x + offsets[1]] and 0x02) | \
(image_data[x + offsets[2]] and 0x04) | \
(image_data[x + offsets[3]] and 0x08) | \
(image_data[x + offsets[4]] and 0x10) | \
(image_data[x + offsets[5]] and 0x20) | \
(image_data[x + offsets[6]] and 0x40) | \
(image_data[x + offsets[7]] and 0x80)
self.data(list(buf))
class ssd1306(device):
"""
Encapsulates the serial interface to the monochrome SSD1306 OLED display
hardware. On creation, an initialization sequence is pumped to the display
to properly configure it. Further control commands can then be called to
affect the brightness and other settings.
"""
def __init__(self, serial_interface=None, width=128, height=64, rotate=0):
super(ssd1306, self).__init__(oled.const.ssd1306, serial_interface)
self.capabilities(width, height, rotate)
self._pages = self._h // 8
self._buffer = [0] * self._w * self._pages
self._offsets = [n * self._w for n in range(8)]
# Supported modes
settings = {
(128, 64): dict(multiplex=0x3F, displayclockdiv=0x80, compins=0x12),
(128, 32): dict(multiplex=0x1F, displayclockdiv=0x80, compins=0x02),
(96, 16): dict(multiplex=0x0F, displayclockdiv=0x60, compins=0x02)
}.get((width, height))
if settings is None:
raise oled.error.DeviceDisplayModeError(
"Unsupported display mode: {0} x {1}".format(width, height))
self.command(
self._const.DISPLAYOFF,
self._const.SETDISPLAYCLOCKDIV, settings['displayclockdiv'],
self._const.SETMULTIPLEX, settings['multiplex'],
self._const.SETDISPLAYOFFSET, 0x00,
self._const.SETSTARTLINE,
self._const.CHARGEPUMP, 0x14,
self._const.MEMORYMODE, 0x00,
self._const.SETREMAP,
self._const.COMSCANDEC,
self._const.SETCOMPINS, settings['compins'],
self._const.SETPRECHARGE, 0xF1,
self._const.SETVCOMDETECT, 0x40,
self._const.DISPLAYALLON_RESUME,
self._const.NORMALDISPLAY)
self.contrast(0xCF)
self.clear()
self.show()
def display(self, image):
"""
Takes a 1-bit :py:mod:`PIL.Image` and dumps it to the SSD1306
OLED display.
"""
assert(image.mode == self.mode)
assert(image.size == self.size)
image = self.preprocess(image)
self.command(
# Column start/end address
self._const.COLUMNADDR, 0x00, self._w - 1,
# Page start/end address
self._const.PAGEADDR, 0x00, self._pages - 1)
w = self._w
pix = list(image.getdata())
step = w * 8
buf = self._buffer
os0, os1, os2, os3, os4, os5, os6, os7 = self._offsets
j = 0
for y in range(0, self._pages * step, step):
i = y + w - 1
while i >= y:
buf[j] = \
(0x01 if pix[i] > 0 else 0) | \
(0x02 if pix[i + os1] > 0 else 0) | \
(0x04 if pix[i + os2] > 0 else 0) | \
(0x08 if pix[i + os3] > 0 else 0) | \
(0x10 if pix[i + os4] > 0 else 0) | \
(0x20 if pix[i + os5] > 0 else 0) | \
(0x40 if pix[i + os6] > 0 else 0) | \
(0x80 if pix[i + os7] > 0 else 0)
i -= 1
j += 1
self.data(buf)
class ssd1331(device):
"""
Encapsulates the serial interface to the 16-bit color (5-6-5 RGB) SSD1331
OLED display hardware. On creation, an initialization sequence is pumped to
the display to properly configure it. Further control commands can then be
called to affect the brightness and other settings.
"""
def __init__(self, serial_interface=None, width=96, height=64, rotate=0):
super(ssd1331, self).__init__(oled.const.ssd1331, serial_interface)
self.capabilities(width, height, rotate, mode="RGB")
self._buffer = [0] * self._w * self._h * 2
if width != 96 or height != 64:
raise oled.error.DeviceDisplayModeError(
"Unsupported display mode: {0} x {1}".format(width, height))
self.command(
self._const.DISPLAYOFF,
self._const.SETREMAP, 0x72,
self._const.SETDISPLAYSTARTLINE, 0x00,
self._const.SETDISPLAYOFFSET, 0x00,
self._const.NORMALDISPLAY,
self._const.SETMULTIPLEX, 0x3F,
self._const.SETMASTERCONFIGURE, 0x8E,
self._const.POWERSAVEMODE, 0x0B,
self._const.PHASE12PERIOD, 0x74,
self._const.CLOCKDIVIDER, 0xD0,
self._const.SETPRECHARGESPEEDA, 0x80,
self._const.SETPRECHARGESPEEDB, 0x80,
self._const.SETPRECHARGESPEEDC, 0x80,
self._const.SETPRECHARGEVOLTAGE, 0x3E,
self._const.SETVVOLTAGE, 0x3E,
self._const.MASTERCURRENTCONTROL, 0x0F)
self.contrast(0xFF)
self.clear()
self.show()
def display(self, image):
"""
Takes a 24-bit RGB :py:mod:`PIL.Image` and dumps it to the SSD1331 OLED
display.
"""
assert(image.mode == self.mode)
assert(image.size == self.size)
image = self.preprocess(image)
self.command(
self._const.SETCOLUMNADDR, 0x00, self._w - 1,
self._const.SETROWADDR, 0x00, self._h - 1)
i = 0
buf = self._buffer
for r, g, b in image.getdata():
# 65K format 1
buf[i] = r & 0xF8 | g >> 5
buf[i + 1] = g << 5 & 0xE0 | b >> 3
i += 2
self.data(buf)
def contrast(self, level):
"""
Switches the display contrast to the desired level, in the range
0-255. Note that setting the level to a low (or zero) value will
not necessarily dim the display to nearly off. In other words,
this method is **NOT** suitable for fade-in/out animation.
:param level: Desired contrast level in the range of 0-255.
:type level: int
"""
assert(level >= 0)
assert(level <= 255)
self.command(self._const.SETCONTRASTA, level,
self._const.SETCONTRASTB, level,
self._const.SETCONTRASTC, level)
class ssd1325(device):
"""
Encapsulates the serial interface to the 4-bit greyscale SSD1325 OLED
display hardware. On creation, an initialization sequence is pumped to the
display to properly configure it. Further control commands can then be
called to affect the brightness and other settings.
"""
def __init__(self, serial_interface=None, width=128, height=64, rotate=0):
super(ssd1325, self).__init__(oled.const.ssd1325, serial_interface)
self.capabilities(width, height, rotate, mode="RGB")
self._buffer = [0] * (self._w * self._h // 2)
if width != 128 or height != 64:
raise oled.error.DeviceDisplayModeError(
"Unsupported display mode: {0} x {1}".format(width, height))
self.command(
self._const.DISPLAYOFF,
self._const.SETCLOCK, 0xF1,
self._const.SETMULTIPLEX, 0x3F,
self._const.SETOFFSET, 0x4C,
self._const.SETSTARTLINE, 0x00,
self._const.MASTERCONFIG, 0x02,
self._const.SETREMAP, 0x50,
self._const.SETCURRENT + 2,
self._const.SETGRAYTABLE, 0x01, 0x11, 0x22, 0x32, 0x43, 0x54, 0x65, 0x76)
self.contrast(0xFF)
self.command(
self._const.SETROWPERIOD, 0x51,
self._const.SETPHASELEN, 0x55,
self._const.SETPRECHARGECOMP, 0x02,
self._const.SETPRECHARGECOMPENABLE, 0x28,
self._const.SETVCOMLEVEL, 0x1C,
self._const.SETVSL, 0x0F,
self._const.NORMALDISPLAY)
self.clear()
self.show()
def display(self, image):
"""
Takes a 24-bit RGB :py:mod:`PIL.Image` and dumps it to the SSD1325 OLED
display, converting the image pixels to 4-bit greyscale using a
simplified Luma calculation, based on *Y'=0.299R'+0.587G'+0.114B'*.
"""
assert(image.mode == self.mode)
assert(image.size == self.size)
image = self.preprocess(image)
self.command(
self._const.SETCOLUMNADDR, 0x00, self._w - 1,
self._const.SETROWADDR, 0x00, self._h - 1)
i = 0
buf = self._buffer
for r, g, b in image.getdata():
# RGB->Greyscale luma calculation into 4-bits
grey = (r * 306 + g * 601 + b * 117) >> 14
if i % 2 == 0:
buf[i // 2] = grey
else:
buf[i // 2] |= (grey << 4)
i += 1
self.data(buf)