Added LCD display integration (closes #145)

This commit is contained in:
Fabio Manganiello 2020-08-23 20:00:04 +01:00
parent 7a7c065754
commit af614480b8
6 changed files with 421 additions and 0 deletions

View file

@ -260,6 +260,8 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
'twilio',
'pytz',
'Adafruit_Python_DHT',
'RPi.GPIO',
'RPLCD',
]
sys.path.insert(0, os.path.abspath('../..'))

View file

@ -0,0 +1,246 @@
from abc import ABC, abstractmethod
from enum import Enum
from typing import List, Optional
from platypush.plugins import Plugin, action
class PinMode(Enum):
import RPi.GPIO
BOARD = RPi.GPIO.BOARD
BCM = RPi.GPIO.BCM
class LcdPlugin(Plugin, ABC):
"""
Abstract class for plugins to communicate with LCD displays.
Requires:
* **RPLCD** (``pip install RPLCD``)
* **RPi.GPIO** (``pip install RPi.GPIO``)
"""
import RPLCD.lcd
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.lcd = None
@staticmethod
def _get_pin_mode(pin_mode: str) -> int:
pin_mode = pin_mode.upper()
assert hasattr(PinMode, pin_mode), \
'Invalid pin_mode: {}. Supported modes: {}'.format(
pin_mode, list([mode.name for mode in PinMode if mode.name != 'RPi']))
return getattr(PinMode, pin_mode).value
@abstractmethod
def _get_lcd(self) -> RPLCD.lcd.BaseCharLCD:
pass
def _init_lcd(self):
if self.lcd:
return
self.lcd = self._get_lcd()
@action
def close(self, clear: bool = False):
"""
Close the handler to the LCD display and release the GPIO resources.
:param clear: Clear the display as well on close (default: False).
"""
if self.lcd:
self.lcd.close(clear=clear)
self.lcd = None
@action
def clear(self):
"""
Clear the LCD display.
"""
self._init_lcd()
self.lcd.clear()
@action
def home(self):
"""
Set cursor to initial position and reset any shifting.
"""
self._init_lcd()
self.lcd.home()
@action
def shift_display(self, amount: int):
"""
Set cursor to initial position and reset any shifting.
"""
self._init_lcd()
self.lcd.shift_display(amount)
@action
def write_string(self, value: str, position: Optional[List[int]] = None):
"""
Write a string to the display.
:param value: String to be displayed.
:param position: String position on the display as a 2-int list.
"""
self._init_lcd()
if position:
self.lcd.cursor_pos = tuple(position)
self.lcd.write_string(value)
@action
def set_cursor_pos(self, position: List[int]):
"""
Change the position of the cursor on the display.
:param position: New cursor position, as a list of two elements.
"""
self._init_lcd()
self.lcd.cursor_pos = tuple(position)
@action
def set_text_align(self, mode: str):
"""
Change the text align mode.
:param mode: Supported values: ``left``, ``right``.
"""
modes = ['left', 'right']
mode = mode.lower()
assert mode in modes, 'Unsupported text mode: {}. Supported modes: {}'.format(
mode, modes)
self._init_lcd()
self.lcd.text_align_mode = mode
@action
def enable_display(self):
"""
Turn on the display.
"""
self._init_lcd()
self.lcd.display_enabled = True
@action
def disable_display(self):
"""
Turn off the display.
"""
self._init_lcd()
self.lcd.display_enabled = False
@action
def toggle_display(self):
"""
Toggle the display state.
"""
self._init_lcd()
self.lcd.display_enabled = not self.lcd.display_enabled
@action
def enable_backlight(self):
"""
Enable the display backlight.
"""
self._init_lcd()
self.lcd.backlight_enabled = True
@action
def disable_backlight(self):
"""
Disable the display backlight.
"""
self._init_lcd()
self.lcd.backlight_enabled = False
@action
def toggle_backlight(self):
"""
Toggle the display backlight on/off.
"""
self._init_lcd()
self.lcd.backlight_enabled = not self.lcd.backlight_enabled
@action
def create_char(self, location: int, bitmap: List[int]):
"""
Create a new character.
The HD44780 supports up to 8 custom characters (location 0-7).
:param location: The place in memory where the character is stored.
Values need to be integers between 0 and 7.
:param bitmap: The bitmap containing the character. This should be a
list of 8 numbers, each representing a 5 pixel row.
Example for the smiley character:
.. code-block:: python
[
0, # 0b00000
10, # 0b01010
10, # 0b01010
0, # 0b00000
17, # 0b10001
17, # 0b10001
14, # 0b01110
0 # 0b00000
]
"""
self._init_lcd()
self.lcd.create_char(location=location, bitmap=tuple(bitmap))
@action
def command(self, value: int):
"""
Send a raw command to the LCD.
:param value: Command to be sent.
"""
self._init_lcd()
self.lcd.command(value)
@action
def write(self, value: int):
"""
Write a raw byte to the LCD.
:param value: Byte to be sent.
"""
self._init_lcd()
self.lcd.write(value)
@action
def cr(self):
"""
Write a carriage return (``\\r``) character to the LCD.
"""
self._init_lcd()
self.lcd.cr()
@action
def lf(self):
"""
Write a line feed (``\\n``) character to the LCD.
"""
self._init_lcd()
self.lcd.lf()
@action
def crlf(self):
"""
Write a carriage return + line feed (``\\r\\n``) sequence to the LCD.
"""
self._init_lcd()
self.lcd.crlf()
# vim:sw=4:ts=4:et:

