protocol_spy: a new URL hander that wraps a serial port to log traffic
diff --git a/serial/urlhandler/protocol_spy.py b/serial/urlhandler/protocol_spy.py
new file mode 100644
index 0000000..f0e6270
--- /dev/null
+++ b/serial/urlhandler/protocol_spy.py
@@ -0,0 +1,95 @@
+#! python
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# This module implements a special URL handler that wraps an other port,
+# printint the traffic for debugging purposes
+#
+# (C) 2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+# URL format: spy://port[?option[=value][&option[=value]]]
+# options:
+# - dev=X a file or device to write to
+# - color use escape code to colorize output
+# - hex hex encode the output
+#
+# example:
+# redirect output to an other terminal window on Posix (Linux):
+# python -m serial.tools.miniterm spy:///dev/ttyUSB0?dev=/dev/pts/12\&color
+
+import sys
+import serial
+
+try:
+ import urlparse
+except ImportError:
+ import urllib.parse as urlparse
+try:
+ basestring
+except NameError:
+ basestring = str # python 3
+
+class Serial(serial.Serial):
+ """Just inherit the native Serial port implementation and patch the port property."""
+
+ def __init__(self, *args, **kwargs):
+ super(Serial, self).__init__(*args, **kwargs)
+ self.output = sys.stderr
+ self.hexlify = False
+ self.color = False
+ self.rx_color = '\x1b[32m'
+ self.tx_color = '\x1b[31m'
+
+ @serial.Serial.port.setter
+ def port(self, value):
+ if value is not None:
+ serial.Serial.port.__set__(self, self.fromURL(value))
+
+ def fromURL(self, url):
+ """extract host and port from an URL string"""
+ print(url)
+ parts = urlparse.urlsplit(url)
+ if parts.scheme != "spy":
+ raise serial.SerialException('expected a string in the form "spy://port[?option[=value][&option[=value]]]": not starting with spy:// (%r)' % (parts.scheme,))
+ # process options now, directly altering self
+ for option, values in urlparse.parse_qs(parts.query, True).items():
+ if option == 'dev':
+ self.output = open(values[0], 'w')
+ elif option == 'color':
+ self.color = True
+ elif option == 'hex':
+ self.hexlify = True
+ return ''.join([parts.netloc, parts.path])
+
+ def write(self, tx):
+ if self.color:
+ self.output.write(self.tx_color)
+ if self.hexlify:
+ self.output.write(tx.encode('hex'))
+ else:
+ self.output.write(tx)
+ self.output.flush()
+ return super(Serial, self).write(tx)
+
+ def read(self, size=1):
+ rx = super(Serial, self).read(size)
+ if rx:
+ if self.color:
+ self.output.write(self.rx_color)
+ if self.hexlify:
+ self.output.write(rx.encode('hex'))
+ else:
+ self.output.write(rx)
+ self.output.flush()
+ return rx
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+if __name__ == '__main__':
+ s = Serial(None)
+ s.port = 'spy:///dev/ttyS0'
+ print(s)
+