summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Liechti <cliechti@gmx.net>2015-09-06 22:35:24 +0200
committerChris Liechti <cliechti@gmx.net>2015-09-06 22:35:24 +0200
commit65f5ee6f41711b7b67b19afc918b1e2c46808cc3 (patch)
tree4287814601ad76b4b6cd95935872b3bbf0068ad5
parent057908be4a8c1b5a164580a041a7a0195dfdb107 (diff)
downloadpyserial-git-65f5ee6f41711b7b67b19afc918b1e2c46808cc3.tar.gz
add a new reader loop implementation (with threads)
-rw-r--r--serial/threaded/__init__.py124
1 files changed, 124 insertions, 0 deletions
diff --git a/serial/threaded/__init__.py b/serial/threaded/__init__.py
new file mode 100644
index 0000000..c9f9561
--- /dev/null
+++ b/serial/threaded/__init__.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+#
+# Working with thrading and pySerial
+#
+# (C) 2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+"""\
+Support threading with serial ports.
+"""
+import logging
+import threading
+
+
+class Protocol(object):
+ """\
+ Protocol as used by the SerialPortWorker. This base class provides empty
+ implementations of all methods.
+ """
+
+ def connection_made(self, transport):
+ """Called when reader thread is started"""
+
+ def data_received(self, data):
+ """Called with snippets received from the serial port"""
+
+ def connection_lost(self, exc):
+ """\
+ Called when the serial port is closed or the reader loop terminated
+ otherwise.
+ """
+
+
+class SerialPortWorker(threading.Thread):
+ """\
+ Implement a serial port read loop and dispatch to a Protocol instance (like
+ the asyncio.Procotol) but do it with threads.
+
+ Calls to close will close the serial port but its also possible to just stop
+ this thread and continue the serial port instance otherwise.
+ """
+
+ def __init__(self, serial_instance, protocol_factory, use_logging=True):
+ """\
+ Initialize thread.
+ Note that the serial_instance' timeout is set to one second!
+
+ When use_logging, log messages are printed on exceptions.
+ """
+ super(SerialPortWorker, self).__init__()
+ self.daemon = True
+ self.serial = serial_instance
+ self.protocol_factory = protocol_factory
+ self.use_logging = use_logging
+ self.alive = True
+ self.lock = threading.Lock()
+
+ def stop(self):
+ self.alive = False
+ self.join(2)
+
+ def run(self):
+ """Reader loop"""
+ protocol = self.protocol_factory()
+ protocol.connection_made(self)
+ self.serial.timeout = 1
+ while self.alive and self.serial.is_open:
+ try:
+ # read all that is there or wait for one byte
+ data = self.serial.read(self.serial.in_waiting or 1)
+ except serial.SerialException:
+ # probably some IO problem such as disconnected USB serial
+ # adapters -> exit
+ self.alive = False
+ if self.use_logging:
+ logging.exception('Error in %s (thread stops):', self.name)
+ else:
+ if data:
+ # make a separated try-except for user called code
+ try:
+ protocol.data_received(data)
+ except:
+ if self.use_logging:
+ logging.exception('Error in %s (thread continues):', self.name)
+ protocol.connection_lost(None)
+
+ def write(self, data):
+ """Thread safe writing (uses lock)"""
+ with self.lock:
+ self.serial.write(data)
+
+ def close(self):
+ """Close the serial port and exit reader thread (uses lock)"""
+ # use the lock to let other threads finish writing
+ with self.lock:
+ # first stop reading, so that closing can be done on idle port
+ self.stop()
+ self.serial.close()
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+ import serial
+ import sys
+ import time
+
+ class Output(Protocol):
+ def connection_made(self, transport):
+ self.transport = transport
+ sys.stdout.write('port opened: {}\n'.format(transport))
+ transport.serial.rts = False
+ transport.write(b'hello world\n')
+
+ def data_received(self, data):
+ sys.stdout.write('data received: {}\n'.format(repr(data)))
+
+ def connection_lost(self, exc):
+ sys.stdout.write('port closed\n')
+
+ ser = serial.Serial('/dev/ttyUSB0', baudrate=115200, timeout=1)
+ t = SerialPortWorker(ser, Output)
+ t.start()
+ time.sleep(2)
+ t.stop()