View file

@ -0,0 +1,85 @@
from typing import List, Optional
from platypush.plugins import action
from platypush.plugins.lcd import LcdPlugin
class LcdGpioPlugin(LcdPlugin):
"""
Plugin to write to an LCD display connected via GPIO.
Requires:
* **RPLCD** (``pip install RPLCD``)
* **RPi.GPIO** (``pip install RPi.GPIO``)
"""
def __init__(self, pin_rs: int, pin_e: int, pins_data: List[int],
pin_rw: Optional[int] = None, pin_mode: str = 'BOARD',
pin_backlight: Optional[int] = None,
cols: int = 16, rows: int = 2,
backlight_enabled: bool = True,
backlight_mode: str = 'active_low',
dotsize: int = 8, charmap: str = 'A02',
auto_linebreaks: bool = True,
compat_mode: bool = False, **kwargs):
"""
:param pin_rs: Pin for register select (RS).
:param pin_e: Pin to start data read or write (E).
:param pins_data: List of data bus pins in 8 bit mode (DB0-DB7) or in 4
bit mode (DB4-DB7) in ascending order.
:param pin_mode: Which scheme to use for numbering of the GPIO pins,
either ``BOARD`` or ``BCM``. Default: ``BOARD``.
:param pin_rw: Pin for selecting read or write mode (R/W). Default:
``None``, read only mode.
:param pin_backlight: Pin for controlling backlight on/off. Set this to
``None`` for no backlight control. Default: ``None``.
:param cols: Number of columns per row (usually 16 or 20). Default: ``16``.
:param rows: Number of display rows (usually 1, 2 or 4). Default: ``2``.
:param backlight_enabled: Whether the backlight is enabled initially.
Default: ``True``. Has no effect if pin_backlight is ``None``
:param backlight_mode: Set this to either ``active_high`` or ``active_low``
to configure the operating control for the backlight. Has no effect if
pin_backlight is ``None``
:param dotsize: Some 1 line displays allow a font height of 10px.
Allowed: ``8`` or ``10``. Default: ``8``.
:param charmap: The character map used. Depends on your LCD. This must
be either ``A00`` or ``A02`` or ``ST0B``. Default: ``A02``.
:param auto_linebreaks: Whether or not to automatically insert line
breaks. Default: ``True``.
:param compat_mode: Whether to run additional checks to support older LCDs
that may not run at the reference clock (or keep up with it).
Default: ``False``.
"""
super().__init__(**kwargs)
self.pin_mode = self._get_pin_mode(pin_mode)
self.pin_rs = pin_rs
self.pin_e = pin_e
self.pin_rw = pin_rw
self.pin_backlight = pin_backlight
self.pins_data = pins_data
self.cols = cols
self.rows = rows
self.backlight_enabled = backlight_enabled
self.backlight_mode = backlight_mode
self.dotsize = dotsize
self.auto_linebreaks = auto_linebreaks
self.compat_mode = compat_mode
self.charmap = charmap
def _get_lcd(self):
from RPLCD.gpio import CharLCD
return CharLCD(cols=self.cols, rows=self.rows, pin_rs=self.pin_rs,
pin_e=self.pin_e, pins_data=self.pins_data,
numbering_mode=self.pin_mode, pin_rw=self.pin_rw,
pin_backlight=self.pin_backlight,
backlight_enabled=self.backlight_enabled,
backlight_mode=self.backlight_mode,
dotsize=self.dotsize, charmap=self.charmap,
auto_linebreaks=self.auto_linebreaks,
compat_mode=self.compat_mode)
# vim:sw=4:ts=4:et:

