Files
garageDoorZigbee/venv/Lib/site-packages/serial/urlhandler/protocol_spy.py
nathan wagner b96310f621 added in micropython external library for const feature.
it's returing valid attribute arrays, that just need converted on z2m
side.

it's also respecting the stop command from the console, which I will
attempt to expose as just a button press.
2022-04-24 12:44:08 -04:00

291 lines
8.9 KiB
Python

#! python
#
# This module implements a special URL handler that wraps an other port,
# print the traffic for debugging purposes. With this, it is possible
# to debug the serial port traffic on every application that uses
# serial_for_url.
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (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
# - raw forward raw bytes instead of hexdump
#
# example:
# redirect output to an other terminal window on Posix (Linux):
# python -m serial.tools.miniterm spy:///dev/ttyUSB0?dev=/dev/pts/14\&color
from __future__ import absolute_import
import sys
import time
import serial
from serial.serialutil import to_bytes
try:
import urlparse
except ImportError:
import urllib.parse as urlparse
def sixteen(data):
"""\
yield tuples of hex and ASCII display in multiples of 16. Includes a
space after 8 bytes and (None, None) after 16 bytes and at the end.
"""
n = 0
for b in serial.iterbytes(data):
yield ('{:02X} '.format(ord(b)), b.decode('ascii') if b' ' <= b < b'\x7f' else '.')
n += 1
if n == 8:
yield (' ', '')
elif n >= 16:
yield (None, None)
n = 0
if n > 0:
while n < 16:
n += 1
if n == 8:
yield (' ', '')
yield (' ', ' ')
yield (None, None)
def hexdump(data):
"""yield lines with hexdump of data"""
values = []
ascii = []
offset = 0
for h, a in sixteen(data):
if h is None:
yield (offset, ' '.join([''.join(values), ''.join(ascii)]))
del values[:]
del ascii[:]
offset += 0x10
else:
values.append(h)
ascii.append(a)
class FormatRaw(object):
"""Forward only RX and TX data to output."""
def __init__(self, output, color):
self.output = output
self.color = color
self.rx_color = '\x1b[32m'
self.tx_color = '\x1b[31m'
def rx(self, data):
"""show received data"""
if self.color:
self.output.write(self.rx_color)
self.output.write(data)
self.output.flush()
def tx(self, data):
"""show transmitted data"""
if self.color:
self.output.write(self.tx_color)
self.output.write(data)
self.output.flush()
def control(self, name, value):
"""(do not) show control calls"""
pass
class FormatHexdump(object):
"""\
Create a hex dump of RX ad TX data, show when control lines are read or
written.
output example::
000000.000 Q-RX flushInput
000002.469 RTS inactive
000002.773 RTS active
000003.001 TX 48 45 4C 4C 4F HELLO
000003.102 RX 48 45 4C 4C 4F HELLO
"""
def __init__(self, output, color):
self.start_time = time.time()
self.output = output
self.color = color
self.rx_color = '\x1b[32m'
self.tx_color = '\x1b[31m'
self.control_color = '\x1b[37m'
def write_line(self, timestamp, label, value, value2=''):
self.output.write('{:010.3f} {:4} {}{}\n'.format(timestamp, label, value, value2))
self.output.flush()
def rx(self, data):
"""show received data as hex dump"""
if self.color:
self.output.write(self.rx_color)
if data:
for offset, row in hexdump(data):
self.write_line(time.time() - self.start_time, 'RX', '{:04X} '.format(offset), row)
else:
self.write_line(time.time() - self.start_time, 'RX', '<empty>')
def tx(self, data):
"""show transmitted data as hex dump"""
if self.color:
self.output.write(self.tx_color)
for offset, row in hexdump(data):
self.write_line(time.time() - self.start_time, 'TX', '{:04X} '.format(offset), row)
def control(self, name, value):
"""show control calls"""
if self.color:
self.output.write(self.control_color)
self.write_line(time.time() - self.start_time, name, value)
class Serial(serial.Serial):
"""\
Inherit the native Serial port implementation and wrap all the methods and
attributes.
"""
# pylint: disable=no-member
def __init__(self, *args, **kwargs):
super(Serial, self).__init__(*args, **kwargs)
self.formatter = None
self.show_all = False
@serial.Serial.port.setter
def port(self, value):
if value is not None:
serial.Serial.port.__set__(self, self.from_url(value))
def from_url(self, url):
"""extract host and port from an URL string"""
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})'.format(parts.scheme))
# process options now, directly altering self
formatter = FormatHexdump
color = False
output = sys.stderr
try:
for option, values in urlparse.parse_qs(parts.query, True).items():
if option == 'file':
output = open(values[0], 'w')
elif option == 'color':
color = True
elif option == 'raw':
formatter = FormatRaw
elif option == 'all':
self.show_all = True
else:
raise ValueError('unknown option: {!r}'.format(option))
except ValueError as e:
raise serial.SerialException(
'expected a string in the form '
'"spy://port[?option[=value][&option[=value]]]": {}'.format(e))
self.formatter = formatter(output, color)
return ''.join([parts.netloc, parts.path])
def write(self, tx):
tx = to_bytes(tx)
self.formatter.tx(tx)
return super(Serial, self).write(tx)
def read(self, size=1):
rx = super(Serial, self).read(size)
if rx or self.show_all:
self.formatter.rx(rx)
return rx
if hasattr(serial.Serial, 'cancel_read'):
def cancel_read(self):
self.formatter.control('Q-RX', 'cancel_read')
super(Serial, self).cancel_read()
if hasattr(serial.Serial, 'cancel_write'):
def cancel_write(self):
self.formatter.control('Q-TX', 'cancel_write')
super(Serial, self).cancel_write()
@property
def in_waiting(self):
n = super(Serial, self).in_waiting
if self.show_all:
self.formatter.control('Q-RX', 'in_waiting -> {}'.format(n))
return n
def flush(self):
self.formatter.control('Q-TX', 'flush')
super(Serial, self).flush()
def reset_input_buffer(self):
self.formatter.control('Q-RX', 'reset_input_buffer')
super(Serial, self).reset_input_buffer()
def reset_output_buffer(self):
self.formatter.control('Q-TX', 'reset_output_buffer')
super(Serial, self).reset_output_buffer()
def send_break(self, duration=0.25):
self.formatter.control('BRK', 'send_break {}s'.format(duration))
super(Serial, self).send_break(duration)
@serial.Serial.break_condition.setter
def break_condition(self, level):
self.formatter.control('BRK', 'active' if level else 'inactive')
serial.Serial.break_condition.__set__(self, level)
@serial.Serial.rts.setter
def rts(self, level):
self.formatter.control('RTS', 'active' if level else 'inactive')
serial.Serial.rts.__set__(self, level)
@serial.Serial.dtr.setter
def dtr(self, level):
self.formatter.control('DTR', 'active' if level else 'inactive')
serial.Serial.dtr.__set__(self, level)
@serial.Serial.cts.getter
def cts(self):
level = super(Serial, self).cts
self.formatter.control('CTS', 'active' if level else 'inactive')
return level
@serial.Serial.dsr.getter
def dsr(self):
level = super(Serial, self).dsr
self.formatter.control('DSR', 'active' if level else 'inactive')
return level
@serial.Serial.ri.getter
def ri(self):
level = super(Serial, self).ri
self.formatter.control('RI', 'active' if level else 'inactive')
return level
@serial.Serial.cd.getter
def cd(self):
level = super(Serial, self).cd
self.formatter.control('CD', 'active' if level else 'inactive')
return level
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if __name__ == '__main__':
ser = Serial(None)
ser.port = 'spy:///dev/ttyS0'
print(ser)