Added luma.oled display support

This commit is contained in:
Fabio Manganiello 2020-08-11 14:43:54 +02:00
parent d3e52ba944
commit 6b43a5e592
8 changed files with 334 additions and 9 deletions

View file

@ -11,11 +11,6 @@ Backends
platypush/backend/assistant.rst
platypush/backend/assistant.google.rst
platypush/backend/assistant.snowboy.rst
platypush/backend/bluetooth.rst
platypush/backend/bluetooth.fileserver.rst
platypush/backend/bluetooth.pushserver.rst
platypush/backend/bluetooth.scanner.rst
platypush/backend/bluetooth.scanner.ble.rst
platypush/backend/button.flic.rst
platypush/backend/camera.pi.rst
platypush/backend/chat.telegram.rst
@ -53,7 +48,6 @@ Backends
platypush/backend/sensor.distance.vl53l1x.rst
platypush/backend/sensor.envirophat.rst
platypush/backend/sensor.ir.zeroborg.rst
platypush/backend/sensor.leap.rst
platypush/backend/sensor.ltr559.rst
platypush/backend/sensor.mcp3008.rst
platypush/backend/sensor.motion.pwm3901.rst

View file

@ -252,6 +252,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
'pandas',
'samsungtvws',
'paramiko',
'luma',
]
sys.path.insert(0, os.path.abspath('../..'))

View file

@ -0,0 +1,5 @@
``platypush.plugins.luma.oled``
===============================
.. automodule:: platypush.plugins.luma.oled
:members:

View file

@ -8,7 +8,6 @@ Plugins
platypush/plugins/adafruit.io.rst
platypush/plugins/alarm.rst
platypush/plugins/arduino.rst
platypush/plugins/assistant.rst
platypush/plugins/assistant.echo.rst
platypush/plugins/assistant.google.rst
@ -66,6 +65,7 @@ Plugins
platypush/plugins/light.hue.rst
platypush/plugins/linode.rst
platypush/plugins/logger.rst
platypush/plugins/luma.oled.rst
platypush/plugins/media.rst
platypush/plugins/media.chromecast.rst
platypush/plugins/media.ctrl.rst
@ -85,7 +85,6 @@ Plugins
platypush/plugins/music.mpd.rst
platypush/plugins/music.snapcast.rst
platypush/plugins/nmap.rst
platypush/plugins/otp.rst
platypush/plugins/pihole.rst
platypush/plugins/ping.rst
platypush/plugins/printer.cups.rst
@ -98,7 +97,6 @@ Plugins
platypush/plugins/sound.rst
platypush/plugins/ssh.rst
platypush/plugins/stt.rst
platypush/plugins/stt.deepspeech.rst
platypush/plugins/stt.picovoice.hotword.rst
platypush/plugins/stt.picovoice.speech.rst
platypush/plugins/switch.rst

View file

View file

@ -0,0 +1,322 @@
import enum
import os
from typing import Optional, Union, Tuple, List
import luma.core.interface.serial
import luma.oled.device
from luma.core.render import canvas
from PIL import Image, ImageFont
from platypush.plugins import Plugin, action
class DeviceInterface(enum.Enum):
I2C = 'i2c'
SPI = 'spi'
class DeviceSlot(enum.IntEnum):
BACK = 0
FRONT = 1
class DeviceRotation(enum.IntEnum):
ROTATE_0 = 0
ROTATE_90 = 1
ROTATE_180 = 2
ROTATE_270 = 3
class LumaOledPlugin(Plugin):
"""
Plugin to interact with small OLED-based RaspberryPi displays through the luma.oled driver.
Requires:
* **luma.oled** (``pip install git+https://github.com/rm-hull/luma.oled``)
"""
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.
"""
super().__init__(**kwargs)
iface_name = interface
interface = getattr(luma.core.interface.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
@action
def clear(self):
"""
clear the display canvas.
"""
self.device.clear()
del self.canvas
self.canvas = canvas(self.device)
@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)
@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)
@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)
@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)
@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)
@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)
@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: Sequence of either 2-tuples like [(x, y), (x, y), ...] or numeric values like [x, y, x, y, ...].
: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)
@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: Sequence of either 2-tuples like [(x, y), (x, y), ...] or numeric values like [x, y, x, y, ...].
: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)
@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: Sequence of either 2-tuples like [(x, y), (x, y), ...] or numeric values like [x, y, x, y, ...].
: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)
@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:

View file

@ -270,3 +270,6 @@ croniter
# Support for Google Translate
# google-cloud-translate
# Support for luma.oled
# git+https://github.com/rm-hull/luma.oled

View file

@ -316,5 +316,7 @@ setup(
'ssh': ['paramiko'],
# Support for clipboard integration
'clipboard': ['pyperclip'],
# Support for luma.oled display drivers
'luma-oled': ['luma.oled @ git+https://github.com/rm-hull/luma.oled'],
},
)