import enum
import os
from typing import Optional, Union, Tuple, List
from PIL import Image, ImageFont
from platypush.plugins import Plugin, action
[docs]
class DeviceInterface(enum.Enum):
I2C = 'i2c'
SPI = 'spi'
[docs]
class DeviceSlot(enum.IntEnum):
BACK = 0
FRONT = 1
[docs]
class DeviceRotation(enum.IntEnum):
ROTATE_0 = 0
ROTATE_90 = 1
ROTATE_180 = 2
ROTATE_270 = 3
[docs]
class LumaOledPlugin(Plugin):
"""
Plugin to interact with small OLED-based RaspberryPi displays through the luma.oled driver.
"""
[docs]
def __init__(
self,
interface: str,
device: str,
port: int = 0,
slot: int = DeviceSlot.BACK.value,
width: int = 128,
height: int = 64,
rotate: int = DeviceRotation.ROTATE_0.value,
gpio_DC: int = 24,
gpio_RST: int = 25,
bus_speed_hz: int = 8000000,
address: int = 0x3C,
cs_high: bool = False,
transfer_size: int = 4096,
spi_mode: Optional[int] = None,
font: Optional[str] = None,
font_size: int = 10,
**kwargs
):
"""
:param interface: Serial interface the display is connected to (``spi``
or ``i2c``).
:param device: Display chipset type (supported: ssd1306 ssd1309,
ssd1322, ssd1325, ssd1327, ssd1331, ssd1351, ssd1362, sh1106).
:param port: Device port (usually 0 or 1).
:param slot: Device slot (0 for back, 1 for front).
:param width: Display width.
:param height: Display height.
:param rotate: Display rotation (0 for no rotation, 1 for 90 degrees, 2
for 180 degrees, 3 for 270 degrees).
:param gpio_DC: [SPI only] GPIO PIN used for data (default: 24).
:param gpio_RST: [SPI only] GPIO PIN used for RST (default: 25).
:param bus_speed_hz: [SPI only] Bus speed in Hz (default: 8 MHz).
:param address: [I2C only] Device address (default: 0x3c).
:param cs_high: [SPI only] Set to True if the SPI chip select is high.
:param transfer_size: [SPI only] Maximum amount of bytes to transfer in
one go (default: 4096).
:param spi_mode: [SPI only] SPI mode as two bit pattern of clock
polarity and phase [CPOL|CPHA], 0-3 (default:None).
:param font: Path to a default TTF font used to display the text.
:param font_size: Font size - it only applies if ``font`` is set.
"""
import luma.core.interface.serial as serial
import luma.oled.device
from luma.core.render import canvas
super().__init__(**kwargs)
iface_name = interface
interface = getattr(serial, DeviceInterface(interface).value)
if iface_name == DeviceInterface.SPI.value:
self.serial = interface(
port=port,
device=slot,
cs_high=cs_high,
gpio_DC=gpio_DC,
gpio_RST=gpio_RST,
bus_speed_hz=bus_speed_hz,
transfer_size=transfer_size,
spi_mode=spi_mode,
)
else:
self.serial = interface(port=port, address=address)
device = getattr(luma.oled.device, device)
self.device = device(self.serial, width=width, height=height, rotate=rotate)
self.canvas = canvas(self.device)
self.font = None
self.font_size = font_size
self.font = self._get_font(font, font_size)
def _get_font(self, font: Optional[str] = None, font_size: Optional[int] = None):
if font:
return ImageFont.truetype(
os.path.abspath(os.path.expanduser(font)), font_size or self.font_size
)
return self.font
[docs]
@action
def clear(self):
"""
clear the display canvas.
"""
from luma.core.render import canvas
self.device.clear()
del self.canvas
self.canvas = canvas(self.device)
[docs]
@action
def text(
self,
text: str,
pos: Union[Tuple[int], List[int]] = (0, 0),
fill: str = 'white',
font: Optional[str] = None,
font_size: Optional[int] = None,
clear: bool = False,
):
"""
Draw text on the canvas.
:param text: Text to be drawn.
:param pos: Position of the text.
:param fill: Text color (default: ``white``).
:param font: ``font`` type override.
:param font_size: ``font_size`` override.
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
font = self._get_font(font, font_size)
with self.canvas as draw:
draw.text(pos, text, fill=fill, font=font)
[docs]
@action
def rectangle(
self,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
outline: Optional[str] = None,
width: int = 1,
clear: bool = False,
):
"""
Draw a rectangle on the canvas.
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param outline: Outline color - can be ``black`` or ``white``.
:param width: Figure width in pixels (default: 1).
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.rectangle(xy, outline=outline, fill=fill, width=width)
[docs]
@action
def arc(
self,
start: int,
end: int,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
outline: Optional[str] = None,
width: int = 1,
clear: bool = False,
):
"""
Draw an arc on the canvas.
:param start: Starting angle, in degrees (measured from 3 o' clock and
increasing clockwise).
:param end: Ending angle, in degrees (measured from 3 o' clock and
increasing clockwise).
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param outline: Outline color - can be ``black`` or ``white``.
:param width: Figure width in pixels (default: 1).
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.arc(xy, start=start, end=end, outline=outline, fill=fill, width=width)
[docs]
@action
def chord(
self,
start: int,
end: int,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
outline: Optional[str] = None,
width: int = 1,
clear: bool = False,
):
"""
Same as ``arc``, but it connects the end points with a straight line.
:param start: Starting angle, in degrees (measured from 3 o' clock and
increasing clockwise).
:param end: Ending angle, in degrees (measured from 3 o' clock and
increasing clockwise).
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param outline: Outline color - can be ``black`` or ``white``.
:param width: Figure width in pixels (default: 1).
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.chord(
xy, start=start, end=end, outline=outline, fill=fill, width=width
)
[docs]
@action
def pieslice(
self,
start: int,
end: int,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
outline: Optional[str] = None,
width: int = 1,
clear: bool = False,
):
"""
Same as ``arc``, but it also draws straight lines between the end
points and the center of the bounding box.
:param start: Starting angle, in degrees (measured from 3 o' clock and
increasing clockwise).
:param end: Ending angle, in degrees (measured from 3 o' clock and
increasing clockwise).
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param outline: Outline color - can be ``black`` or ``white``.
:param width: Figure width in pixels (default: 1).
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.pieslice(
xy, start=start, end=end, outline=outline, fill=fill, width=width
)
[docs]
@action
def ellipse(
self,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
outline: Optional[str] = None,
width: int = 1,
clear: bool = False,
):
"""
Draw an ellipse on the canvas.
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param outline: Outline color - can be ``black`` or ``white``.
:param width: Figure width in pixels (default: 1).
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.ellipse(xy, outline=outline, fill=fill, width=width)
[docs]
@action
def line(
self,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
outline: Optional[str] = None,
width: int = 1,
curve: bool = False,
clear: bool = False,
):
"""
Draw a line on the canvas.
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param outline: Outline color - can be ``black`` or ``white``.
:param width: Figure width in pixels (default: 1).
:param curve: Set to True for rounded edges (default: False).
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.line(
xy,
outline=outline,
fill=fill,
width=width,
joint='curve' if curve else None,
)
[docs]
@action
def point(
self,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
clear: bool = False,
):
"""
Draw one or more points on the canvas.
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.point(xy, fill=fill)
[docs]
@action
def polygon(
self,
xy: Optional[Union[Tuple[int], List[int]]] = None,
fill: Optional[str] = None,
outline: Optional[str] = None,
clear: bool = False,
):
"""
Draw a polygon on the canvas.
:param xy: Two points defining the bounding box, either as ``[(x0, y0),
(x1, y1)]`` or ``[x0, y0, x1, y1]``. Default: bounding box of the
device.
:param fill: Fill color - can be ``black`` or ``white``.
:param outline: Outline color - can be ``black`` or ``white``.
:param clear: Set to True if you want to clear the canvas before
writing the text (default: False).
"""
if clear:
self.clear()
if not xy:
xy = self.device.bounding_box
with self.canvas as draw:
draw.polygon(xy, outline=outline, fill=fill)
[docs]
@action
def image(self, image: str):
"""
Draws an image to the canvas (this will clear the existing canvas).
:param image: Image path.
"""
image = Image.open(os.path.abspath(os.path.expanduser(image)))
self.clear()
self.device.display(image)
# vim:sw=4:ts=4:et: