From ffd23cf04d2bd4362acc304972db6d813bb41062 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sun, 27 Mar 2022 16:14:30 +0200 Subject: [PATCH] [#212] Support for asynchronous event monitoring on the GPIO plugin --- platypush/message/event/gpio/__init__.py | 17 +++++ platypush/plugins/__init__.py | 3 +- platypush/plugins/gpio/__init__.py | 79 +++++++++++++++++++----- platypush/plugins/gpio/manifest.yaml | 4 +- 4 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 platypush/message/event/gpio/__init__.py diff --git a/platypush/message/event/gpio/__init__.py b/platypush/message/event/gpio/__init__.py new file mode 100644 index 000000000..c8c5f244b --- /dev/null +++ b/platypush/message/event/gpio/__init__.py @@ -0,0 +1,17 @@ +from typing import Union + +from platypush.message.event import Event + + +class GPIOEvent(Event): + """ + Event triggered when the value on a GPIO PIN changes. + """ + + def __init__(self, pin: Union[int, str], value: int, *args, **kwargs): + """ + :param pin: PIN number or name. + :param value: Current value of the PIN. + """ + super().__init__(*args, pin=pin, value=value, **kwargs) + diff --git a/platypush/plugins/__init__.py b/platypush/plugins/__init__.py index 6a55f4b95..8338620ec 100644 --- a/platypush/plugins/__init__.py +++ b/platypush/plugins/__init__.py @@ -88,7 +88,8 @@ class RunnablePlugin(Plugin): if self._thread and self._thread.is_alive(): self.logger.info(f'Waiting for {self.__class__.__name__} to stop') try: - self._thread.join() + if self._thread: + self._thread.join() except Exception as e: self.logger.warning(f'Could not join thread on stop: {e}') diff --git a/platypush/plugins/gpio/__init__.py b/platypush/plugins/gpio/__init__.py index e4bddbc58..53f677a68 100644 --- a/platypush/plugins/gpio/__init__.py +++ b/platypush/plugins/gpio/__init__.py @@ -3,35 +3,58 @@ """ import threading -from typing import Any, Optional, Dict, Union +from typing import Any, Optional, Dict, Union, Collection -from platypush.plugins import Plugin, action +from platypush.context import get_bus +from platypush.message.event.gpio import GPIOEvent +from platypush.plugins import RunnablePlugin, action -class GpioPlugin(Plugin): +class GpioPlugin(RunnablePlugin): """ - Plugin to handle raw read/write operation on the Raspberry Pi GPIO pins. + This plugin can be used to interact with custom electronic devices + connected to a Raspberry Pi (or compatible device) over GPIO pins. Requires: * **RPi.GPIO** (``pip install RPi.GPIO``) + + Triggers: + + * :class:`platypush.message.event.gpio.GPIOEvent` when the value of a + monitored PIN changes. + """ - def __init__(self, pins: Optional[Dict[str, int]] = None, mode: str = 'board', **kwargs): + def __init__( + self, + pins: Optional[Dict[str, int]] = None, + monitored_pins: Optional[Collection[Union[str, int]]] = None, + mode: str = 'board', + **kwargs + ): """ - :param mode: Specify 'board' if you want to use the board PIN numbers, - 'bcm' for Broadcom PIN numbers (default: 'board') - :param pins: Configuration for the GPIO PINs as a name -> pin_number map. + :param mode: Specify ``board`` if you want to use the board PIN numbers, + ``bcm`` for Broadcom PIN numbers (default: ``board``) + :param pins: Custom `GPIO name` -> `PIN number` mapping. This can be + useful if you want to reference your GPIO ports by name instead + of PIN number. - Example:: + Example: - { - "LED_1": 14, - "LED_2": 15, - "MOTOR": 16, - "SENSOR": 17 - } + .. code-block:: yaml + pins: + LED_1: 14, + LED_2: 15, + MOTOR: 16, + SENSOR_1: 17 + SENSOR_2: 18 + + :param monitored_pins: List of PINs to monitor. If a new value is detected + on these pins then a :class:`platypush.message.event.gpio.GPIOEvent` + event will be triggered. GPIO PINS can be referenced either by number + or name, if a name is specified on the `pins` argument. """ super().__init__(**kwargs) @@ -39,6 +62,7 @@ class GpioPlugin(Plugin): self._initialized = False self._init_lock = threading.RLock() self._initialized_pins = {} + self._monitored_pins = monitored_pins or [] self.pins_by_name = pins if pins else {} self.pins_by_number = {number: name for (name, number) in self.pins_by_name.items()} @@ -71,6 +95,31 @@ class GpioPlugin(Plugin): assert mode_str in ['BOARD', 'BCM'], 'Invalid mode: {}'.format(mode_str) return getattr(GPIO, mode_str) + def on_gpio_event(self): + def callback(pin: int): + import RPi.GPIO as GPIO + value = GPIO.input(pin) + pin = self.pins_by_number.get(pin, pin) + get_bus().post(GPIOEvent(pin=pin, value=value)) + + return callback + + def main(self): + import RPi.GPIO as GPIO + if not self._monitored_pins: + return # No need to start the monitor + + self._init_board() + monitored_pins = [ + self._get_pin_number(pin) for pin in self._monitored_pins + ] + + for pin in monitored_pins: + GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + GPIO.add_event_detect(pin, GPIO.BOTH, callback=self.on_gpio_event()) + + self._should_stop.wait() + @action def write(self, pin: Union[int, str], value: Union[int, bool], name: Optional[str] = None) -> Dict[str, Any]: diff --git a/platypush/plugins/gpio/manifest.yaml b/platypush/plugins/gpio/manifest.yaml index 49d157e55..dfe358cc9 100644 --- a/platypush/plugins/gpio/manifest.yaml +++ b/platypush/plugins/gpio/manifest.yaml @@ -1,5 +1,7 @@ manifest: - events: {} + events: + - platypush.message.event.gpio.GPIOEvent: + When the value of a monitored PIN changes. install: pip: - RPi.GPIO