View file

@ -0,0 +1,82 @@
from typing import List, Optional
from platypush.plugins import action
from platypush.plugins.lcd import LcdPlugin
class LcdI2cPlugin(LcdPlugin):
"""
Plugin to write to an LCD display connected via I2C.
Adafruit I2C/SPI LCD Backback is supported.
Warning: You might need a level shifter (that supports i2c)
between the SCL/SDA connections on the MCP chip / backpack and the Raspberry Pi.
Or you might damage the Pi and possibly any other 3.3V i2c devices
connected on the i2c bus. Or cause reliability issues. The SCL/SDA are rated 0.7*VDD
on the MCP23008, so it needs 3.5V on the SCL/SDA when 5V is applied to drive the LCD.
The MCP23008 and MCP23017 needs to be connected exactly the same way as the backpack.
For complete schematics see the adafruit page at:
https://learn.adafruit.com/i2c-spi-lcd-backpack/
4-bit operation. I2C only supported.
Pin mapping::
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
BL | D7 | D6 | D5 | D4 | E | RS | -
Requires:
* **RPLCD** (``pip install RPLCD``)
* **RPi.GPIO** (``pip install RPi.GPIO``)
"""
def __init__(self, i2c_expander: str, address: int,
expander_params: Optional[dict] = None,
port: int = 1, cols: int = 16, rows: int = 2,
backlight_enabled: bool = True,
dotsize: int = 8, charmap: str = 'A02',
auto_linebreaks: bool = True, **kwargs):
"""
:param i2c_expander: Set your I²C chip type. Supported: "PCF8574", "MCP23008", "MCP23017".
:param address: The I2C address of your LCD.
:param expander_params: Parameters for expanders, in a dictionary. Only needed for MCP23017
gpio_bank - This must be either ``A`` or ``B``
If you have a HAT, A is usually marked 1 and B is 2.
Example: ``expander_params={'gpio_bank': 'A'}``
:param port: The I2C port number. Default: ``1``.
:param cols: Number of columns per row (usually 16 or 20). Default: ``16``.
:param rows: Number of display rows (usually 1, 2 or 4). Default: ``2``.
:param backlight_enabled: Whether the backlight is enabled initially.
Default: ``True``. Has no effect if pin_backlight is ``None``
:param dotsize: Some 1 line displays allow a font height of 10px.
Allowed: ``8`` or ``10``. Default: ``8``.
:param charmap: The character map used. Depends on your LCD. This must
be either ``A00`` or ``A02`` or ``ST0B``. Default: ``A02``.
:param auto_linebreaks: Whether or not to automatically insert line
breaks. Default: ``True``.
"""
super().__init__(**kwargs)
self.i2c_expander = i2c_expander
self.address = address
self.expander_params = expander_params or {}
self.port = port
self.cols = cols
self.rows = rows
self.backlight_enabled = backlight_enabled
self.dotsize = dotsize
self.auto_linebreaks = auto_linebreaks
self.charmap = charmap
def _get_lcd(self):
from RPLCD.i2c import CharLCD
return CharLCD(cols=self.cols, rows=self.rows,
i2c_expander=self.i2c_expander,
address=self.address, port=self.port,
backlight_enabled=self.backlight_enabled,
dotsize=self.dotsize, charmap=self.charmap,
auto_linebreaks=self.auto_linebreaks)
# vim:sw=4:ts=4:et:

View file

@ -289,3 +289,7 @@ croniter
# Support for DHT11/DHT22/AM2302 temperature/humidity sensors
# git+https://github.com/adafruit/Adafruit_Python_DHT
# Support for LCD display integration
# RPi.GPIO
# RPLCD

View file

@ -328,5 +328,7 @@ setup(
'github': ['pytz'],
# Support for DHT11/DHT22/AM2302 temperature/humidity sensors
'dht': ['Adafruit_Python_DHT @ git+https://github.com/adafruit/Adafruit_Python_DHT'],
# Support for LCD display integration
'lcd': ['RPi.GPIO', 'RPLCD'],
},
)