openrvdas.logger.readers.serial_reader
1#!/usr/bin/env python3 2 3import logging 4 5# Don't freak out if pyserial isn't installed - unless they actually 6# try to instantiate a SerialReader 7try: 8 import serial 9 SERIAL_MODULE_FOUND = True 10except ModuleNotFoundError: 11 SERIAL_MODULE_FOUND = False 12 13from logger.readers.reader import Reader # noqa: E402 14 15 16################################################################################ 17class SerialReader(Reader): 18 """ 19 Read records from a serial port. 20 """ 21 22 def __init__(self, port, baudrate=9600, bytesize=8, parity='N', 23 stopbits=1, timeout=None, xonxoff=False, rtscts=False, 24 write_timeout=None, dsrdtr=False, inter_byte_timeout=None, 25 exclusive=None, max_bytes=None, eol=None, allow_empty=False, 26 encoding='utf-8', encoding_errors='ignore', **kwargs): 27 """If max_bytes is specified on initialization, read up to that many 28 bytes when read() is called. If eol is not specified, read() will 29 read up to the first newline it receives. In both cases, if 30 timeout is specified, it will return after timeout with as many 31 bytes as it has succeeded in reading. 32 33 By default, the SerialReader will read until it encounters a newline character. 34 This behavior may be overwritten by specifying 35 36 max_bytes - if specified, and write_timeout is None, read this many bytes per record. 37 If write_timeout is not None, it may return fewer bytes. 38 39 eol - if specified, read up until encountering the specified eol 40 41 By default, the SerialReader will assume that records are encoded in UTF-8, and will 42 ignore non unicode characters it encounters. These defaults may be changed by specifying 43 44 allow_empty - If True, preserve and return empty records 45 46 encoding - 'utf-8' by default. If empty or None, do not attempt any decoding 47 and return raw bytes. Other possible encodings are listed in online 48 documentation here: 49 https://docs.python.org/3/library/codecs.html#standard-encodings 50 51 encoding_errors - 'ignore' by default. Other error strategies are 'strict', 52 'replace', and 'backslashreplace', described here: 53 https://docs.python.org/3/howto/unicode.html#encodings 54 55 command line example: 56 ``` 57 # Read serial port ttyr05 expecting a LF as end of record 58 logger/listener/listen.py --serial port=/dev/ttyr05,eol='\r' 59 ``` 60 config example: 61 ``` 62 class: SerialReader 63 kwargs: 64 baudrate: 4800 65 port: /dev/ttyr05 66 eol: \r 67 ``` 68 """ 69 super().__init__(encoding=encoding, encoding_errors=encoding_errors, **kwargs) 70 71 if not SERIAL_MODULE_FOUND: 72 raise RuntimeError('Serial port functionality not available. Please ' 73 'install Python module pyserial.') 74 try: 75 self.serial = serial.Serial(port=port, baudrate=baudrate, 76 bytesize=bytesize, parity=parity, 77 stopbits=stopbits, timeout=timeout, 78 xonxoff=xonxoff, rtscts=rtscts, 79 write_timeout=write_timeout, dsrdtr=dsrdtr, 80 inter_byte_timeout=inter_byte_timeout, 81 exclusive=exclusive) 82 except (serial.SerialException, serial.serialutil.SerialException) as e: 83 logging.fatal('Failed to open serial port %s: %s', port, e) 84 raise 85 86 self.max_bytes = max_bytes 87 self.encoding = encoding 88 self.allow_empty = allow_empty 89 self.encoding_errors = encoding_errors 90 91 # 'eol' comes in as a (probably escaped) string. We need to 92 # unescape it, which means converting to bytes and back. 93 # 94 # NOTE: This block is different from SerialWriter because we use 95 # readline() in here, which already looks for trailing '\n' and 96 # handles encoding itself. 97 # 98 if eol is not None and self.encoding: 99 eol = self._encode_str(eol, unescape=True) 100 self.eol = eol 101 102 ############################ 103 def read(self): 104 try: 105 if self.eol: 106 record = self.serial.read_until(expected=self.eol, size=self.max_bytes) 107 # read_until()'s record includes a trailing 'eol', strip it off 108 # 109 # NOTE: But don't use rstrip which just looks explicitly for 110 # whitespace 111 # 112 record = record.rsplit(self.eol)[0] 113 elif self.max_bytes: 114 # no stripping on this one, just use exactly what we got 115 record = self.serial.read(size=self.max_bytes) 116 else: 117 # readline()'s record includes the trailing '\n', strip it off 118 record = self.serial.readline().rstrip() 119 120 return self._decode_bytes(record, self.allow_empty) 121 122 except KeyboardInterrupt as e: 123 raise e 124 except serial.serialutil.SerialException as e: 125 logging.error(str(e)) 126 return None
18class SerialReader(Reader): 19 """ 20 Read records from a serial port. 21 """ 22 23 def __init__(self, port, baudrate=9600, bytesize=8, parity='N', 24 stopbits=1, timeout=None, xonxoff=False, rtscts=False, 25 write_timeout=None, dsrdtr=False, inter_byte_timeout=None, 26 exclusive=None, max_bytes=None, eol=None, allow_empty=False, 27 encoding='utf-8', encoding_errors='ignore', **kwargs): 28 """If max_bytes is specified on initialization, read up to that many 29 bytes when read() is called. If eol is not specified, read() will 30 read up to the first newline it receives. In both cases, if 31 timeout is specified, it will return after timeout with as many 32 bytes as it has succeeded in reading. 33 34 By default, the SerialReader will read until it encounters a newline character. 35 This behavior may be overwritten by specifying 36 37 max_bytes - if specified, and write_timeout is None, read this many bytes per record. 38 If write_timeout is not None, it may return fewer bytes. 39 40 eol - if specified, read up until encountering the specified eol 41 42 By default, the SerialReader will assume that records are encoded in UTF-8, and will 43 ignore non unicode characters it encounters. These defaults may be changed by specifying 44 45 allow_empty - If True, preserve and return empty records 46 47 encoding - 'utf-8' by default. If empty or None, do not attempt any decoding 48 and return raw bytes. Other possible encodings are listed in online 49 documentation here: 50 https://docs.python.org/3/library/codecs.html#standard-encodings 51 52 encoding_errors - 'ignore' by default. Other error strategies are 'strict', 53 'replace', and 'backslashreplace', described here: 54 https://docs.python.org/3/howto/unicode.html#encodings 55 56 command line example: 57 ``` 58 # Read serial port ttyr05 expecting a LF as end of record 59 logger/listener/listen.py --serial port=/dev/ttyr05,eol='\r' 60 ``` 61 config example: 62 ``` 63 class: SerialReader 64 kwargs: 65 baudrate: 4800 66 port: /dev/ttyr05 67 eol: \r 68 ``` 69 """ 70 super().__init__(encoding=encoding, encoding_errors=encoding_errors, **kwargs) 71 72 if not SERIAL_MODULE_FOUND: 73 raise RuntimeError('Serial port functionality not available. Please ' 74 'install Python module pyserial.') 75 try: 76 self.serial = serial.Serial(port=port, baudrate=baudrate, 77 bytesize=bytesize, parity=parity, 78 stopbits=stopbits, timeout=timeout, 79 xonxoff=xonxoff, rtscts=rtscts, 80 write_timeout=write_timeout, dsrdtr=dsrdtr, 81 inter_byte_timeout=inter_byte_timeout, 82 exclusive=exclusive) 83 except (serial.SerialException, serial.serialutil.SerialException) as e: 84 logging.fatal('Failed to open serial port %s: %s', port, e) 85 raise 86 87 self.max_bytes = max_bytes 88 self.encoding = encoding 89 self.allow_empty = allow_empty 90 self.encoding_errors = encoding_errors 91 92 # 'eol' comes in as a (probably escaped) string. We need to 93 # unescape it, which means converting to bytes and back. 94 # 95 # NOTE: This block is different from SerialWriter because we use 96 # readline() in here, which already looks for trailing '\n' and 97 # handles encoding itself. 98 # 99 if eol is not None and self.encoding: 100 eol = self._encode_str(eol, unescape=True) 101 self.eol = eol 102 103 ############################ 104 def read(self): 105 try: 106 if self.eol: 107 record = self.serial.read_until(expected=self.eol, size=self.max_bytes) 108 # read_until()'s record includes a trailing 'eol', strip it off 109 # 110 # NOTE: But don't use rstrip which just looks explicitly for 111 # whitespace 112 # 113 record = record.rsplit(self.eol)[0] 114 elif self.max_bytes: 115 # no stripping on this one, just use exactly what we got 116 record = self.serial.read(size=self.max_bytes) 117 else: 118 # readline()'s record includes the trailing '\n', strip it off 119 record = self.serial.readline().rstrip() 120 121 return self._decode_bytes(record, self.allow_empty) 122 123 except KeyboardInterrupt as e: 124 raise e 125 except serial.serialutil.SerialException as e: 126 logging.error(str(e)) 127 return None
Read records from a serial port.
23 def __init__(self, port, baudrate=9600, bytesize=8, parity='N', 24 stopbits=1, timeout=None, xonxoff=False, rtscts=False, 25 write_timeout=None, dsrdtr=False, inter_byte_timeout=None, 26 exclusive=None, max_bytes=None, eol=None, allow_empty=False, 27 encoding='utf-8', encoding_errors='ignore', **kwargs): 28 """If max_bytes is specified on initialization, read up to that many 29 bytes when read() is called. If eol is not specified, read() will 30 read up to the first newline it receives. In both cases, if 31 timeout is specified, it will return after timeout with as many 32 bytes as it has succeeded in reading. 33 34 By default, the SerialReader will read until it encounters a newline character. 35 This behavior may be overwritten by specifying 36 37 max_bytes - if specified, and write_timeout is None, read this many bytes per record. 38 If write_timeout is not None, it may return fewer bytes. 39 40 eol - if specified, read up until encountering the specified eol 41 42 By default, the SerialReader will assume that records are encoded in UTF-8, and will 43 ignore non unicode characters it encounters. These defaults may be changed by specifying 44 45 allow_empty - If True, preserve and return empty records 46 47 encoding - 'utf-8' by default. If empty or None, do not attempt any decoding 48 and return raw bytes. Other possible encodings are listed in online 49 documentation here: 50 https://docs.python.org/3/library/codecs.html#standard-encodings 51 52 encoding_errors - 'ignore' by default. Other error strategies are 'strict', 53 'replace', and 'backslashreplace', described here: 54 https://docs.python.org/3/howto/unicode.html#encodings 55 56 command line example: 57 ``` 58 # Read serial port ttyr05 expecting a LF as end of record 59 logger/listener/listen.py --serial port=/dev/ttyr05,eol='\r' 60 ``` 61 config example: 62 ``` 63 class: SerialReader 64 kwargs: 65 baudrate: 4800 66 port: /dev/ttyr05 67 eol: \r 68 ``` 69 """ 70 super().__init__(encoding=encoding, encoding_errors=encoding_errors, **kwargs) 71 72 if not SERIAL_MODULE_FOUND: 73 raise RuntimeError('Serial port functionality not available. Please ' 74 'install Python module pyserial.') 75 try: 76 self.serial = serial.Serial(port=port, baudrate=baudrate, 77 bytesize=bytesize, parity=parity, 78 stopbits=stopbits, timeout=timeout, 79 xonxoff=xonxoff, rtscts=rtscts, 80 write_timeout=write_timeout, dsrdtr=dsrdtr, 81 inter_byte_timeout=inter_byte_timeout, 82 exclusive=exclusive) 83 except (serial.SerialException, serial.serialutil.SerialException) as e: 84 logging.fatal('Failed to open serial port %s: %s', port, e) 85 raise 86 87 self.max_bytes = max_bytes 88 self.encoding = encoding 89 self.allow_empty = allow_empty 90 self.encoding_errors = encoding_errors 91 92 # 'eol' comes in as a (probably escaped) string. We need to 93 # unescape it, which means converting to bytes and back. 94 # 95 # NOTE: This block is different from SerialWriter because we use 96 # readline() in here, which already looks for trailing '\n' and 97 # handles encoding itself. 98 # 99 if eol is not None and self.encoding: 100 eol = self._encode_str(eol, unescape=True) 101 self.eol = eol
If max_bytes is specified on initialization, read up to that many bytes when read() is called. If eol is not specified, read() will read up to the first newline it receives. In both cases, if timeout is specified, it will return after timeout with as many bytes as it has succeeded in reading.
By default, the SerialReader will read until it encounters a newline character. This behavior may be overwritten by specifying
max_bytes - if specified, and write_timeout is None, read this many bytes per record. If write_timeout is not None, it may return fewer bytes.
eol - if specified, read up until encountering the specified eol
By default, the SerialReader will assume that records are encoded in UTF-8, and will ignore non unicode characters it encounters. These defaults may be changed by specifying
allow_empty - If True, preserve and return empty records
encoding - 'utf-8' by default. If empty or None, do not attempt any decoding and return raw bytes. Other possible encodings are listed in online documentation here: https://docs.python.org/3/library/codecs.html#standard-encodings
encoding_errors - 'ignore' by default. Other error strategies are 'strict', 'replace', and 'backslashreplace', described here: https://docs.python.org/3/howto/unicode.html#encodings
command line example:
# Read serial port ttyr05 expecting a LF as end of record
logger/listener/listen.py --serial port=/dev/ttyr05,eol='
'
config example:
class: SerialReader
kwargs:
baudrate: 4800
port: /dev/ttyr05
eol:
104 def read(self): 105 try: 106 if self.eol: 107 record = self.serial.read_until(expected=self.eol, size=self.max_bytes) 108 # read_until()'s record includes a trailing 'eol', strip it off 109 # 110 # NOTE: But don't use rstrip which just looks explicitly for 111 # whitespace 112 # 113 record = record.rsplit(self.eol)[0] 114 elif self.max_bytes: 115 # no stripping on this one, just use exactly what we got 116 record = self.serial.read(size=self.max_bytes) 117 else: 118 # readline()'s record includes the trailing '\n', strip it off 119 record = self.serial.readline().rstrip() 120 121 return self._decode_bytes(record, self.allow_empty) 122 123 except KeyboardInterrupt as e: 124 raise e 125 except serial.serialutil.SerialException as e: 126 logging.error(str(e)) 127 return None
read() should return None when there are no more records.