From 7586412ded0a55b76c0688dece6f7566d2de55b6 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sun, 16 Dec 2018 23:14:34 +0100 Subject: [PATCH] Serial plugin extended with a general purpose read() method - to communicate for e.g. Espruino and compatible devices --- platypush/plugins/serial/__init__.py | 135 +++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 9 deletions(-) diff --git a/platypush/plugins/serial/__init__.py b/platypush/plugins/serial/__init__.py index 262dfe779b..b88ee1bde0 100644 --- a/platypush/plugins/serial/__init__.py +++ b/platypush/plugins/serial/__init__.py @@ -1,3 +1,4 @@ +import base64 import json import serial import threading @@ -17,7 +18,7 @@ class SerialPlugin(GpioSensorPlugin): https://github.com/bblanchon/ArduinoJson. """ - def __init__(self, device, baud_rate=9600, *args, **kwargs): + def __init__(self, device=None, baud_rate=9600, *args, **kwargs): """ :param device: Device path (e.g. ``/dev/ttyUSB0`` or ``/dev/ttyACM0``) :type device: str @@ -64,14 +65,24 @@ class SerialPlugin(GpioSensorPlugin): return output.decode().strip() - def _get_serial(self, reset=False): + def _get_serial(self, device=None, baud_rate=None, reset=False): + if not device: + if not self.device: + raise RuntimeError('No device specified nor default device configured') + device = self.device + + if baud_rate is None: + if self.baud_rate is None: + raise RuntimeError('No baud_rate specified nor default configured') + baud_rate = self.baud_rate + if self.serial: if not reset: return self.serial self._close_serial() - self.serial = serial.Serial(self.device, self.baud_rate) + self.serial = serial.Serial(device, baud_rate) return self.serial def _close_serial(self): @@ -84,21 +95,37 @@ class SerialPlugin(GpioSensorPlugin): self.logger.exception(e) @action - def get_measurement(self): + def get_measurement(self, device=None, baud_rate=None): """ Reads JSON data from the serial device and returns it as a message + + :param device: Device path (default: default configured device) + :type device: str + + :param baud_rate: Baud rate (default: default configured baud_rate) + :type baud_rate: int """ + if not device: + if not self.device: + raise RuntimeError('No device specified nor default device configured') + device = self.device + + if baud_rate is None: + if self.baud_rate is None: + raise RuntimeError('No baud_rate specified nor default configured') + baud_rate = self.baud_rate + data = None try: serial_available = self.serial_lock.acquire(timeout=2) if serial_available: try: - ser = self._get_serial() + ser = self._get_serial(device=device) except: time.sleep(1) - ser = self._get_serial(reset=True) + ser = self._get_serial(device=device, reset=True) data = self._read_json(ser) @@ -120,15 +147,105 @@ class SerialPlugin(GpioSensorPlugin): return data + @action - def write(self, data): + def read(self, device=None, baud_rate=None, size=None, end=None): + """ + Reads raw data from the serial device + + :param device: Device to read (default: default configured device) + :type device: str + + :param baud_rate: Baud rate (default: default configured baud_rate) + :type baud_rate: int + + :param size: Number of bytes to read + :type size: int + + :param end: End of message byte or character + :type end: int, bytes or str + """ + + if not device: + if not self.device: + raise RuntimeError('No device specified nor default device configured') + device = self.device + + if baud_rate is None: + if self.baud_rate is None: + raise RuntimeError('No baud_rate specified nor default configured') + baud_rate = self.baud_rate + + if (size is None and end is None) or (size is not None and end is not None): + raise RuntimeError('Either size or end must be specified') + + if end and len(end) > 1: + raise RuntimeError('The serial end must be a single character, not a string') + + data = bytes() + + try: + serial_available = self.serial_lock.acquire(timeout=2) + if serial_available: + try: + ser = self._get_serial(device=device) + except: + time.sleep(1) + ser = self._get_serial(device=device, reset=True) + + if size is not None: + for _ in range(0, size): + data += ser.read() + elif end is not None: + if isinstance(end, str): + end = end.encode() + + ch = None + while ch != end: + ch = ser.read() + + if ch != end: + data += ch + else: + self.logger.warning('Serial read timeout') + finally: + try: + self.serial_lock.release() + except: + pass + + try: + data = data.decode() + except: + data = base64.encodebytes(data) + + return data + + @action + def write(self, data, device=None, baud_rate=None): """ Writes data to the serial device. + :param device: Device to write (default: default configured device) + :type device: str + + :param baud_rate: Baud rate (default: default configured baud_rate) + :type baud_rate: int + :param data: Data to send to the serial device :type data: str, bytes or dict. If dict, it will be serialized as JSON. """ + if not device: + if not self.device: + raise RuntimeError('No device specified nor default device configured') + device = self.device + + if baud_rate is None: + if self.baud_rate is None: + raise RuntimeError('No baud_rate specified nor default configured') + baud_rate = self.baud_rate + if isinstance(data, dict): data = json.dumps(data) if isinstance(data, str): @@ -138,10 +255,10 @@ class SerialPlugin(GpioSensorPlugin): serial_available = self.serial_lock.acquire(timeout=2) if serial_available: try: - ser = self._get_serial() + ser = self._get_serial(device=device) except: time.sleep(1) - ser = self._get_serial(reset=True) + ser = self._get_serial(device=device, reset=True) self.logger.info('Writing {} to {}'.format(data, self.device)) ser.write(data)