sensors module now in early working stage
This commit is contained in:
BIN
backend/__pycache__/sensors.cpython-36.pyc
Normal file
BIN
backend/__pycache__/sensors.cpython-36.pyc
Normal file
Binary file not shown.
@@ -6,20 +6,84 @@ import serial as ser
|
||||
import pandas as pd
|
||||
from threading import Timer
|
||||
from sys import exit as ex
|
||||
import time
|
||||
|
||||
class Spectrometer:
|
||||
def schedule(self, delay):
|
||||
self.timerObject=Timer(delay, retrieveData)
|
||||
def initializeSensor(self):
|
||||
'''confirm the sensor is responding and proceed with spectrometer initialization'''
|
||||
try:
|
||||
rstring='undefined' # just need it set to a value
|
||||
self.serialObject.write(b'AT\n')
|
||||
rstring=self.serialObject.readline().decode()
|
||||
if rstring == 'undefined':
|
||||
raise Exception #sensor didn't respond
|
||||
if rstring == 'OK':
|
||||
pass #handshake passed
|
||||
if rstring == 'ERROR':
|
||||
raise Exception #sensor is in error state
|
||||
except:
|
||||
print('An exception ocurred when performing spectrometer handshake')
|
||||
ex(1)
|
||||
self.setParameters()
|
||||
|
||||
def __init__(self, path='/dev/ttyAMA0', baudrate=115200, timeout=1, refreshrate=1):
|
||||
def setParameters(self, parameters={}):
|
||||
'''applies the parameters like LED light and gain to the spectrometer'''
|
||||
# try:
|
||||
if 'it_time' in parameters:
|
||||
it_time = int(parameters['it_time'])
|
||||
if it_time <=0 :
|
||||
it_time = 1
|
||||
self.serialObject.write('ATINTTIME={}\n'.format(string(it_time)).encode())
|
||||
self.serialObject.readline()
|
||||
|
||||
if 'gain' in parameters:
|
||||
gain = int(parameters['gain'])
|
||||
if gain < 0 or gain > 3:
|
||||
gain = 1
|
||||
self.serialObject.write('ATGAIN={}\n'.format(gain).encode())
|
||||
self.serialObject.readline()
|
||||
|
||||
if 'led' in parameters:
|
||||
led = bool(parameters['led'])
|
||||
if led:
|
||||
led=1
|
||||
else:
|
||||
led=0
|
||||
self.serialObject.write('ATLED3={}\n'.format(led).encode())
|
||||
self.serialObject.readline()
|
||||
# except:
|
||||
# print('An exception occured during spectrometer initialization')
|
||||
# ex(1)
|
||||
|
||||
def startDataCollection(self):
|
||||
try:
|
||||
self.serialObject.write(b'ATCDATA\n')
|
||||
rawresp = self.serialObject.readline().decode()
|
||||
except:
|
||||
print('An exception occurred when polling for spectrometer data')
|
||||
ex(1)
|
||||
else:
|
||||
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF']
|
||||
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
|
||||
response = pd.Series([float(i)/35.0 for i in rawresp[:-3].split(',')], index=responseorder)
|
||||
self.data = pd.DataFrame(response, index=realorder, columns = ['uW/cm^2'])
|
||||
self.schedule(self.rrate)
|
||||
|
||||
def schedule(self, refresh):
|
||||
self.timerObject = Timer(refresh, self.startDataCollection)
|
||||
self.timerObject.start()
|
||||
|
||||
|
||||
def __init__(self, path='/dev/ttyUSB0', baudrate=115200, tout=1, rrate=1, params={}):
|
||||
self.path=path
|
||||
self.baudrate=baudrate
|
||||
self.timeout=1
|
||||
self.rrate=rrate
|
||||
try:
|
||||
self.serialObject = ser.Serial(path, baudrate, timeout)
|
||||
self.serialObject = ser.Serial(path, baudrate, timeout=tout)
|
||||
except:
|
||||
print('An exception occured when opening the serial port at {}'.format(path))
|
||||
ex(1)
|
||||
else:
|
||||
initialiseSensor()
|
||||
startDataCollection()
|
||||
self.initializeSensor()
|
||||
self.startDataCollection()
|
||||
|
||||
BIN
backend/venv/bin/__pycache__/miniterm.cpython-36.pyc
Normal file
BIN
backend/venv/bin/__pycache__/miniterm.cpython-36.pyc
Normal file
Binary file not shown.
976
backend/venv/bin/miniterm.py
Executable file
976
backend/venv/bin/miniterm.py
Executable file
@@ -0,0 +1,976 @@
|
||||
#!/root/projekti/TeraHz/backend/venv/bin/python3
|
||||
#
|
||||
# Very simple serial terminal
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import serial
|
||||
from serial.tools.list_ports import comports
|
||||
from serial.tools import hexlify_codec
|
||||
|
||||
# pylint: disable=wrong-import-order,wrong-import-position
|
||||
|
||||
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
# pylint: disable=redefined-builtin,invalid-name
|
||||
raw_input = input # in python3 it's "raw"
|
||||
unichr = chr
|
||||
|
||||
|
||||
def key_description(character):
|
||||
"""generate a readable description for a key"""
|
||||
ascii_code = ord(character)
|
||||
if ascii_code < 32:
|
||||
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
|
||||
else:
|
||||
return repr(character)
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
class ConsoleBase(object):
|
||||
"""OS abstraction for console (input/output codec, no echo)"""
|
||||
|
||||
def __init__(self):
|
||||
if sys.version_info >= (3, 0):
|
||||
self.byte_output = sys.stdout.buffer
|
||||
else:
|
||||
self.byte_output = sys.stdout
|
||||
self.output = sys.stdout
|
||||
|
||||
def setup(self):
|
||||
"""Set console to read single characters, no echo"""
|
||||
|
||||
def cleanup(self):
|
||||
"""Restore default console settings"""
|
||||
|
||||
def getkey(self):
|
||||
"""Read a single key from the console"""
|
||||
return None
|
||||
|
||||
def write_bytes(self, byte_string):
|
||||
"""Write bytes (already encoded)"""
|
||||
self.byte_output.write(byte_string)
|
||||
self.byte_output.flush()
|
||||
|
||||
def write(self, text):
|
||||
"""Write string"""
|
||||
self.output.write(text)
|
||||
self.output.flush()
|
||||
|
||||
def cancel(self):
|
||||
"""Cancel getkey operation"""
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# context manager:
|
||||
# switch terminal temporary to normal mode (e.g. to get user input)
|
||||
|
||||
def __enter__(self):
|
||||
self.cleanup()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.setup()
|
||||
|
||||
|
||||
if os.name == 'nt': # noqa
|
||||
import msvcrt
|
||||
import ctypes
|
||||
|
||||
class Out(object):
|
||||
"""file-like wrapper that uses os.write"""
|
||||
|
||||
def __init__(self, fd):
|
||||
self.fd = fd
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def write(self, s):
|
||||
os.write(self.fd, s)
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
|
||||
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
|
||||
ctypes.windll.kernel32.SetConsoleCP(65001)
|
||||
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
|
||||
# the change of the code page is not propagated to Python, manually fix it
|
||||
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
|
||||
sys.stdout = self.output
|
||||
self.output.encoding = 'UTF-8' # needed for input
|
||||
|
||||
def __del__(self):
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
|
||||
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
|
||||
|
||||
def getkey(self):
|
||||
while True:
|
||||
z = msvcrt.getwch()
|
||||
if z == unichr(13):
|
||||
return unichr(10)
|
||||
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
|
||||
msvcrt.getwch()
|
||||
else:
|
||||
return z
|
||||
|
||||
def cancel(self):
|
||||
# CancelIo, CancelSynchronousIo do not seem to work when using
|
||||
# getwch, so instead, send a key to the window with the console
|
||||
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
|
||||
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
|
||||
|
||||
elif os.name == 'posix':
|
||||
import atexit
|
||||
import termios
|
||||
import fcntl
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self.fd = sys.stdin.fileno()
|
||||
self.old = termios.tcgetattr(self.fd)
|
||||
atexit.register(self.cleanup)
|
||||
if sys.version_info < (3, 0):
|
||||
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
|
||||
else:
|
||||
self.enc_stdin = sys.stdin
|
||||
|
||||
def setup(self):
|
||||
new = termios.tcgetattr(self.fd)
|
||||
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
|
||||
new[6][termios.VMIN] = 1
|
||||
new[6][termios.VTIME] = 0
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, new)
|
||||
|
||||
def getkey(self):
|
||||
c = self.enc_stdin.read(1)
|
||||
if c == unichr(0x7f):
|
||||
c = unichr(8) # map the BS key (which yields DEL) to backspace
|
||||
return c
|
||||
|
||||
def cancel(self):
|
||||
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
|
||||
|
||||
def cleanup(self):
|
||||
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
class Transform(object):
|
||||
"""do-nothing: forward all data unchanged"""
|
||||
def rx(self, text):
|
||||
"""text received from serial port"""
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
"""text to be sent to serial port"""
|
||||
return text
|
||||
|
||||
def echo(self, text):
|
||||
"""text to be sent but displayed on console"""
|
||||
return text
|
||||
|
||||
|
||||
class CRLF(Transform):
|
||||
"""ENTER sends CR+LF"""
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r\n')
|
||||
|
||||
|
||||
class CR(Transform):
|
||||
"""ENTER sends CR"""
|
||||
|
||||
def rx(self, text):
|
||||
return text.replace('\r', '\n')
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r')
|
||||
|
||||
|
||||
class LF(Transform):
|
||||
"""ENTER sends LF"""
|
||||
|
||||
|
||||
class NoTerminal(Transform):
|
||||
"""remove typical terminal control codes from input"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
def rx(self, text):
|
||||
return text.translate(self.REPLACEMENT_MAP)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class NoControls(NoTerminal):
|
||||
"""Remove all control codes, incl. CR+LF"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x20: 0x2423, # visual space
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
|
||||
class Printable(Transform):
|
||||
"""Show decimal code for all non-ASCII characters and replace most control codes"""
|
||||
|
||||
def rx(self, text):
|
||||
r = []
|
||||
for c in text:
|
||||
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
|
||||
r.append(c)
|
||||
elif c < ' ':
|
||||
r.append(unichr(0x2400 + ord(c)))
|
||||
else:
|
||||
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
|
||||
r.append(' ')
|
||||
return ''.join(r)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class Colorize(Transform):
|
||||
"""Apply different colors for received and echo"""
|
||||
|
||||
def __init__(self):
|
||||
# XXX make it configurable, use colorama?
|
||||
self.input_color = '\x1b[37m'
|
||||
self.echo_color = '\x1b[31m'
|
||||
|
||||
def rx(self, text):
|
||||
return self.input_color + text
|
||||
|
||||
def echo(self, text):
|
||||
return self.echo_color + text
|
||||
|
||||
|
||||
class DebugIO(Transform):
|
||||
"""Print what is sent and received"""
|
||||
|
||||
def rx(self, text):
|
||||
sys.stderr.write(' [RX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
sys.stderr.write(' [TX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
|
||||
# other ideas:
|
||||
# - add date/time for each newline
|
||||
# - insert newline after: a) timeout b) packet end character
|
||||
|
||||
EOL_TRANSFORMATIONS = {
|
||||
'crlf': CRLF,
|
||||
'cr': CR,
|
||||
'lf': LF,
|
||||
}
|
||||
|
||||
TRANSFORMATIONS = {
|
||||
'direct': Transform, # no transformation
|
||||
'default': NoTerminal,
|
||||
'nocontrol': NoControls,
|
||||
'printable': Printable,
|
||||
'colorize': Colorize,
|
||||
'debug': DebugIO,
|
||||
}
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def ask_for_port():
|
||||
"""\
|
||||
Show a list of ports and ask the user for a choice. To make selection
|
||||
easier on systems with long device names, also allow the input of an
|
||||
index.
|
||||
"""
|
||||
sys.stderr.write('\n--- Available ports:\n')
|
||||
ports = []
|
||||
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
|
||||
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
|
||||
ports.append(port)
|
||||
while True:
|
||||
port = raw_input('--- Enter port index or full name: ')
|
||||
try:
|
||||
index = int(port) - 1
|
||||
if not 0 <= index < len(ports):
|
||||
sys.stderr.write('--- Invalid index!\n')
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
port = ports[index]
|
||||
return port
|
||||
|
||||
|
||||
class Miniterm(object):
|
||||
"""\
|
||||
Terminal application. Copy data from serial port to console and vice versa.
|
||||
Handle special keys from the console to show menu etc.
|
||||
"""
|
||||
|
||||
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
|
||||
self.console = Console()
|
||||
self.serial = serial_instance
|
||||
self.echo = echo
|
||||
self.raw = False
|
||||
self.input_encoding = 'UTF-8'
|
||||
self.output_encoding = 'UTF-8'
|
||||
self.eol = eol
|
||||
self.filters = filters
|
||||
self.update_transformations()
|
||||
self.exit_character = 0x1d # GS/CTRL+]
|
||||
self.menu_character = 0x14 # Menu: CTRL+T
|
||||
self.alive = None
|
||||
self._reader_alive = None
|
||||
self.receiver_thread = None
|
||||
self.rx_decoder = None
|
||||
self.tx_decoder = None
|
||||
|
||||
def _start_reader(self):
|
||||
"""Start reader thread"""
|
||||
self._reader_alive = True
|
||||
# start serial->console thread
|
||||
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
|
||||
self.receiver_thread.daemon = True
|
||||
self.receiver_thread.start()
|
||||
|
||||
def _stop_reader(self):
|
||||
"""Stop reader thread only, wait for clean exit of thread"""
|
||||
self._reader_alive = False
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def start(self):
|
||||
"""start worker threads"""
|
||||
self.alive = True
|
||||
self._start_reader()
|
||||
# enter console->serial loop
|
||||
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
|
||||
self.transmitter_thread.daemon = True
|
||||
self.transmitter_thread.start()
|
||||
self.console.setup()
|
||||
|
||||
def stop(self):
|
||||
"""set flag to stop worker threads"""
|
||||
self.alive = False
|
||||
|
||||
def join(self, transmit_only=False):
|
||||
"""wait for worker threads to terminate"""
|
||||
self.transmitter_thread.join()
|
||||
if not transmit_only:
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def close(self):
|
||||
self.serial.close()
|
||||
|
||||
def update_transformations(self):
|
||||
"""take list of transformation classes and instantiate them for rx and tx"""
|
||||
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
|
||||
for f in self.filters]
|
||||
self.tx_transformations = [t() for t in transformations]
|
||||
self.rx_transformations = list(reversed(self.tx_transformations))
|
||||
|
||||
def set_rx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for received data"""
|
||||
self.input_encoding = encoding
|
||||
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
|
||||
|
||||
def set_tx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for transmitted data"""
|
||||
self.output_encoding = encoding
|
||||
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
|
||||
|
||||
def dump_port_settings(self):
|
||||
"""Write current settings to sys.stderr"""
|
||||
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
|
||||
p=self.serial))
|
||||
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
|
||||
('active' if self.serial.rts else 'inactive'),
|
||||
('active' if self.serial.dtr else 'inactive'),
|
||||
('active' if self.serial.break_condition else 'inactive')))
|
||||
try:
|
||||
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
|
||||
('active' if self.serial.cts else 'inactive'),
|
||||
('active' if self.serial.dsr else 'inactive'),
|
||||
('active' if self.serial.ri else 'inactive'),
|
||||
('active' if self.serial.cd else 'inactive')))
|
||||
except serial.SerialException:
|
||||
# on RFC 2217 ports, it can happen if no modem state notification was
|
||||
# yet received. ignore this error.
|
||||
pass
|
||||
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
|
||||
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def reader(self):
|
||||
"""loop and copy serial->console"""
|
||||
try:
|
||||
while self.alive and self._reader_alive:
|
||||
# read all that is there or wait for one byte
|
||||
data = self.serial.read(self.serial.in_waiting or 1)
|
||||
if data:
|
||||
if self.raw:
|
||||
self.console.write_bytes(data)
|
||||
else:
|
||||
text = self.rx_decoder.decode(data)
|
||||
for transformation in self.rx_transformations:
|
||||
text = transformation.rx(text)
|
||||
self.console.write(text)
|
||||
except serial.SerialException:
|
||||
self.alive = False
|
||||
self.console.cancel()
|
||||
raise # XXX handle instead of re-raise?
|
||||
|
||||
def writer(self):
|
||||
"""\
|
||||
Loop and copy console->serial until self.exit_character character is
|
||||
found. When self.menu_character is found, interpret the next key
|
||||
locally.
|
||||
"""
|
||||
menu_active = False
|
||||
try:
|
||||
while self.alive:
|
||||
try:
|
||||
c = self.console.getkey()
|
||||
except KeyboardInterrupt:
|
||||
c = '\x03'
|
||||
if not self.alive:
|
||||
break
|
||||
if menu_active:
|
||||
self.handle_menu_key(c)
|
||||
menu_active = False
|
||||
elif c == self.menu_character:
|
||||
menu_active = True # next char will be for menu
|
||||
elif c == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
else:
|
||||
#~ if self.raw:
|
||||
text = c
|
||||
for transformation in self.tx_transformations:
|
||||
text = transformation.tx(text)
|
||||
self.serial.write(self.tx_encoder.encode(text))
|
||||
if self.echo:
|
||||
echo_text = c
|
||||
for transformation in self.tx_transformations:
|
||||
echo_text = transformation.echo(echo_text)
|
||||
self.console.write(echo_text)
|
||||
except:
|
||||
self.alive = False
|
||||
raise
|
||||
|
||||
def handle_menu_key(self, c):
|
||||
"""Implement a simple menu / settings"""
|
||||
if c == self.menu_character or c == self.exit_character:
|
||||
# Menu/exit character again -> send itself
|
||||
self.serial.write(self.tx_encoder.encode(c))
|
||||
if self.echo:
|
||||
self.console.write(c)
|
||||
elif c == '\x15': # CTRL+U -> upload file
|
||||
self.upload_file()
|
||||
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
|
||||
sys.stderr.write(self.get_help_text())
|
||||
elif c == '\x12': # CTRL+R -> Toggle RTS
|
||||
self.serial.rts = not self.serial.rts
|
||||
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
|
||||
elif c == '\x04': # CTRL+D -> Toggle DTR
|
||||
self.serial.dtr = not self.serial.dtr
|
||||
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
|
||||
elif c == '\x02': # CTRL+B -> toggle BREAK condition
|
||||
self.serial.break_condition = not self.serial.break_condition
|
||||
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
|
||||
elif c == '\x05': # CTRL+E -> toggle local echo
|
||||
self.echo = not self.echo
|
||||
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
|
||||
elif c == '\x06': # CTRL+F -> edit filters
|
||||
self.change_filter()
|
||||
elif c == '\x0c': # CTRL+L -> EOL mode
|
||||
modes = list(EOL_TRANSFORMATIONS) # keys
|
||||
eol = modes.index(self.eol) + 1
|
||||
if eol >= len(modes):
|
||||
eol = 0
|
||||
self.eol = modes[eol]
|
||||
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
|
||||
self.update_transformations()
|
||||
elif c == '\x01': # CTRL+A -> set encoding
|
||||
self.change_encoding()
|
||||
elif c == '\x09': # CTRL+I -> info
|
||||
self.dump_port_settings()
|
||||
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
|
||||
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
|
||||
elif c in 'pP': # P -> change port
|
||||
self.change_port()
|
||||
elif c in 'sS': # S -> suspend / open port temporarily
|
||||
self.suspend_port()
|
||||
elif c in 'bB': # B -> change baudrate
|
||||
self.change_baudrate()
|
||||
elif c == '8': # 8 -> change to 8 bits
|
||||
self.serial.bytesize = serial.EIGHTBITS
|
||||
self.dump_port_settings()
|
||||
elif c == '7': # 7 -> change to 8 bits
|
||||
self.serial.bytesize = serial.SEVENBITS
|
||||
self.dump_port_settings()
|
||||
elif c in 'eE': # E -> change to even parity
|
||||
self.serial.parity = serial.PARITY_EVEN
|
||||
self.dump_port_settings()
|
||||
elif c in 'oO': # O -> change to odd parity
|
||||
self.serial.parity = serial.PARITY_ODD
|
||||
self.dump_port_settings()
|
||||
elif c in 'mM': # M -> change to mark parity
|
||||
self.serial.parity = serial.PARITY_MARK
|
||||
self.dump_port_settings()
|
||||
elif c in 'sS': # S -> change to space parity
|
||||
self.serial.parity = serial.PARITY_SPACE
|
||||
self.dump_port_settings()
|
||||
elif c in 'nN': # N -> change to no parity
|
||||
self.serial.parity = serial.PARITY_NONE
|
||||
self.dump_port_settings()
|
||||
elif c == '1': # 1 -> change to 1 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE
|
||||
self.dump_port_settings()
|
||||
elif c == '2': # 2 -> change to 2 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_TWO
|
||||
self.dump_port_settings()
|
||||
elif c == '3': # 3 -> change to 1.5 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
|
||||
self.dump_port_settings()
|
||||
elif c in 'xX': # X -> change software flow control
|
||||
self.serial.xonxoff = (c == 'X')
|
||||
self.dump_port_settings()
|
||||
elif c in 'rR': # R -> change hardware flow control
|
||||
self.serial.rtscts = (c == 'R')
|
||||
self.dump_port_settings()
|
||||
else:
|
||||
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
|
||||
|
||||
def upload_file(self):
|
||||
"""Ask user for filenname and send its contents"""
|
||||
sys.stderr.write('\n--- File to upload: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
filename = sys.stdin.readline().rstrip('\r\n')
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
|
||||
while True:
|
||||
block = f.read(1024)
|
||||
if not block:
|
||||
break
|
||||
self.serial.write(block)
|
||||
# Wait for output buffer to drain.
|
||||
self.serial.flush()
|
||||
sys.stderr.write('.') # Progress indicator.
|
||||
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
|
||||
except IOError as e:
|
||||
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
|
||||
|
||||
def change_filter(self):
|
||||
"""change the i/o transformations"""
|
||||
sys.stderr.write('\n--- Available Filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'--- {:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
|
||||
with self.console:
|
||||
new_filters = sys.stdin.readline().lower().split()
|
||||
if new_filters:
|
||||
for f in new_filters:
|
||||
if f not in TRANSFORMATIONS:
|
||||
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
|
||||
break
|
||||
else:
|
||||
self.filters = new_filters
|
||||
self.update_transformations()
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def change_encoding(self):
|
||||
"""change encoding on the serial port"""
|
||||
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
|
||||
with self.console:
|
||||
new_encoding = sys.stdin.readline().strip()
|
||||
if new_encoding:
|
||||
try:
|
||||
codecs.lookup(new_encoding)
|
||||
except LookupError:
|
||||
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
|
||||
else:
|
||||
self.set_rx_encoding(new_encoding)
|
||||
self.set_tx_encoding(new_encoding)
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
|
||||
def change_baudrate(self):
|
||||
"""change the baudrate"""
|
||||
sys.stderr.write('\n--- Baudrate: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
backup = self.serial.baudrate
|
||||
try:
|
||||
self.serial.baudrate = int(sys.stdin.readline().strip())
|
||||
except ValueError as e:
|
||||
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
|
||||
self.serial.baudrate = backup
|
||||
else:
|
||||
self.dump_port_settings()
|
||||
|
||||
def change_port(self):
|
||||
"""Have a conversation with the user to change the serial port"""
|
||||
with self.console:
|
||||
try:
|
||||
port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
port = None
|
||||
if port and port != self.serial.port:
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
# save settings
|
||||
settings = self.serial.getSettingsDict()
|
||||
try:
|
||||
new_serial = serial.serial_for_url(port, do_not_open=True)
|
||||
# restore settings and open
|
||||
new_serial.applySettingsDict(settings)
|
||||
new_serial.rts = self.serial.rts
|
||||
new_serial.dtr = self.serial.dtr
|
||||
new_serial.open()
|
||||
new_serial.break_condition = self.serial.break_condition
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
|
||||
new_serial.close()
|
||||
else:
|
||||
self.serial.close()
|
||||
self.serial = new_serial
|
||||
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
|
||||
def suspend_port(self):
|
||||
"""\
|
||||
open port temporarily, allow reconnect, exit and port change to get
|
||||
out of the loop
|
||||
"""
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
self.serial.close()
|
||||
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
|
||||
do_change_port = False
|
||||
while not self.serial.is_open:
|
||||
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
|
||||
exit=key_description(self.exit_character)))
|
||||
k = self.console.getkey()
|
||||
if k == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
elif k in 'pP':
|
||||
do_change_port = True
|
||||
break
|
||||
try:
|
||||
self.serial.open()
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
|
||||
if do_change_port:
|
||||
self.change_port()
|
||||
else:
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
|
||||
|
||||
def get_help_text(self):
|
||||
"""return the help text"""
|
||||
# help text, starts with blank line!
|
||||
return """
|
||||
--- pySerial ({version}) - miniterm - help
|
||||
---
|
||||
--- {exit:8} Exit program
|
||||
--- {menu:8} Menu escape key, followed by:
|
||||
--- Menu keys:
|
||||
--- {menu:7} Send the menu character itself to remote
|
||||
--- {exit:7} Send the exit character itself to remote
|
||||
--- {info:7} Show info
|
||||
--- {upload:7} Upload file (prompt will be shown)
|
||||
--- {repr:7} encoding
|
||||
--- {filter:7} edit filters
|
||||
--- Toggles:
|
||||
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
|
||||
--- {echo:7} echo {eol:7} EOL
|
||||
---
|
||||
--- Port settings ({menu} followed by the following):
|
||||
--- p change port
|
||||
--- 7 8 set data bits
|
||||
--- N E O S M change parity (None, Even, Odd, Space, Mark)
|
||||
--- 1 2 3 set stop bits (1, 2, 1.5)
|
||||
--- b change baud rate
|
||||
--- x X disable/enable software flow control
|
||||
--- r R disable/enable hardware flow control
|
||||
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
|
||||
exit=key_description(self.exit_character),
|
||||
menu=key_description(self.menu_character),
|
||||
rts=key_description('\x12'),
|
||||
dtr=key_description('\x04'),
|
||||
brk=key_description('\x02'),
|
||||
echo=key_description('\x05'),
|
||||
info=key_description('\x09'),
|
||||
upload=key_description('\x15'),
|
||||
repr=key_description('\x01'),
|
||||
filter=key_description('\x06'),
|
||||
eol=key_description('\x0c'))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# default args can be used to override when calling main() from an other script
|
||||
# e.g to create a miniterm-my-device.py
|
||||
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
|
||||
"""Command line tool, entry point"""
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Miniterm - A simple terminal program for the serial port.")
|
||||
|
||||
parser.add_argument(
|
||||
"port",
|
||||
nargs='?',
|
||||
help="serial port name ('-' to show port list)",
|
||||
default=default_port)
|
||||
|
||||
parser.add_argument(
|
||||
"baudrate",
|
||||
nargs='?',
|
||||
type=int,
|
||||
help="set baud rate, default: %(default)s",
|
||||
default=default_baudrate)
|
||||
|
||||
group = parser.add_argument_group("port settings")
|
||||
|
||||
group.add_argument(
|
||||
"--parity",
|
||||
choices=['N', 'E', 'O', 'S', 'M'],
|
||||
type=lambda c: c.upper(),
|
||||
help="set parity, one of {N E O S M}, default: N",
|
||||
default='N')
|
||||
|
||||
group.add_argument(
|
||||
"--rtscts",
|
||||
action="store_true",
|
||||
help="enable RTS/CTS flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--xonxoff",
|
||||
action="store_true",
|
||||
help="enable software flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--rts",
|
||||
type=int,
|
||||
help="set initial RTS line state (possible values: 0, 1)",
|
||||
default=default_rts)
|
||||
|
||||
group.add_argument(
|
||||
"--dtr",
|
||||
type=int,
|
||||
help="set initial DTR line state (possible values: 0, 1)",
|
||||
default=default_dtr)
|
||||
|
||||
group.add_argument(
|
||||
"--ask",
|
||||
action="store_true",
|
||||
help="ask again for port when open fails",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("data handling")
|
||||
|
||||
group.add_argument(
|
||||
"-e", "--echo",
|
||||
action="store_true",
|
||||
help="enable local echo (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--encoding",
|
||||
dest="serial_port_encoding",
|
||||
metavar="CODEC",
|
||||
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
|
||||
default='UTF-8')
|
||||
|
||||
group.add_argument(
|
||||
"-f", "--filter",
|
||||
action="append",
|
||||
metavar="NAME",
|
||||
help="add text transformation",
|
||||
default=[])
|
||||
|
||||
group.add_argument(
|
||||
"--eol",
|
||||
choices=['CR', 'LF', 'CRLF'],
|
||||
type=lambda c: c.upper(),
|
||||
help="end of line mode",
|
||||
default='CRLF')
|
||||
|
||||
group.add_argument(
|
||||
"--raw",
|
||||
action="store_true",
|
||||
help="Do no apply any encodings/transformations",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("hotkeys")
|
||||
|
||||
group.add_argument(
|
||||
"--exit-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode of special character that is used to exit the application, default: %(default)s",
|
||||
default=0x1d) # GS/CTRL+]
|
||||
|
||||
group.add_argument(
|
||||
"--menu-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
|
||||
default=0x14) # Menu: CTRL+T
|
||||
|
||||
group = parser.add_argument_group("diagnostics")
|
||||
|
||||
group.add_argument(
|
||||
"-q", "--quiet",
|
||||
action="store_true",
|
||||
help="suppress non-error messages",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--develop",
|
||||
action="store_true",
|
||||
help="show Python traceback on error",
|
||||
default=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.menu_char == args.exit_char:
|
||||
parser.error('--exit-char can not be the same as --menu-char')
|
||||
|
||||
if args.filter:
|
||||
if 'help' in args.filter:
|
||||
sys.stderr.write('Available filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'{:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n')
|
||||
sys.exit(1)
|
||||
filters = args.filter
|
||||
else:
|
||||
filters = ['default']
|
||||
|
||||
while True:
|
||||
# no port given on command line -> ask user now
|
||||
if args.port is None or args.port == '-':
|
||||
try:
|
||||
args.port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write('\n')
|
||||
parser.error('user aborted and port is not given')
|
||||
else:
|
||||
if not args.port:
|
||||
parser.error('port is not given')
|
||||
try:
|
||||
serial_instance = serial.serial_for_url(
|
||||
args.port,
|
||||
args.baudrate,
|
||||
parity=args.parity,
|
||||
rtscts=args.rtscts,
|
||||
xonxoff=args.xonxoff,
|
||||
do_not_open=True)
|
||||
|
||||
if not hasattr(serial_instance, 'cancel_read'):
|
||||
# enable timeout for alive flag polling if cancel_read is not available
|
||||
serial_instance.timeout = 1
|
||||
|
||||
if args.dtr is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
|
||||
serial_instance.dtr = args.dtr
|
||||
if args.rts is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
|
||||
serial_instance.rts = args.rts
|
||||
|
||||
serial_instance.open()
|
||||
except serial.SerialException as e:
|
||||
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
|
||||
if args.develop:
|
||||
raise
|
||||
if not args.ask:
|
||||
sys.exit(1)
|
||||
else:
|
||||
args.port = '-'
|
||||
else:
|
||||
break
|
||||
|
||||
miniterm = Miniterm(
|
||||
serial_instance,
|
||||
echo=args.echo,
|
||||
eol=args.eol.lower(),
|
||||
filters=filters)
|
||||
miniterm.exit_character = unichr(args.exit_char)
|
||||
miniterm.menu_character = unichr(args.menu_char)
|
||||
miniterm.raw = args.raw
|
||||
miniterm.set_rx_encoding(args.serial_port_encoding)
|
||||
miniterm.set_tx_encoding(args.serial_port_encoding)
|
||||
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
|
||||
p=miniterm.serial))
|
||||
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
|
||||
key_description(miniterm.exit_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description('\x08')))
|
||||
|
||||
miniterm.start()
|
||||
try:
|
||||
miniterm.join(True)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
if not args.quiet:
|
||||
sys.stderr.write("\n--- exit ---\n")
|
||||
miniterm.join()
|
||||
miniterm.close()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
backend/venv/bin/miniterm.pyc
Executable file
BIN
backend/venv/bin/miniterm.pyc
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
Python Serial Port Extension for Win32, OSX, Linux, BSD, Jython, IronPython
|
||||
|
||||
Stable:
|
||||
|
||||
- Documentation: http://pythonhosted.org/pyserial/
|
||||
- Download Page: https://pypi.python.org/pypi/pyserial
|
||||
|
||||
Latest:
|
||||
|
||||
- Documentation: http://pyserial.readthedocs.io/en/latest/
|
||||
- Project Homepage: https://github.com/pyserial/pyserial
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: pyserial
|
||||
Version: 3.4
|
||||
Summary: Python Serial Port Extension
|
||||
Home-page: https://github.com/pyserial/pyserial
|
||||
Author: Chris Liechti
|
||||
Author-email: cliechti@gmx.net
|
||||
License: BSD
|
||||
Platform: any
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: End Users/Desktop
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: Operating System :: POSIX
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Operating System :: MacOS :: MacOS X
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Topic :: Communications
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Terminals :: Serial
|
||||
|
||||
Python Serial Port Extension for Win32, OSX, Linux, BSD, Jython, IronPython
|
||||
|
||||
Stable:
|
||||
|
||||
- Documentation: http://pythonhosted.org/pyserial/
|
||||
- Download Page: https://pypi.python.org/pypi/pyserial
|
||||
|
||||
Latest:
|
||||
|
||||
- Documentation: http://pyserial.readthedocs.io/en/latest/
|
||||
- Project Homepage: https://github.com/pyserial/pyserial
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
../../../bin/__pycache__/miniterm.cpython-36.pyc,,
|
||||
../../../bin/miniterm.py,sha256=XmdvozzNwHh8zd6tDMoefXHz39myqwvc63L3b6mnS0g,35088
|
||||
../../../bin/miniterm.pyc,sha256=cjhQp6L0aitsx6yg5IpG9lYPVzBAYkrSndVHpGwYorY,28907
|
||||
pyserial-3.4.dist-info/DESCRIPTION.rst,sha256=rXXIUFeAsfXq2YS7DGkztGmXez-G7gAwbwdBL8t9KME,320
|
||||
pyserial-3.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
pyserial-3.4.dist-info/METADATA,sha256=CExk0lotyj8ds3rAKUq2ME1vN0lsYWn3CWPDjFqzLH8,1563
|
||||
pyserial-3.4.dist-info/RECORD,,
|
||||
pyserial-3.4.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
|
||||
pyserial-3.4.dist-info/metadata.json,sha256=fUPYvHyeQugtZXmQ7u6u8vJOn7CGHArr__PtrUQrg0w,1280
|
||||
pyserial-3.4.dist-info/top_level.txt,sha256=FSjfWHWw-VjPiEqOhttbiP-F8OHn-liixq1wKL2fWOA,7
|
||||
serial/__init__.py,sha256=M6W0AbWpV-Y6fyw9eb56C2hahVw7MIvWBefT6yCAHIE,3172
|
||||
serial/__init__.pyc,sha256=CajVqMkMYQHB_krKsRE8Rz1x_IeWtEz7NUD0oKFZ0tk,2322
|
||||
serial/__pycache__/__init__.cpython-36.pyc,,
|
||||
serial/__pycache__/aio.cpython-36.pyc,,
|
||||
serial/__pycache__/rfc2217.cpython-36.pyc,,
|
||||
serial/__pycache__/rs485.cpython-36.pyc,,
|
||||
serial/__pycache__/serialcli.cpython-36.pyc,,
|
||||
serial/__pycache__/serialjava.cpython-36.pyc,,
|
||||
serial/__pycache__/serialposix.cpython-36.pyc,,
|
||||
serial/__pycache__/serialutil.cpython-36.pyc,,
|
||||
serial/__pycache__/serialwin32.cpython-36.pyc,,
|
||||
serial/__pycache__/win32.cpython-36.pyc,,
|
||||
serial/aio.py,sha256=6gWn7JXv21B1jE-Ykf-7v17y8AA1pha1cEp-OXmCmDU,3659
|
||||
serial/aio.pyc,sha256=t5P_mbq_c2SDIDwqG88UbKGMKYeNYh77wqZC3k-vyMQ,4496
|
||||
serial/rfc2217.py,sha256=FLgI3m-gx53aH1vISC7P8okCyoagN80Nh1tC6t7hisc,59486
|
||||
serial/rfc2217.pyc,sha256=y8gblsIrXc0OUU-HZV-QRhJ138n5a2qbQqi1eht-hs0,37632
|
||||
serial/rs485.py,sha256=O2YbxLMlziEX1N39WOukrlhMWcYzVwiP95FBhMmUIHc,3265
|
||||
serial/rs485.pyc,sha256=9pJKRaRQLI-aLwWRdJ-5LObGezVeSO45M6AollCHjnM,3266
|
||||
serial/serialcli.py,sha256=MVzD-mB-snJhwY4hjXVAuOWA3OXjFU79A2zMMlAMPKQ,9104
|
||||
serial/serialcli.pyc,sha256=CqL9FtRa1Wz15V428jnBaukO-7KKn5_4q1t0z713C7s,8308
|
||||
serial/serialjava.py,sha256=OsYX1HEEo0pJzRJqmJfJLqZdBFhCO2s8LLt1s1-TYVg,8414
|
||||
serial/serialjava.pyc,sha256=fDM30MRvDuytk1Ss8rfK88Gk-a3KvYhhvGWNks7-a2U,9577
|
||||
serial/serialposix.py,sha256=UYZDZcu5uCNHjZJfnJ1IUhTXLz-wyV2n0AB0hFd0qxE,31654
|
||||
serial/serialposix.pyc,sha256=UOz_nIhtX_D6cpXqQXeGzZBIX4_i6R-xhR9NWiv_K8I,24286
|
||||
serial/serialutil.py,sha256=V-AeEMJyl8GvijaiildLRY8k-IOAZpW-YeBxHV2iDvg,21692
|
||||
serial/serialutil.pyc,sha256=BO3_7SlWWYxCbYOmerYsQOiLu7XRbda4kkrZx4wIZWw,20027
|
||||
serial/serialwin32.py,sha256=tMa-81Oh1c4g5nHf86v2eDnLKb7JHAG-vEQZfq2q_Jo,20087
|
||||
serial/serialwin32.pyc,sha256=FArzpAX1_F6xFQmkAbDmaYUbw0wEPWRr39lkXuoHPVA,14471
|
||||
serial/threaded/__init__.py,sha256=EHay8pD__ePKaa-WvweNjWe9V1SUgZMefNnayVyiwRw,9272
|
||||
serial/threaded/__pycache__/__init__.cpython-36.pyc,,
|
||||
serial/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
serial/tools/__init__.pyc,sha256=stNDuwUOHX5ySsWZfedBkax1go_wefynkMBqnkv9DSI,140
|
||||
serial/tools/__pycache__/__init__.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/hexlify_codec.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/list_ports.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/list_ports_common.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/list_ports_linux.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/list_ports_osx.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/list_ports_posix.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/list_ports_windows.cpython-36.pyc,,
|
||||
serial/tools/__pycache__/miniterm.cpython-36.pyc,,
|
||||
serial/tools/hexlify_codec.py,sha256=1lvd2TFWYUsYMRt7nsyw0VfHTRfl57Hl6kdCmNhMkC4,3637
|
||||
serial/tools/hexlify_codec.pyc,sha256=xy_EM_EuW4LzSO3BhEAs7V7HhCxb6e-Iv3KafY9Ckrw,4872
|
||||
serial/tools/list_ports.py,sha256=mfNJILSFufMJR2Ns7AWF7CMMhSqXcuK-R-cyK4xBLro,3349
|
||||
serial/tools/list_ports.pyc,sha256=Czc32SiTfXsqIC3s2gtZoQNS8i0AoQ6iTBakY84R2-s,2815
|
||||
serial/tools/list_ports_common.py,sha256=H_NcNxOc15MnzQWGlIXi0mRiLQV1QNiO6fHjPQ3SXEY,3319
|
||||
serial/tools/list_ports_common.pyc,sha256=gE-B4IMXiG2w2mUybAEyrCcSkmuGYcCkcbKhE7tbIm4,3315
|
||||
serial/tools/list_ports_linux.py,sha256=-uPj714Jb2Iu3NzFjpnFBEYLCaL3eQG6_iuXj1p9tp4,4427
|
||||
serial/tools/list_ports_linux.pyc,sha256=BJ7V6Bu44dKh2PcTK3dDyIhU8qPzjaPxPnqmypLNepI,3038
|
||||
serial/tools/list_ports_osx.py,sha256=h0AaNNnMgRWX2VBfQCWpeAlpynA8YUVUblHa8krdoTY,9302
|
||||
serial/tools/list_ports_osx.pyc,sha256=FQYKsoGne9LNzhwIM8WFOzvhhR8dYzgPHLOgOcKK1gQ,7164
|
||||
serial/tools/list_ports_posix.py,sha256=6z6XZKrjsHkORJe1bn4_loVcgKrkmZCWtSPikrST6y8,4495
|
||||
serial/tools/list_ports_posix.pyc,sha256=bpGgc6CIIb3JHvXe14LF6bWx7dT4_yyv2XCdYaya6q0,3966
|
||||
serial/tools/list_ports_windows.py,sha256=yHlGXUuCtaBMt3ucU7WeYL27IAHkvrB732ZDiT8TUgA,11336
|
||||
serial/tools/list_ports_windows.pyc,sha256=X47GTdA5jDWxd013ilynOgxsYP_vffQfOiJUJEIdxVg,6912
|
||||
serial/tools/miniterm.py,sha256=t3rlJRihyYzMGO0EAz0Kpd0sewWYnS6M8qtw6ygqg_g,35101
|
||||
serial/tools/miniterm.pyc,sha256=qNPk-vOUXoPDpswiQE4R0LEybrRBzmNBPWV1XglLhTc,30469
|
||||
serial/urlhandler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
serial/urlhandler/__init__.pyc,sha256=SBlUmYLyKiul4P6w70w1c0vIiRpv8MGzn5awpHwaO3o,145
|
||||
serial/urlhandler/__pycache__/__init__.cpython-36.pyc,,
|
||||
serial/urlhandler/__pycache__/protocol_alt.cpython-36.pyc,,
|
||||
serial/urlhandler/__pycache__/protocol_hwgrep.cpython-36.pyc,,
|
||||
serial/urlhandler/__pycache__/protocol_loop.cpython-36.pyc,,
|
||||
serial/urlhandler/__pycache__/protocol_rfc2217.cpython-36.pyc,,
|
||||
serial/urlhandler/__pycache__/protocol_serve-rfc2217.cpython-36.pyc,,
|
||||
serial/urlhandler/__pycache__/protocol_socket.cpython-36.pyc,,
|
||||
serial/urlhandler/__pycache__/protocol_spy.cpython-36.pyc,,
|
||||
serial/urlhandler/protocol_alt.py,sha256=pWlKn8HBBENU9DNeO46gLgTTWvYHLaLB9O5YVwfEQ0o,1993
|
||||
serial/urlhandler/protocol_hwgrep.py,sha256=tx4r6Y8smyF7rV0tb2OXMV3ws7xpJtImBH57ZUaSWIo,3119
|
||||
serial/urlhandler/protocol_hwgrep.pyc,sha256=cW_46V23glPYWPwp17_zbxxcZJyJEvEOuoZzosyR11o,1627
|
||||
serial/urlhandler/protocol_loop.py,sha256=FoDnQw69tQnPRz8Q7p7PMhxIL1wlBKBi7oAPBwJ9-Xo,10119
|
||||
serial/urlhandler/protocol_loop.pyc,sha256=g7QJtSdFQBBNZQwHlgABtxYtWZbOMUH92KuKjZmJDEA,9394
|
||||
serial/urlhandler/protocol_rfc2217.py,sha256=886ex7FHRb_qPKXJC7HcR6RtMdmvxo4ChEohliqNs5E,277
|
||||
serial/urlhandler/protocol_rfc2217.pyc,sha256=Yb3Cn9QymSWgHiiU3s21_yQiKyOKOgjd3nTRIrkvY4Q,214
|
||||
serial/urlhandler/protocol_serve-rfc2217.py,sha256=886ex7FHRb_qPKXJC7HcR6RtMdmvxo4ChEohliqNs5E,277
|
||||
serial/urlhandler/protocol_socket.py,sha256=cCy3zC4WzclIHnYQaf0MaW65h22TPRJpEGbMmJhXPyk,14130
|
||||
serial/urlhandler/protocol_socket.pyc,sha256=KoUDgHAXrHpKi8NC5ABBZuscVOe8dIC-URq5iWGTWys,9803
|
||||
serial/urlhandler/protocol_spy.py,sha256=9DxcDfV0upKmH3beYBcHMSQzLcxRS9ptnx7a__vefUg,9024
|
||||
serial/urlhandler/protocol_spy.pyc,sha256=laRaXOAPYzcgG05c68fWObXgVJBJLL5g9CfqOMAdr8I,10984
|
||||
serial/win32.py,sha256=-GiHVB-QFrQfD4vHelhaEccwfW__6kC2S_46lIYLVsM,10853
|
||||
serial/win32.pyc,sha256=iZof5kxLw1eLsxzaD_O2xgIXs9TVFao_zlyqN8C5fZ8,7725
|
||||
@@ -1,5 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.32.1)
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
@@ -0,0 +1 @@
|
||||
{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Communications", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Terminals :: Serial"], "extensions": {"python.details": {"contacts": [{"email": "cliechti@gmx.net", "name": "Chris Liechti", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/pyserial/pyserial"}}}, "generator": "bdist_wheel (0.29.0)", "license": "BSD", "metadata_version": "2.0", "name": "pyserial", "platform": "any", "summary": "Python Serial Port Extension", "version": "3.4"}
|
||||
@@ -1,26 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: serial
|
||||
Version: 0.0.91
|
||||
Summary: A framework for serializing/deserializing JSON/YAML/XML into python class instances and vice versa
|
||||
Home-page: https://bitbucket.com/davebelais/serial.git
|
||||
Author: David Belais
|
||||
Author-email: david@belais.me
|
||||
License: MIT
|
||||
Keywords: rest api serialization serialize
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 2 - Pre-Alpha
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Requires-Dist: future (>=0.17.1)
|
||||
Requires-Dist: pyyaml (>=3.13)
|
||||
Requires-Dist: iso8601 (>=0.1.12)
|
||||
|
||||
UNKNOWN
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
serial-0.0.91.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
serial-0.0.91.dist-info/METADATA,sha256=HIgxcDdfyCfsjlnzIWs9nmFaeMKOHvkxTjv6cBHRSTM,889
|
||||
serial-0.0.91.dist-info/RECORD,,
|
||||
serial-0.0.91.dist-info/WHEEL,sha256=8T8fxefr_r-A79qbOJ9d_AaEgkpCGmEPHc-gpCq5BRg,110
|
||||
serial-0.0.91.dist-info/top_level.txt,sha256=FSjfWHWw-VjPiEqOhttbiP-F8OHn-liixq1wKL2fWOA,7
|
||||
serial/__init__.py,sha256=93wvMGEaUuBYuXcFk9knZsuJVrU6UxUVAfGyAXBgJ94,472
|
||||
serial/__pycache__/__init__.cpython-36.pyc,,
|
||||
serial/__pycache__/errors.cpython-36.pyc,,
|
||||
serial/__pycache__/hooks.cpython-36.pyc,,
|
||||
serial/__pycache__/marshal.cpython-36.pyc,,
|
||||
serial/__pycache__/meta.cpython-36.pyc,,
|
||||
serial/__pycache__/model.cpython-36.pyc,,
|
||||
serial/__pycache__/properties.cpython-36.pyc,,
|
||||
serial/__pycache__/request.cpython-36.pyc,,
|
||||
serial/__pycache__/test.cpython-36.pyc,,
|
||||
serial/abc/__init__.py,sha256=kPltSkxwc2LSzjvmCLdRkou3fk6ND85H0Rq0pR3Sbcc,257
|
||||
serial/abc/__pycache__/__init__.cpython-36.pyc,,
|
||||
serial/abc/__pycache__/model.cpython-36.pyc,,
|
||||
serial/abc/__pycache__/properties.cpython-36.pyc,,
|
||||
serial/abc/model.py,sha256=lI-7DDWvFjYHJ03VeZSNDxODdCCQeLia0ixZG5uiymU,6116
|
||||
serial/abc/properties.py,sha256=ho1HkWayE4m8SHfqM4O-RvcLjzRql4mQxBm65iOiwcc,3036
|
||||
serial/errors.py,sha256=aagqxWtjMLuyvHojOhSK4jspVDcXXKUQ-WavH9iXXmM,5217
|
||||
serial/hooks.py,sha256=PH28AQ5VtFbT5IuHS8IVMSjYtEWHR0FlBgREqDESi_U,8749
|
||||
serial/marshal.py,sha256=b61GMVRP19h_-DW1yqeu82676ti0ermlsvZEVb09pso,22125
|
||||
serial/meta.py,sha256=qXDFjkznp7fB_t4KDTVhLSGKnFfafQEAr9YeO9wtkX4,27529
|
||||
serial/model.py,sha256=NzdXq9CvcYNYnn-7wZvmwv1j2c8j7gR8gS6CWMtasq0,41874
|
||||
serial/properties.py,sha256=XdZlkjaeyc_7rs6x2BXtVl6pKx5cEaZuuzA9vi0RJuc,29488
|
||||
serial/request.py,sha256=hqn0CGzGP9pn5eErrHdiPUllIcVqYe-6YhOgGaT9J1M,14226
|
||||
serial/test.py,sha256=xNmDsOCeX7NYqmfxs1aQeB1P_0Fnfd88hyfEWhMPH4E,9273
|
||||
serial/utilities/__init__.py,sha256=_k3k8_wm9sZ-jWRNS9Bfi0zaHZWY9by-Ykj2qMmUvNQ,13250
|
||||
serial/utilities/__pycache__/__init__.cpython-36.pyc,,
|
||||
serial/utilities/__pycache__/compatibility.cpython-36.pyc,,
|
||||
serial/utilities/compatibility.py,sha256=rfAYMsSPUyHP8H2rURgbKW7mCsttBJ0R_NFXjXaZZLg,845
|
||||
@@ -1,10 +1,89 @@
|
||||
"""
|
||||
`serial` is an object serialization/deserialization library intended to facilitate authoring of API models which are
|
||||
readable and introspective, and to expedite code and data validation and testing. `serial` supports JSON, YAML, and
|
||||
XML.
|
||||
"""
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a wrapper module for different platform implementations
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2017 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
from . import utilities, abc, model, marshal, errors, properties, meta, hooks, test, request
|
||||
from serial.serialutil import *
|
||||
#~ SerialBase, SerialException, to_bytes, iterbytes
|
||||
|
||||
__version__ = '3.4'
|
||||
|
||||
VERSION = __version__
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
if sys.platform == 'cli':
|
||||
from serial.serialcli import Serial
|
||||
else:
|
||||
import os
|
||||
# chose an implementation, depending on os
|
||||
if os.name == 'nt': # sys.platform == 'win32':
|
||||
from serial.serialwin32 import Serial
|
||||
elif os.name == 'posix':
|
||||
from serial.serialposix import Serial, PosixPollSerial, VTIMESerial # noqa
|
||||
elif os.name == 'java':
|
||||
from serial.serialjava import Serial
|
||||
else:
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
|
||||
protocol_handler_packages = [
|
||||
'serial.urlhandler',
|
||||
]
|
||||
|
||||
|
||||
def serial_for_url(url, *args, **kwargs):
|
||||
"""\
|
||||
Get an instance of the Serial class, depending on port/url. The port is not
|
||||
opened when the keyword parameter 'do_not_open' is true, by default it
|
||||
is. All other parameters are directly passed to the __init__ method when
|
||||
the port is instantiated.
|
||||
|
||||
The list of package names that is searched for protocol handlers is kept in
|
||||
``protocol_handler_packages``.
|
||||
|
||||
e.g. we want to support a URL ``foobar://``. A module
|
||||
``my_handlers.protocol_foobar`` is provided by the user. Then
|
||||
``protocol_handler_packages.append("my_handlers")`` would extend the search
|
||||
path so that ``serial_for_url("foobar://"))`` would work.
|
||||
"""
|
||||
# check and remove extra parameter to not confuse the Serial class
|
||||
do_open = not kwargs.pop('do_not_open', False)
|
||||
# the default is to use the native implementation
|
||||
klass = Serial
|
||||
try:
|
||||
url_lowercase = url.lower()
|
||||
except AttributeError:
|
||||
# it's not a string, use default
|
||||
pass
|
||||
else:
|
||||
# if it is an URL, try to import the handler module from the list of possible packages
|
||||
if '://' in url_lowercase:
|
||||
protocol = url_lowercase.split('://', 1)[0]
|
||||
module_name = '.protocol_{}'.format(protocol)
|
||||
for package_name in protocol_handler_packages:
|
||||
try:
|
||||
importlib.import_module(package_name)
|
||||
handler_module = importlib.import_module(module_name, package_name)
|
||||
except ImportError:
|
||||
continue
|
||||
else:
|
||||
if hasattr(handler_module, 'serial_class_for_url'):
|
||||
url, klass = handler_module.serial_class_for_url(url)
|
||||
else:
|
||||
klass = handler_module.Serial
|
||||
break
|
||||
else:
|
||||
raise ValueError('invalid URL, protocol {!r} not known'.format(protocol))
|
||||
# instantiate and open when desired
|
||||
instance = klass(None, *args, **kwargs)
|
||||
instance.port = url
|
||||
if do_open:
|
||||
instance.open()
|
||||
return instance
|
||||
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/__init__.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/__init__.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
# region Backwards Compatibility
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
from ..utilities.compatibility import backport
|
||||
|
||||
backport()
|
||||
|
||||
from . import model, properties
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,250 +0,0 @@
|
||||
# Tell the linters what's up:
|
||||
# pylint:disable=wrong-import-position,consider-using-enumerate,useless-object-inheritance
|
||||
# mccabe:options:max-complexity=999
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
|
||||
from serial.utilities.compatibility import backport
|
||||
|
||||
backport()
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
# We need ABCs to inherit from `ABC` in python 3x, but in python 2x ABC is absent and we need classes to inherit from
|
||||
# `object` in order to be new-style classes
|
||||
try:
|
||||
from abc import ABC
|
||||
except ImportError:
|
||||
ABC = object
|
||||
|
||||
from ..utilities import collections
|
||||
|
||||
|
||||
class Model(ABC):
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
_format = None # type: Optional[str]
|
||||
_meta = None # type: Optional[meta.Object]
|
||||
_hooks = None # type: Optional[hooks.Object]
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
self._format = None # type: Optional[str]
|
||||
self._meta = None # type: Optional[meta.Meta]
|
||||
self._hooks = None # type: Optional[hooks.Hooks]
|
||||
self._url = None # type: Optional[str]
|
||||
self._xpath = None # type: Optional[str]
|
||||
self._pointer = None # type: Optional[str]
|
||||
|
||||
@classmethod
|
||||
def __subclasscheck__(cls, subclass):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Check a subclass to ensure it has required properties
|
||||
"""
|
||||
|
||||
if cls is subclass or type.__subclasscheck__(cls, subclass):
|
||||
return True
|
||||
|
||||
for attribute in (
|
||||
'_format',
|
||||
'_meta',
|
||||
'_hooks'
|
||||
):
|
||||
if not hasattr(subclass, attribute):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __instancecheck__(self, instance):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Check an instance of a subclass to ensure it has required properties
|
||||
"""
|
||||
|
||||
for attribute in (
|
||||
'_format',
|
||||
'_meta',
|
||||
'_hooks',
|
||||
'_url',
|
||||
'_xpath',
|
||||
'_pointer'
|
||||
):
|
||||
if not hasattr(self, attribute):
|
||||
return False
|
||||
|
||||
# Perform any instance checks needed for our superclass(es)
|
||||
try:
|
||||
return super().__instancecheck__(instance)
|
||||
except AttributeError: # There is no further instance-checking to perform
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
def _marshal(self):
|
||||
# type: (...) -> Union[collections.OrderedDict, list]
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _validate(self, raise_errors=True):
|
||||
# type: (bool) -> None
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self):
|
||||
# type: (...) -> str
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __repr__(self):
|
||||
# type: (...) -> str
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __copy__(self):
|
||||
# type: (...) -> Object
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __deepcopy__(self, memo):
|
||||
# type: (Optional[dict]) -> Object
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __eq__(self, other):
|
||||
# type: (Any) -> bool
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __ne__(self, other):
|
||||
# type: (Any) -> bool
|
||||
pass
|
||||
|
||||
|
||||
class Object(Model):
|
||||
|
||||
@abstractmethod
|
||||
def _marshal(self):
|
||||
# type: (...) -> collections.OrderedDict
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __setitem__(self, key, value):
|
||||
# type: (str, Any) -> None
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __getitem__(self, key):
|
||||
# type: (str, Any) -> None
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __setattr__(self, property_name, value):
|
||||
# type: (Object, str, Any) -> None
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __getattr__(self, key):
|
||||
# type: (str) -> Any
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __delattr__(self, key):
|
||||
# type: (str) -> None
|
||||
pass
|
||||
|
||||
|
||||
class Dictionary(Model):
|
||||
|
||||
@classmethod
|
||||
def __subclasscheck__(cls, subclass):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Verify inheritance
|
||||
"""
|
||||
if cls is subclass or type.__subclasscheck__(cls, subclass):
|
||||
return True
|
||||
|
||||
if not issubclass(subclass, collections.OrderedDict):
|
||||
return False
|
||||
|
||||
# Perform any subclass checks needed for our superclass(es)
|
||||
return super().__subclasscheck__(subclass)
|
||||
|
||||
def __instancecheck__(self, instance):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Check an instance of a subclass to ensure it has required properties
|
||||
"""
|
||||
|
||||
if not isinstance(self, collections.OrderedDict):
|
||||
return False
|
||||
|
||||
# Perform any instance checks needed for our superclass(es)
|
||||
return super().__instancecheck__(instance)
|
||||
|
||||
@abstractmethod
|
||||
def _marshal(self):
|
||||
# type: (...) -> collections.OrderedDict
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __setitem__(self, key, value):
|
||||
# type: (str, Any) -> None
|
||||
pass
|
||||
|
||||
def keys(self):
|
||||
# type: (...) -> Iterable[str]
|
||||
pass
|
||||
|
||||
def values(self):
|
||||
# type: (...) -> Iterable[Any]
|
||||
pass
|
||||
|
||||
|
||||
class Array(Model):
|
||||
|
||||
@classmethod
|
||||
def __subclasscheck__(cls, subclass):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Verify inheritance
|
||||
"""
|
||||
|
||||
if cls is subclass or type.__subclasscheck__(cls, subclass):
|
||||
return True
|
||||
|
||||
if not issubclass(subclass, list):
|
||||
return False
|
||||
|
||||
# Perform any subclass checks needed for our superclass(es)
|
||||
return super().__subclasscheck__(subclass)
|
||||
|
||||
def __instancecheck__(self, instance):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Check an instance of a subclass to ensure it has required properties
|
||||
"""
|
||||
|
||||
if not isinstance(self, list):
|
||||
return False
|
||||
|
||||
# Perform any instance checks needed for our superclass(es)
|
||||
return super().__instancecheck__(instance)
|
||||
|
||||
@abstractmethod
|
||||
def _marshal(self):
|
||||
# type: (...) -> list
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __setitem__(self, key, value):
|
||||
# type: (str, Any) -> None
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def append(self, value):
|
||||
# type: (Any) -> None
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# Tell the linters what's up:
|
||||
# pylint:disable=wrong-import-position,consider-using-enumerate,useless-object-inheritance
|
||||
# mccabe:options:max-complexity=999
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
from ..utilities.compatibility import backport
|
||||
|
||||
backport()
|
||||
|
||||
from future.utils import native_str # noqa
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
# We need to inherit from `ABC` in python 3x, but in python 2x ABC is absent
|
||||
try:
|
||||
from abc import ABC
|
||||
except ImportError:
|
||||
ABC = object
|
||||
|
||||
|
||||
class Property(ABC):
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
types=None, # type: Sequence[Union[type, Property]]
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Sequence[Union[str, serial.meta.Version]]]
|
||||
):
|
||||
self._types = None # type: Optional[Sequence[Union[type, Property]]]
|
||||
self.types = types
|
||||
self.name = name
|
||||
self.required = required
|
||||
self._versions = None # type: Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
|
||||
self.versions = versions # type: Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
|
||||
|
||||
def __instancecheck__(self, instance):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Check an instance of a subclass to ensure it has required properties
|
||||
"""
|
||||
|
||||
for attribute in (
|
||||
'_types',
|
||||
'_versions',
|
||||
'name',
|
||||
'required'
|
||||
):
|
||||
if not hasattr(self, attribute):
|
||||
return False
|
||||
|
||||
# Perform any instance checks needed for our superclass(es)
|
||||
return super().__instancecheck__(instance)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def types(self):
|
||||
# type: (...) -> Optional[Sequence[Union[type, Property, Model]]]
|
||||
pass
|
||||
|
||||
@types.setter
|
||||
@abstractmethod
|
||||
def types(self, types_or_properties):
|
||||
# type: (Optional[Sequence[Union[type, Property, Model]]]) -> None
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def versions(self):
|
||||
# type: () -> Optional[Sequence[meta.Version]]
|
||||
pass
|
||||
|
||||
@versions.setter
|
||||
@abstractmethod
|
||||
def versions(
|
||||
self,
|
||||
versions # type: Optional[Sequence[Union[str, collections_abc.Iterable, meta.Version]]]
|
||||
):
|
||||
# type: (...) -> Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def unmarshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def marshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __repr__(self):
|
||||
# type: (...) -> str
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __copy__(self):
|
||||
# type: (...) -> Property
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __deepcopy__(self, memo):
|
||||
# type: (dict) -> Property
|
||||
pass
|
||||
115
backend/venv/lib/python3.6/site-packages/serial/aio.py
Normal file
115
backend/venv/lib/python3.6/site-packages/serial/aio.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Python Serial Port Extension for Win32, Linux, BSD, Jython
|
||||
# module for serial IO for POSIX compatible systems, like Linux
|
||||
# see __init__.py
|
||||
#
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Support asyncio with serial ports. EXPERIMENTAL
|
||||
|
||||
Posix platforms only, Python 3.4+ only.
|
||||
|
||||
Windows event loops can not wait for serial ports with the current
|
||||
implementation. It should be possible to get that working though.
|
||||
"""
|
||||
import asyncio
|
||||
import serial
|
||||
import logger
|
||||
|
||||
|
||||
class SerialTransport(asyncio.Transport):
|
||||
def __init__(self, loop, protocol, serial_instance):
|
||||
self._loop = loop
|
||||
self._protocol = protocol
|
||||
self.serial = serial_instance
|
||||
self._closing = False
|
||||
self._paused = False
|
||||
# XXX how to support url handlers too
|
||||
self.serial.timeout = 0
|
||||
self.serial.nonblocking()
|
||||
loop.call_soon(protocol.connection_made, self)
|
||||
# only start reading when connection_made() has been called
|
||||
loop.call_soon(loop.add_reader, self.serial.fd, self._read_ready)
|
||||
|
||||
def __repr__(self):
|
||||
return '{self.__class__.__name__}({self._loop}, {self._protocol}, {self.serial})'.format(self=self)
|
||||
|
||||
def close(self):
|
||||
if self._closing:
|
||||
return
|
||||
self._closing = True
|
||||
self._loop.remove_reader(self.serial.fd)
|
||||
self.serial.close()
|
||||
self._loop.call_soon(self._protocol.connection_lost, None)
|
||||
|
||||
def _read_ready(self):
|
||||
data = self.serial.read(1024)
|
||||
if data:
|
||||
self._protocol.data_received(data)
|
||||
|
||||
def write(self, data):
|
||||
self.serial.write(data)
|
||||
|
||||
def can_write_eof(self):
|
||||
return False
|
||||
|
||||
def pause_reading(self):
|
||||
if self._closing:
|
||||
raise RuntimeError('Cannot pause_reading() when closing')
|
||||
if self._paused:
|
||||
raise RuntimeError('Already paused')
|
||||
self._paused = True
|
||||
self._loop.remove_reader(self._sock_fd)
|
||||
if self._loop.get_debug():
|
||||
logger.debug("%r pauses reading", self)
|
||||
|
||||
def resume_reading(self):
|
||||
if not self._paused:
|
||||
raise RuntimeError('Not paused')
|
||||
self._paused = False
|
||||
if self._closing:
|
||||
return
|
||||
self._loop.add_reader(self._sock_fd, self._read_ready)
|
||||
if self._loop.get_debug():
|
||||
logger.debug("%r resumes reading", self)
|
||||
|
||||
#~ def set_write_buffer_limits(self, high=None, low=None):
|
||||
#~ def get_write_buffer_size(self):
|
||||
#~ def writelines(self, list_of_data):
|
||||
#~ def write_eof(self):
|
||||
#~ def abort(self):
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def create_serial_connection(loop, protocol_factory, *args, **kwargs):
|
||||
ser = serial.Serial(*args, **kwargs)
|
||||
protocol = protocol_factory()
|
||||
transport = SerialTransport(loop, protocol, ser)
|
||||
return (transport, protocol)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
class Output(asyncio.Protocol):
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
print('port opened', transport)
|
||||
transport.serial.rts = False
|
||||
transport.write(b'hello world\n')
|
||||
|
||||
def data_received(self, data):
|
||||
print('data received', repr(data))
|
||||
self.transport.close()
|
||||
|
||||
def connection_lost(self, exc):
|
||||
print('port closed')
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
|
||||
loop.run_until_complete(coro)
|
||||
loop.run_forever()
|
||||
loop.close()
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/aio.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/aio.pyc
Normal file
Binary file not shown.
@@ -1,214 +0,0 @@
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
from .utilities.compatibility import backport
|
||||
|
||||
backport()
|
||||
|
||||
from future.utils import native_str
|
||||
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from numbers import Number
|
||||
|
||||
try:
|
||||
import typing
|
||||
except ImportError as e:
|
||||
typing = None
|
||||
|
||||
from .abc.model import Model
|
||||
from .utilities import qualified_name, collections, collections_abc, Generator
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class VersionError(AttributeError):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DefinitionExistsError(Exception):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UnmarshalValueError(ValueError):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
UNMARSHALLABLE_TYPES = tuple({
|
||||
str, bytes, native_str, Number, Decimal, date, datetime, bool,
|
||||
dict, collections.OrderedDict,
|
||||
collections_abc.Set, collections_abc.Sequence, Generator,
|
||||
Model
|
||||
})
|
||||
|
||||
|
||||
class UnmarshalError(Exception):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data, # type: Any
|
||||
types=None, # type: Optional[Sequence[Model, Property, type]]
|
||||
item_types=None, # type: Optional[Sequence[Model, Property, type]]
|
||||
value_types=None # type: Optional[Sequence[Model, Property, type]]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Generate a comprehensible error message for data which could not be un-marshalled according to spec, and raise
|
||||
the appropriate exception
|
||||
"""
|
||||
|
||||
self._message = None # type: Optional[str]
|
||||
self._parameter = None # type: Optional[str]
|
||||
self._index = None # type: Optional[int]
|
||||
self._key = None # type: Optional[str]
|
||||
|
||||
error_message_lines = ['']
|
||||
|
||||
# Identify which parameter is being used for type validation
|
||||
|
||||
types_label = None
|
||||
|
||||
if types:
|
||||
types_label = 'types'
|
||||
elif item_types:
|
||||
types_label = 'item_types'
|
||||
types = item_types
|
||||
elif value_types:
|
||||
types_label = 'value_types'
|
||||
types = value_types
|
||||
|
||||
# Assemble the error message
|
||||
|
||||
# Assemble a text representation of the `data`
|
||||
|
||||
data_lines = []
|
||||
|
||||
lines = repr(data).strip().split('\n')
|
||||
|
||||
if len(lines) == 1:
|
||||
|
||||
data_lines.append(lines[0])
|
||||
|
||||
else:
|
||||
|
||||
data_lines.append('')
|
||||
|
||||
for line in lines:
|
||||
data_lines.append(
|
||||
' ' + line
|
||||
)
|
||||
|
||||
# Assemble a text representation of the `types`, `item_types`, or `value_types`.
|
||||
|
||||
if types is None:
|
||||
|
||||
error_message_lines.append('The data provided is not an instance of an un-marshallable type:')
|
||||
|
||||
else:
|
||||
|
||||
error_message_lines.append(
|
||||
'The data provided does not match any of the expected types and/or property definitions:'
|
||||
)
|
||||
|
||||
error_message_lines.append(
|
||||
' - data: %s' % '\n'.join(data_lines)
|
||||
)
|
||||
|
||||
if types is None:
|
||||
|
||||
types = UNMARSHALLABLE_TYPES
|
||||
types_label = 'un-marshallable types'
|
||||
|
||||
types_lines = ['(']
|
||||
|
||||
for type_ in types:
|
||||
|
||||
if isinstance(type_, type):
|
||||
lines = (qualified_name(type_),)
|
||||
else:
|
||||
lines = repr(type_).split('\n')
|
||||
|
||||
for line in lines:
|
||||
types_lines.append(
|
||||
' ' + line
|
||||
)
|
||||
|
||||
types_lines[-1] += ','
|
||||
|
||||
types_lines.append(' )')
|
||||
|
||||
error_message_lines.append(
|
||||
' - %s: %s' % (types_label, '\n'.join(types_lines))
|
||||
)
|
||||
|
||||
self.message = '\n'.join(error_message_lines)
|
||||
|
||||
@property
|
||||
def paramater(self):
|
||||
# type: (...) -> Optional[str]
|
||||
return self._parameter
|
||||
|
||||
@paramater.setter
|
||||
def paramater(self, paramater_name):
|
||||
# type: (str) -> None
|
||||
if paramater_name != self.paramater:
|
||||
self._parameter = paramater_name
|
||||
self.assemble_message()
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
# type: (...) -> Optional[str]
|
||||
return self._message
|
||||
|
||||
@message.setter
|
||||
def message(self, message_text):
|
||||
# type: (str) -> None
|
||||
if message_text != self.message:
|
||||
self._message = message_text
|
||||
self.assemble_message()
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
# type: (...) -> Optional[int]
|
||||
return self._message
|
||||
|
||||
@index.setter
|
||||
def index(self, index_or_key):
|
||||
# type: (Union[str, int]) -> None
|
||||
if index_or_key != self.index:
|
||||
self._index = index_or_key
|
||||
self.assemble_message()
|
||||
|
||||
def assemble_message(self):
|
||||
|
||||
messages = []
|
||||
|
||||
if self.paramater:
|
||||
messages.append(
|
||||
'Errors encountered in attempting to un-marshal %s:' % self.paramater
|
||||
)
|
||||
|
||||
if self.index is not None:
|
||||
messages.append(
|
||||
'Errors encountered in attempting to un-marshal the value at index %s:' % repr(self.index)
|
||||
)
|
||||
|
||||
if self.message:
|
||||
messages.append(self.message)
|
||||
|
||||
super().__init__('\n'.join(messages))
|
||||
|
||||
|
||||
class UnmarshalTypeError(UnmarshalError, TypeError):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UnmarshalValueError(UnmarshalError, ValueError):
|
||||
|
||||
pass
|
||||
@@ -1,258 +0,0 @@
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
from .utilities.compatibility import backport
|
||||
|
||||
backport()
|
||||
|
||||
import serial.abc
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
import serial
|
||||
from .utilities import qualified_name
|
||||
from .abc.model import Model
|
||||
|
||||
try:
|
||||
import typing
|
||||
except ImportError as e:
|
||||
typing = None
|
||||
|
||||
|
||||
class Hooks(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
before_marshal=None, # Optional[Callable]
|
||||
after_marshal=None, # Optional[Callable]
|
||||
before_unmarshal=None, # Optional[Callable]
|
||||
after_unmarshal=None, # Optional[Callable]
|
||||
before_serialize=None, # Optional[Callable]
|
||||
after_serialize=None, # Optional[Callable]
|
||||
before_deserialize=None, # Optional[Callable]
|
||||
after_deserialize=None, # Optional[Callable]
|
||||
before_validate=None, # Optional[Callable]
|
||||
after_validate=None, # Optional[Callable]
|
||||
):
|
||||
self.before_marshal = before_marshal
|
||||
self.after_marshal = after_marshal
|
||||
self.before_unmarshal = before_unmarshal
|
||||
self.after_unmarshal = after_unmarshal
|
||||
self.before_serialize = before_serialize
|
||||
self.after_serialize = after_serialize
|
||||
self.before_deserialize = before_deserialize
|
||||
self.after_deserialize = after_deserialize
|
||||
self.before_validate = before_validate
|
||||
self.after_validate = after_validate
|
||||
|
||||
def __copy__(self):
|
||||
return self.__class__(**vars(self))
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
# type: (dict) -> Memo
|
||||
return self.__class__(**{
|
||||
k: deepcopy(v, memo=memo)
|
||||
for k, v in vars(self).items()
|
||||
})
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
|
||||
class Object(Hooks):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
before_marshal=None, # Optional[Callable]
|
||||
after_marshal=None, # Optional[Callable]
|
||||
before_unmarshal=None, # Optional[Callable]
|
||||
after_unmarshal=None, # Optional[Callable]
|
||||
before_serialize=None, # Optional[Callable]
|
||||
after_serialize=None, # Optional[Callable]
|
||||
before_deserialize=None, # Optional[Callable]
|
||||
after_deserialize=None, # Optional[Callable]
|
||||
before_validate=None, # Optional[Callable]
|
||||
after_validate=None, # Optional[Callable]
|
||||
before_setattr=None, # Optional[Callable]
|
||||
after_setattr=None, # Optional[Callable]
|
||||
before_setitem=None, # Optional[Callable]
|
||||
after_setitem=None, # Optional[Callable]
|
||||
):
|
||||
self.before_marshal = before_marshal
|
||||
self.after_marshal = after_marshal
|
||||
self.before_unmarshal = before_unmarshal
|
||||
self.after_unmarshal = after_unmarshal
|
||||
self.before_serialize = before_serialize
|
||||
self.after_serialize = after_serialize
|
||||
self.before_deserialize = before_deserialize
|
||||
self.after_deserialize = after_deserialize
|
||||
self.before_validate = before_validate
|
||||
self.after_validate = after_validate
|
||||
self.before_setattr = before_setattr
|
||||
self.after_setattr = after_setattr
|
||||
self.before_setitem = before_setitem
|
||||
self.after_setitem = after_setitem
|
||||
|
||||
|
||||
class Array(Hooks):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
before_marshal=None, # Optional[Callable]
|
||||
after_marshal=None, # Optional[Callable]
|
||||
before_unmarshal=None, # Optional[Callable]
|
||||
after_unmarshal=None, # Optional[Callable]
|
||||
before_serialize=None, # Optional[Callable]
|
||||
after_serialize=None, # Optional[Callable]
|
||||
before_deserialize=None, # Optional[Callable]
|
||||
after_deserialize=None, # Optional[Callable]
|
||||
before_validate=None, # Optional[Callable]
|
||||
after_validate=None, # Optional[Callable]
|
||||
before_setitem=None, # Optional[Callable]
|
||||
after_setitem=None, # Optional[Callable]
|
||||
before_append=None, # Optional[Callable]
|
||||
after_append=None, # Optional[Callable]
|
||||
):
|
||||
self.before_marshal = before_marshal
|
||||
self.after_marshal = after_marshal
|
||||
self.before_unmarshal = before_unmarshal
|
||||
self.after_unmarshal = after_unmarshal
|
||||
self.before_serialize = before_serialize
|
||||
self.after_serialize = after_serialize
|
||||
self.before_deserialize = before_deserialize
|
||||
self.after_deserialize = after_deserialize
|
||||
self.before_validate = before_validate
|
||||
self.after_validate = after_validate
|
||||
self.before_setitem = before_setitem
|
||||
self.after_setitem = after_setitem
|
||||
self.before_append = before_append
|
||||
self.after_append = after_append
|
||||
|
||||
|
||||
class Dictionary(Hooks):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
before_marshal=None, # Optional[Callable]
|
||||
after_marshal=None, # Optional[Callable]
|
||||
before_unmarshal=None, # Optional[Callable]
|
||||
after_unmarshal=None, # Optional[Callable]
|
||||
before_serialize=None, # Optional[Callable]
|
||||
after_serialize=None, # Optional[Callable]
|
||||
before_deserialize=None, # Optional[Callable]
|
||||
after_deserialize=None, # Optional[Callable]
|
||||
before_validate=None, # Optional[Callable]
|
||||
after_validate=None, # Optional[Callable]
|
||||
before_setitem=None, # Optional[Callable]
|
||||
after_setitem=None, # Optional[Callable]
|
||||
):
|
||||
self.before_marshal = before_marshal
|
||||
self.after_marshal = after_marshal
|
||||
self.before_unmarshal = before_unmarshal
|
||||
self.after_unmarshal = after_unmarshal
|
||||
self.before_serialize = before_serialize
|
||||
self.after_serialize = after_serialize
|
||||
self.before_deserialize = before_deserialize
|
||||
self.after_deserialize = after_deserialize
|
||||
self.before_validate = before_validate
|
||||
self.after_validate = after_validate
|
||||
self.before_setitem = before_setitem
|
||||
self.after_setitem = after_setitem
|
||||
|
||||
|
||||
def read(
|
||||
model_instance # type: Union[type, serial.abc.model.Model]
|
||||
):
|
||||
# type: (...) -> Hooks
|
||||
"""
|
||||
Read metadata from a model instance (the returned metadata may be inherited, and therefore should not be written to)
|
||||
"""
|
||||
|
||||
if isinstance(model_instance, type):
|
||||
return model_instance._hooks
|
||||
elif isinstance(model_instance, Model):
|
||||
return model_instance._hooks or read(type(model_instance))
|
||||
|
||||
|
||||
def writable(
|
||||
model_instance # type: Union[type, serial.abc.model.Model]
|
||||
):
|
||||
# type: (...) -> Hooks
|
||||
"""
|
||||
Retrieve a metadata instance. If the instance currently inherits its metadata from a class or superclass,
|
||||
this funtion will copy that metadata and assign it directly to the model instance.
|
||||
"""
|
||||
|
||||
if isinstance(model_instance, type):
|
||||
|
||||
if model_instance._hooks is None:
|
||||
|
||||
model_instance._hooks = (
|
||||
Object()
|
||||
if issubclass(model_instance, serial.model.Object) else
|
||||
Array()
|
||||
if issubclass(model_instance, serial.model.Array) else
|
||||
Dictionary()
|
||||
if issubclass(model_instance, serial.model.Dictionary)
|
||||
else None
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
for b in model_instance.__bases__:
|
||||
|
||||
if hasattr(b, '_hooks') and (model_instance._hooks is b._hooks):
|
||||
model_instance._hooks = deepcopy(model_instance._hooks)
|
||||
break
|
||||
|
||||
elif isinstance(model_instance, Model):
|
||||
|
||||
if model_instance._hooks is None:
|
||||
model_instance._hooks = deepcopy(writable(type(model_instance)))
|
||||
|
||||
return model_instance._hooks
|
||||
|
||||
|
||||
def write(
|
||||
model_instance, # type: Union[type, serial.abc.model.Model]
|
||||
meta # type: Hooks
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Write metadata to a class or instance
|
||||
"""
|
||||
|
||||
if isinstance(model_instance, type):
|
||||
|
||||
t = model_instance
|
||||
mt = (
|
||||
Object
|
||||
if issubclass(model_instance, serial.model.Object) else
|
||||
Array
|
||||
if issubclass(model_instance, serial.model.Array) else
|
||||
Dictionary
|
||||
if issubclass(model_instance, serial.model.Dictionary)
|
||||
else None
|
||||
)
|
||||
|
||||
elif isinstance(model_instance, Model):
|
||||
|
||||
t = type(model_instance)
|
||||
mt = (
|
||||
Object
|
||||
if isinstance(model_instance, serial.model.Object) else
|
||||
Array
|
||||
if isinstance(model_instance, serial.model.Array) else
|
||||
Dictionary
|
||||
if isinstance(model_instance, serial.model.Dictionary)
|
||||
else None
|
||||
)
|
||||
|
||||
if not isinstance(meta, mt):
|
||||
raise ValueError(
|
||||
'Hooks assigned to `%s` must be of type `%s`' % (
|
||||
qualified_name(t),
|
||||
qualified_name(mt)
|
||||
)
|
||||
)
|
||||
|
||||
model_instance._hooks = meta
|
||||
@@ -1,704 +0,0 @@
|
||||
# Tell the linters what's up:
|
||||
# pylint:disable=wrong-import-position,consider-using-enumerate,useless-object-inheritance
|
||||
# mccabe:options:max-complexity=999
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
|
||||
from .utilities.compatibility import backport
|
||||
|
||||
backport() # noqa
|
||||
|
||||
from future.utils import native_str
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from base64 import b64encode
|
||||
from numbers import Number
|
||||
|
||||
import yaml
|
||||
from datetime import date, datetime
|
||||
|
||||
from itertools import chain
|
||||
|
||||
import serial
|
||||
from . import utilities, abc, properties, errors, meta, hooks
|
||||
from .utilities import Generator, qualified_name, read, collections, collections_abc
|
||||
|
||||
|
||||
try:
|
||||
from typing import Union, Optional, Sequence, Any, Callable
|
||||
except ImportError:
|
||||
Union = Optional = Sequence = Any = Callable = None
|
||||
|
||||
try:
|
||||
from abc import ABC
|
||||
except ImportError:
|
||||
ABC = None
|
||||
|
||||
|
||||
UNMARSHALLABLE_TYPES = tuple({
|
||||
str, bytes, native_str, Number, Decimal, date, datetime, bool,
|
||||
dict, collections.OrderedDict,
|
||||
collections_abc.Set, collections_abc.Sequence, Generator,
|
||||
abc.model.Model, properties.Null, properties.NoneType
|
||||
})
|
||||
|
||||
|
||||
def ab2c(abc_or_property):
|
||||
# type: (ABC) -> Union[model.Object, model.Dict, model.Array, properties.Property]
|
||||
"""
|
||||
Get the corresponding model class from an abstract base class, or if none exists--return the original class or type
|
||||
"""
|
||||
|
||||
class_or_property = abc_or_property # type: (type)
|
||||
|
||||
if isinstance(abc_or_property, type):
|
||||
|
||||
if abc_or_property in {abc.model.Dictionary, abc.model.Array, abc.model.Object}:
|
||||
|
||||
class_or_property = getattr(
|
||||
serial.model,
|
||||
abc_or_property.__name__.split('.')[-1]
|
||||
)
|
||||
|
||||
elif isinstance(abc_or_property, properties.Property):
|
||||
|
||||
class_or_property = deepcopy(abc_or_property)
|
||||
|
||||
for types_attribute in ('types', 'value_types', 'item_types'):
|
||||
|
||||
if hasattr(class_or_property, types_attribute):
|
||||
|
||||
setattr(
|
||||
class_or_property,
|
||||
types_attribute,
|
||||
tuple(
|
||||
ab2c(type_) for type_ in getattr(class_or_property, types_attribute)
|
||||
)
|
||||
)
|
||||
|
||||
return class_or_property
|
||||
|
||||
|
||||
def ab2cs(abcs_or_properties):
|
||||
# type: (Sequence[Union[ABC, properties.Property]]) -> Sequence[model.Model, properties.Property]
|
||||
for abc_or_property in abcs_or_properties:
|
||||
yield ab2c(abc_or_property)
|
||||
|
||||
|
||||
class _Marshal(object):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def marshal(
|
||||
data, # type: Any
|
||||
types=None, # type: Optional[Sequence[Union[type, properties.Property, Callable]]]
|
||||
value_types=None, # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
item_types=None, # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
):
|
||||
# type: (...) -> Any
|
||||
|
||||
"""
|
||||
Recursively converts instances of `serial.abc.model.Model` into JSON/YAML/XML serializable objects.
|
||||
"""
|
||||
|
||||
if hasattr(data, '_marshal'):
|
||||
return data._marshal() # noqa - this is *our* protected member, so linters can buzz off
|
||||
|
||||
if data is None:
|
||||
return data
|
||||
|
||||
if callable(types):
|
||||
types = types(data)
|
||||
|
||||
# If data types have been provided, validate the un-marshalled data by attempting to initialize the provided type(s)
|
||||
# with `data`
|
||||
|
||||
if types is not None:
|
||||
|
||||
if (str in types) and (native_str is not str) and (native_str not in types):
|
||||
|
||||
types = tuple(chain(*(
|
||||
((type_, native_str) if (type_ is str) else (type_,))
|
||||
for type_ in types
|
||||
)))
|
||||
|
||||
matched = False
|
||||
|
||||
for type_ in types:
|
||||
|
||||
if isinstance(type_, properties.Property):
|
||||
|
||||
try:
|
||||
data = type_.marshal(data)
|
||||
matched = True
|
||||
break
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
elif isinstance(type_, type) and isinstance(data, type_):
|
||||
|
||||
matched = True
|
||||
|
||||
break
|
||||
|
||||
if not matched:
|
||||
raise TypeError(
|
||||
'%s cannot be interpreted as any of the designated types: %s' % (
|
||||
repr(data),
|
||||
repr(types)
|
||||
)
|
||||
)
|
||||
|
||||
if value_types is not None:
|
||||
for k, v in data.items():
|
||||
data[k] = marshal(v, types=value_types)
|
||||
|
||||
if item_types is not None:
|
||||
|
||||
for i in range(len(data)):
|
||||
data[i] = marshal(data[i], types=item_types)
|
||||
|
||||
if isinstance(data, Decimal):
|
||||
return float(data)
|
||||
|
||||
if isinstance(data, (date, datetime)):
|
||||
return data.isoformat()
|
||||
|
||||
if isinstance(data, native_str):
|
||||
return data
|
||||
|
||||
if isinstance(data, (bytes, bytearray)):
|
||||
return str(b64encode(data), 'ascii')
|
||||
|
||||
if hasattr(data, '__bytes__'):
|
||||
return str(b64encode(bytes(data)), 'ascii')
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class _Unmarshal(object):
|
||||
"""
|
||||
This class should be used exclusively by `serial.marshal.unmarshal`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data, # type: Any
|
||||
types=None, # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
value_types=None, # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
item_types=None # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
):
|
||||
# type: (...) -> None
|
||||
|
||||
if not isinstance(
|
||||
data,
|
||||
UNMARSHALLABLE_TYPES
|
||||
):
|
||||
# Verify that the data can be parsed before attempting to un-marshall it
|
||||
|
||||
raise errors.UnmarshalTypeError(
|
||||
'%s, an instance of `%s`, cannot be un-marshalled. ' % (repr(data), type(data).__name__) +
|
||||
'Acceptable types are: ' + ', '.join((
|
||||
qualified_name(data_type)
|
||||
for data_type in UNMARSHALLABLE_TYPES
|
||||
))
|
||||
)
|
||||
|
||||
# If only one type was passed for any of the following parameters--we convert it to a tuple
|
||||
# If any parameters are abstract base classes--we convert them to the corresponding models
|
||||
|
||||
if types is not None:
|
||||
|
||||
if not isinstance(types, collections_abc.Sequence):
|
||||
types = (types,)
|
||||
|
||||
if value_types is not None:
|
||||
|
||||
if not isinstance(value_types, collections_abc.Sequence):
|
||||
value_types = (value_types,)
|
||||
|
||||
if item_types is not None:
|
||||
|
||||
if not isinstance(item_types, collections_abc.Sequence):
|
||||
item_types = (item_types,)
|
||||
|
||||
# Member data
|
||||
|
||||
self.data = data # type: Any
|
||||
self.types = types # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
self.value_types = value_types # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
self.item_types = item_types # type: Optional[Sequence[Union[type, properties.Property]]]
|
||||
self.meta = None # type: Optional[meta.Meta]
|
||||
|
||||
def __call__(self):
|
||||
# type: (...) -> Any
|
||||
"""
|
||||
Return `self.data` unmarshalled
|
||||
"""
|
||||
|
||||
unmarshalled_data = self.data
|
||||
|
||||
if (
|
||||
(self.data is not None) and
|
||||
(self.data is not properties.NULL)
|
||||
):
|
||||
# If the data is a serial `Model`, get it's metadata
|
||||
if isinstance(self.data, abc.model.Model):
|
||||
|
||||
self.meta = meta.read(self.data)
|
||||
|
||||
if self.meta is None: # Only un-marshall models if they have no metadata yet (are generic)
|
||||
|
||||
# If `types` is a function, it should be one which expects to receive marshalled data and returns a list
|
||||
# of types which are applicable
|
||||
if callable(self.types):
|
||||
self.types = self.types(self.data)
|
||||
|
||||
# If the data provided is a `Generator`, make it static by casting the data into a tuple
|
||||
if isinstance(self.data, Generator):
|
||||
self.data = tuple(self.data)
|
||||
|
||||
if self.types is None:
|
||||
|
||||
# If no types are provided, we unmarshal the data into one of serial's generic container types
|
||||
unmarshalled_data = self.as_container_or_simple_type
|
||||
|
||||
else:
|
||||
|
||||
self.backport_types()
|
||||
|
||||
unmarshalled_data = None
|
||||
successfully_unmarshalled = False
|
||||
|
||||
first_error = None # type: Optional[Exception]
|
||||
|
||||
# Attempt to un-marshal the data as each type, in the order provided
|
||||
for type_ in self.types:
|
||||
|
||||
error = None # type: Optional[Union[AttributeError, KeyError, TypeError, ValueError]]
|
||||
|
||||
try:
|
||||
|
||||
unmarshalled_data = self.as_type(type_)
|
||||
|
||||
# if (self.data is not None) and (unmarshalled_data is None):
|
||||
# raise RuntimeError(self.data)
|
||||
|
||||
# If the data is un-marshalled successfully, we do not need to try any further types
|
||||
successfully_unmarshalled = True
|
||||
break
|
||||
|
||||
except (AttributeError, KeyError, TypeError, ValueError) as e:
|
||||
|
||||
error = e
|
||||
|
||||
if (first_error is None) and (error is not None):
|
||||
first_error = error
|
||||
|
||||
if not successfully_unmarshalled:
|
||||
|
||||
if (first_error is None) or isinstance(first_error, TypeError):
|
||||
|
||||
raise errors.UnmarshalTypeError(
|
||||
self.data,
|
||||
types=self.types,
|
||||
value_types=self.value_types,
|
||||
item_types=self.item_types
|
||||
)
|
||||
|
||||
elif isinstance(first_error, ValueError):
|
||||
|
||||
raise errors.UnmarshalValueError(
|
||||
self.data,
|
||||
types=self.types,
|
||||
value_types=self.value_types,
|
||||
item_types=self.item_types
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
raise first_error # noqa - pylint erroneously identifies this as raising `None`
|
||||
|
||||
return unmarshalled_data
|
||||
|
||||
@property
|
||||
def as_container_or_simple_type(self):
|
||||
# type: (...) -> Any
|
||||
|
||||
"""
|
||||
This function unmarshalls and returns the data into one of serial's container types, or if the data is of a
|
||||
simple data type--it returns that data unmodified
|
||||
"""
|
||||
|
||||
unmarshalled_data = self.data
|
||||
|
||||
if isinstance(self.data, abc.model.Dictionary):
|
||||
|
||||
type_ = type(self.data())
|
||||
|
||||
if self.value_types is not None:
|
||||
unmarshalled_data = type_(self.data, value_types=self.value_types)
|
||||
|
||||
elif isinstance(self.data, abc.model.Array):
|
||||
|
||||
if self.item_types is not None:
|
||||
unmarshalled_data = serial.model.Array(self.data, item_types=self.item_types)
|
||||
|
||||
elif isinstance(self.data, (dict, collections.OrderedDict)):
|
||||
|
||||
unmarshalled_data = serial.model.Dictionary(self.data, value_types=self.value_types)
|
||||
|
||||
elif (
|
||||
isinstance(self.data, (collections_abc.Set, collections_abc.Sequence))
|
||||
) and (
|
||||
not isinstance(self.data, (str, bytes, native_str))
|
||||
):
|
||||
|
||||
unmarshalled_data = serial.model.Array(self.data, item_types=self.item_types)
|
||||
|
||||
elif not isinstance(self.data, (str, bytes, native_str, Number, Decimal, date, datetime, bool, abc.model.Model)):
|
||||
|
||||
raise errors.UnmarshalValueError(
|
||||
'%s cannot be un-marshalled' % repr(self.data)
|
||||
)
|
||||
|
||||
return unmarshalled_data
|
||||
|
||||
def backport_types(self):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
This examines a set of types passed to `unmarshal`, and resolves any compatibility issues with the python
|
||||
version being utilized
|
||||
"""
|
||||
|
||||
if (str in self.types) and (native_str is not str) and (native_str not in self.types):
|
||||
|
||||
self.types = tuple(chain(*(
|
||||
((type_, native_str) if (type_ is str) else (type_,))
|
||||
for type_ in self.types
|
||||
))) # type: Tuple[Union[type, properties.Property], ...]
|
||||
|
||||
def as_type(
|
||||
self,
|
||||
type_, # type: Union[type, properties.Property]
|
||||
):
|
||||
# type: (...) -> bool
|
||||
|
||||
unmarshalled_data = None # type: Union[abc.model.Model, Number, str, bytes, date, datetime]
|
||||
|
||||
if isinstance(
|
||||
type_,
|
||||
properties.Property
|
||||
):
|
||||
|
||||
unmarshalled_data = type_.unmarshal(self.data)
|
||||
|
||||
elif isinstance(type_, type):
|
||||
|
||||
if isinstance(
|
||||
self.data,
|
||||
(dict, collections.OrderedDict, abc.model.Model)
|
||||
):
|
||||
|
||||
if issubclass(type_, abc.model.Object):
|
||||
|
||||
unmarshalled_data = type_(self.data)
|
||||
|
||||
elif issubclass(
|
||||
type_,
|
||||
abc.model.Dictionary
|
||||
):
|
||||
|
||||
unmarshalled_data = type_(self.data, value_types=self.value_types)
|
||||
|
||||
elif issubclass(
|
||||
type_,
|
||||
(dict, collections.OrderedDict)
|
||||
):
|
||||
|
||||
unmarshalled_data = serial.model.Dictionary(self.data, value_types=self.value_types)
|
||||
|
||||
else:
|
||||
|
||||
raise TypeError(self.data)
|
||||
|
||||
elif (
|
||||
isinstance(self.data, (collections_abc.Set, collections_abc.Sequence, abc.model.Array)) and
|
||||
(not isinstance(self.data, (str, bytes, native_str)))
|
||||
):
|
||||
|
||||
if issubclass(type_, abc.model.Array):
|
||||
|
||||
unmarshalled_data = type_(self.data, item_types=self.item_types)
|
||||
|
||||
elif issubclass(
|
||||
type_,
|
||||
(collections_abc.Set, collections_abc.Sequence)
|
||||
) and not issubclass(
|
||||
type_,
|
||||
(str, bytes, native_str)
|
||||
):
|
||||
|
||||
unmarshalled_data = serial.model.Array(self.data, item_types=self.item_types)
|
||||
|
||||
else:
|
||||
|
||||
raise TypeError('%s is not of type `%s`' % (repr(self.data), repr(type_)))
|
||||
|
||||
elif isinstance(self.data, type_):
|
||||
|
||||
if isinstance(self.data, Decimal):
|
||||
unmarshalled_data = float(self.data)
|
||||
else:
|
||||
unmarshalled_data = self.data
|
||||
|
||||
else:
|
||||
|
||||
raise TypeError(self.data)
|
||||
|
||||
return unmarshalled_data
|
||||
|
||||
|
||||
def unmarshal(
|
||||
data, # type: Any
|
||||
types=None, # type: Optional[Union[Sequence[Union[type, properties.Property]], type, properties.Property]]
|
||||
value_types=None, # type: Optional[Union[Sequence[Union[type, properties.Property]], type, properties.Property]]
|
||||
item_types=None, # type: Optional[Union[Sequence[Union[type, properties.Property]], type, properties.Property]]
|
||||
):
|
||||
# type: (...) -> Optional[Union[abc.model., str, Number, date, datetime]]
|
||||
"""
|
||||
Converts `data` into an instance of a serial model, and recursively does the same for all member data.
|
||||
|
||||
Parameters:
|
||||
|
||||
- data ([type|serial.properties.Property]): One or more data types. Each type
|
||||
|
||||
This is done by attempting to cast that data into a series of `types`.
|
||||
|
||||
to "un-marshal" data which has been deserialized from bytes or text, but is still represented
|
||||
by generic containers
|
||||
"""
|
||||
|
||||
unmarshalled_data = _Unmarshal(
|
||||
data,
|
||||
types=types,
|
||||
value_types=value_types,
|
||||
item_types=item_types
|
||||
)()
|
||||
|
||||
return unmarshalled_data
|
||||
|
||||
|
||||
def serialize(data, format_='json'):
|
||||
# type: (Union[abc.model.Model, str, Number], Optional[str]) -> str
|
||||
"""
|
||||
Serializes instances of `serial.model.Object` as JSON or YAML.
|
||||
"""
|
||||
instance_hooks = None
|
||||
|
||||
if isinstance(data, abc.model.Model):
|
||||
|
||||
instance_hooks = hooks.read(data)
|
||||
|
||||
if (instance_hooks is not None) and (instance_hooks.before_serialize is not None):
|
||||
data = instance_hooks.before_serialize(data)
|
||||
|
||||
if format_ not in ('json', 'yaml'): # , 'xml'
|
||||
|
||||
format_ = format_.lower()
|
||||
|
||||
if format_ not in ('json', 'yaml'):
|
||||
|
||||
raise ValueError(
|
||||
'Supported `serial.model.serialize()` `format_` values include "json" and "yaml" (not "%s").' %
|
||||
format_
|
||||
)
|
||||
|
||||
if format_ == 'json':
|
||||
data = json.dumps(marshal(data))
|
||||
elif format_ == 'yaml':
|
||||
data = yaml.dump(marshal(data))
|
||||
|
||||
if (instance_hooks is not None) and (instance_hooks.after_serialize is not None):
|
||||
data = instance_hooks.after_serialize(data)
|
||||
|
||||
if not isinstance(data, str):
|
||||
if isinstance(data, native_str):
|
||||
data = str(data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def deserialize(data, format_):
|
||||
# type: (Optional[Union[str, IOBase, addbase]], str) -> Any
|
||||
"""
|
||||
Parameters:
|
||||
|
||||
- data (str|io.IOBase|io.addbase):
|
||||
|
||||
This can be a string or file-like object containing JSON, YAML, or XML serialized inforation.
|
||||
|
||||
- format_ (str):
|
||||
|
||||
This can be "json", "yaml" or "xml".
|
||||
|
||||
Returns:
|
||||
|
||||
A deserialized representation of the information you provided.
|
||||
"""
|
||||
if format_ not in ('json', 'yaml'): # , 'xml'
|
||||
raise NotImplementedError(
|
||||
'Deserialization of data in the format %s is not currently supported.' % repr(format_)
|
||||
)
|
||||
if not isinstance(data, (str, bytes)):
|
||||
data = read(data)
|
||||
if isinstance(data, bytes):
|
||||
data = str(data, encoding='utf-8')
|
||||
if isinstance(data, str):
|
||||
if format_ == 'json':
|
||||
data = json.loads(
|
||||
data,
|
||||
object_hook=collections.OrderedDict,
|
||||
object_pairs_hook=collections.OrderedDict
|
||||
)
|
||||
elif format_ == 'yaml':
|
||||
data = yaml.load(data)
|
||||
return data
|
||||
|
||||
|
||||
def detect_format(data):
|
||||
# type: (Optional[Union[str, IOBase, addbase]]) -> Tuple[Any, str]
|
||||
"""
|
||||
Parameters:
|
||||
|
||||
- data (str|io.IOBase|io.addbase):
|
||||
|
||||
This can be a string or file-like object containing JSON, YAML, or XML serialized inforation.
|
||||
|
||||
Returns:
|
||||
|
||||
A tuple containing the deserialized information and a string indicating the format of that information.
|
||||
"""
|
||||
if not isinstance(data, str):
|
||||
try:
|
||||
data = utilities.read(data)
|
||||
except TypeError:
|
||||
return data, None
|
||||
formats = ('json', 'yaml') # , 'xml'
|
||||
format_ = None
|
||||
for potential_format in formats:
|
||||
try:
|
||||
data = deserialize(data, potential_format)
|
||||
format_ = potential_format
|
||||
break
|
||||
except (ValueError, yaml.YAMLError):
|
||||
pass
|
||||
if format is None:
|
||||
raise ValueError(
|
||||
'The data provided could not be parsed:\n' + repr(data)
|
||||
)
|
||||
return data, format_
|
||||
|
||||
|
||||
def validate(
|
||||
data, # type: Optional[abc.model.Model]
|
||||
types=None, # type: Optional[Union[type, properties.Property, model.Object, Callable]]
|
||||
raise_errors=True # type: bool
|
||||
):
|
||||
# type: (...) -> Sequence[str]
|
||||
"""
|
||||
This function verifies that all properties/items/values in an instance of `serial.abc.model.Model` are of the
|
||||
correct data type(s), and that all required attributes are present (if applicable). If `raise_errors` is `True`
|
||||
(this is the default)--violations will result in a validation error. If `raise_errors` is `False`--a list of error
|
||||
messages will be returned if invalid/missing information is found, or an empty list otherwise.
|
||||
"""
|
||||
|
||||
if isinstance(data, Generator):
|
||||
data = tuple(data)
|
||||
|
||||
error_messages = []
|
||||
|
||||
error_message = None
|
||||
|
||||
if types is not None:
|
||||
|
||||
if callable(types):
|
||||
types = types(data)
|
||||
|
||||
if (str in types) and (native_str is not str) and (native_str not in types):
|
||||
|
||||
types = tuple(chain(*(
|
||||
((type_, native_str) if (type_ is str) else (type_,))
|
||||
for type_ in types
|
||||
)))
|
||||
|
||||
valid = False
|
||||
|
||||
for type_ in types:
|
||||
|
||||
if isinstance(type_, type) and isinstance(data, type_):
|
||||
|
||||
valid = True
|
||||
break
|
||||
|
||||
elif isinstance(type_, properties.Property):
|
||||
|
||||
if type_.types is None:
|
||||
|
||||
valid = True
|
||||
break
|
||||
|
||||
try:
|
||||
|
||||
validate(data, type_.types, raise_errors=True)
|
||||
valid = True
|
||||
break
|
||||
|
||||
except errors.ValidationError:
|
||||
|
||||
pass
|
||||
|
||||
if not valid:
|
||||
|
||||
error_message = (
|
||||
'Invalid data:\n\n%s\n\nThe data must be one of the following types:\n\n%s' % (
|
||||
'\n'.join(
|
||||
' ' + line
|
||||
for line in repr(data).split('\n')
|
||||
),
|
||||
'\n'.join(chain(
|
||||
(' (',),
|
||||
(
|
||||
' %s,' % '\n'.join(
|
||||
' ' + line
|
||||
for line in repr(type_).split('\n')
|
||||
).strip()
|
||||
for type_ in types
|
||||
),
|
||||
(' )',)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
if error_message is not None:
|
||||
|
||||
if (not error_messages) or (error_message not in error_messages):
|
||||
|
||||
error_messages.append(error_message)
|
||||
|
||||
if ('_validate' in dir(data)) and callable(data._validate):
|
||||
|
||||
error_messages.extend(
|
||||
error_message for error_message in
|
||||
data._validate(raise_errors=False)
|
||||
if error_message not in error_messages
|
||||
)
|
||||
|
||||
if raise_errors and error_messages:
|
||||
raise errors.ValidationError('\n' + '\n\n'.join(error_messages))
|
||||
|
||||
return error_messages
|
||||
@@ -1,812 +0,0 @@
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
from .utilities.compatibility import backport
|
||||
|
||||
backport() # noqa
|
||||
|
||||
from future.utils import native_str
|
||||
|
||||
import numbers
|
||||
import operator
|
||||
import re
|
||||
import collections
|
||||
from collections import OrderedDict
|
||||
from copy import copy, deepcopy
|
||||
from itertools import chain
|
||||
from numbers import Number
|
||||
|
||||
try:
|
||||
from typing import Optional, Dict, Sequence, Tuple, Mapping, Union, Any, List
|
||||
except ImportError:
|
||||
Optional = Sequence = Dict = Tuple = Mapping = Union = Any = List = None
|
||||
|
||||
import serial
|
||||
from serial.utilities import qualified_name, properties_values, collections_abc
|
||||
from serial.abc.model import Model
|
||||
from serial.abc.properties import Property
|
||||
|
||||
_DOT_SYNTAX_RE = re.compile(
|
||||
r'^\d+(\.\d+)*$'
|
||||
)
|
||||
|
||||
|
||||
class Meta(object):
|
||||
|
||||
def __copy__(self):
|
||||
new_instance = self.__class__()
|
||||
for a in dir(self):
|
||||
if a[0] != '_':
|
||||
v = getattr(self, a)
|
||||
if not isinstance(v, collections.Callable):
|
||||
setattr(new_instance, a, v)
|
||||
return new_instance
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
# type: (Optional[dict]) -> Meta
|
||||
new_instance = self.__class__()
|
||||
for a, v in properties_values(self):
|
||||
setattr(new_instance, a, deepcopy(v, memo=memo))
|
||||
return new_instance
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return ('\n'.join(
|
||||
['%s(' % qualified_name(type(self))] +
|
||||
[
|
||||
' %s=%s,' % (p, repr(v))
|
||||
for p, v in properties_values(self)
|
||||
] +
|
||||
[')']
|
||||
))
|
||||
|
||||
|
||||
class Version(Meta):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
version_number=None, # type: Optional[str]
|
||||
specification=None, # type: Optional[Sequence[str]]
|
||||
equals=None, # type: Optional[Sequence[Union[str, Number]]]
|
||||
not_equals=None, # type: Optional[Sequence[Union[str, Number]]]
|
||||
less_than=None, # type: Optional[Sequence[Union[str, Number]]]
|
||||
less_than_or_equal_to=None, # type: Optional[Sequence[Union[str, Number]]]
|
||||
greater_than=None, # type: Optional[Sequence[Union[str, Number]]]
|
||||
greater_than_or_equal_to=None, # type: Optional[Sequence[Union[str, Number]]]
|
||||
):
|
||||
if isinstance(version_number, str) and (
|
||||
(specification is None) and
|
||||
(equals is None) and
|
||||
(not_equals is None) and
|
||||
(less_than is None) and
|
||||
(less_than_or_equal_to is None) and
|
||||
(greater_than is None) and
|
||||
(greater_than_or_equal_to is None)
|
||||
):
|
||||
specification = None
|
||||
for s in version_number.split('&'):
|
||||
if '==' in s:
|
||||
s, equals = s.split('==')
|
||||
elif '<=' in s:
|
||||
s, less_than_or_equal_to = s.split('<=')
|
||||
elif '>=' in s:
|
||||
s, greater_than_or_equal_to = s.split('>=')
|
||||
elif '<' in s:
|
||||
s, less_than = s.split('<')
|
||||
elif '>' in s:
|
||||
s, greater_than = s.split('>')
|
||||
elif '!=' in s:
|
||||
s, not_equals = s.split('!=')
|
||||
elif '=' in s:
|
||||
s, equals = s.split('=')
|
||||
if specification:
|
||||
if s != specification:
|
||||
raise ValueError(
|
||||
'Multiple specifications cannot be associated with an instance of ' +
|
||||
'`serial.meta.Version`: ' + repr(version_number)
|
||||
)
|
||||
elif s:
|
||||
specification = s
|
||||
self.specification = specification
|
||||
self.equals = equals
|
||||
self.not_equals = not_equals
|
||||
self.less_than = less_than
|
||||
self.less_than_or_equal_to = less_than_or_equal_to
|
||||
self.greater_than = greater_than
|
||||
self.greater_than_or_equal_to = greater_than_or_equal_to
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (Any) -> bool
|
||||
|
||||
compare_properties_functions = (
|
||||
('equals', operator.eq),
|
||||
('not_equals', operator.ne),
|
||||
('less_than', operator.lt),
|
||||
('less_than_or_equal_to', operator.le),
|
||||
('greater_than', operator.gt),
|
||||
('greater_than_or_equal_to', operator.ge),
|
||||
)
|
||||
|
||||
if (
|
||||
(isinstance(other, str) and _DOT_SYNTAX_RE.match(other)) or
|
||||
isinstance(other, (collections_abc.Sequence, int))
|
||||
):
|
||||
if isinstance(other, (native_str, bytes, numbers.Number)):
|
||||
other = str(other)
|
||||
if isinstance(other, str):
|
||||
|
||||
other = other.rstrip('.0')
|
||||
if other == '':
|
||||
other_components = (0,)
|
||||
else:
|
||||
other_components = tuple(int(other_component) for other_component in other.split('.'))
|
||||
else:
|
||||
other_components = tuple(other)
|
||||
for compare_property, compare_function in compare_properties_functions:
|
||||
compare_value = getattr(self, compare_property)
|
||||
if compare_value is not None:
|
||||
compare_values = tuple(int(n) for n in compare_value.split('.'))
|
||||
other_values = copy(other_components)
|
||||
ld = len(other_values) - len(compare_values)
|
||||
if ld < 0:
|
||||
other_values = tuple(chain(other_values, [0] * (-ld)))
|
||||
elif ld > 0:
|
||||
compare_values = tuple(chain(compare_values, [0] * ld))
|
||||
if not compare_function(other_values, compare_values):
|
||||
return False
|
||||
else:
|
||||
for compare_property, compare_function in compare_properties_functions:
|
||||
compare_value = getattr(self, compare_property)
|
||||
if (compare_value is not None) and not compare_function(other, compare_value):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
representation = []
|
||||
for property, operator in (
|
||||
('equals', '=='),
|
||||
('not_equals', '!='),
|
||||
('greater_than', '>'),
|
||||
('greater_than_or_equal_to', '>='),
|
||||
('less_than', '<'),
|
||||
('less_than_or_equal_to', '<='),
|
||||
):
|
||||
v = getattr(self, property)
|
||||
if v is not None:
|
||||
representation.append(
|
||||
self.specification + operator + v
|
||||
)
|
||||
return '&'.join(representation)
|
||||
|
||||
|
||||
class Object(Meta):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
properties=None, # type: Optional[Properties]
|
||||
):
|
||||
self._properties = None # type: Optional[Properties]
|
||||
self.properties = properties
|
||||
|
||||
@property
|
||||
def properties(self):
|
||||
# type: () -> Optional[Properties]
|
||||
return self._properties
|
||||
|
||||
@properties.setter
|
||||
def properties(
|
||||
self,
|
||||
properties_
|
||||
# type: Optional[Union[Dict[str, Property], Sequence[Tuple[str, Property]]]]
|
||||
):
|
||||
# type: (...) -> None
|
||||
self._properties = Properties(properties_)
|
||||
|
||||
|
||||
class Dictionary(Meta):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value_types=None, # type: Optional[Sequence[Property, type]]
|
||||
):
|
||||
self._value_types = None # type: Optional[Tuple]
|
||||
self.value_types = value_types
|
||||
|
||||
@property
|
||||
def value_types(self):
|
||||
# type: () -> Optional[Dict[str, Union[type, Property, serial.model.Object]]]
|
||||
return self._value_types
|
||||
|
||||
@value_types.setter
|
||||
def value_types(self, value_types):
|
||||
# type: (Optional[Sequence[Union[type, Property, serial.model.Object]]]) -> None
|
||||
|
||||
if value_types is not None:
|
||||
|
||||
if isinstance(value_types, (type, Property)):
|
||||
value_types = (value_types,)
|
||||
|
||||
if native_str is not str:
|
||||
|
||||
if isinstance(value_types, collections.Callable):
|
||||
|
||||
_types = value_types
|
||||
|
||||
def value_types(d):
|
||||
# type: (Any) -> Any
|
||||
|
||||
ts = _types(d)
|
||||
|
||||
if (ts is not None) and (str in ts) and (native_str not in ts):
|
||||
ts = tuple(chain(*(
|
||||
((t, native_str) if (t is str) else (t,))
|
||||
for t in ts
|
||||
)))
|
||||
|
||||
return ts
|
||||
|
||||
elif (str in value_types) and (native_str is not str) and (native_str not in value_types):
|
||||
|
||||
value_types = chain(*(
|
||||
((t, native_str) if (t is str) else (t,))
|
||||
for t in value_types
|
||||
))
|
||||
|
||||
if not isinstance(value_types, collections_abc.Callable):
|
||||
value_types = tuple(value_types)
|
||||
|
||||
self._value_types = value_types
|
||||
|
||||
|
||||
class Array(Meta):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
item_types=None, # type: Optional[Sequence[Property, type]]
|
||||
):
|
||||
self._item_types = None # type: Optional[Tuple]
|
||||
self.item_types = item_types
|
||||
|
||||
@property
|
||||
def item_types(self):
|
||||
return self._item_types
|
||||
|
||||
@item_types.setter
|
||||
def item_types(self, item_types):
|
||||
# type: (Optional[Sequence[Union[type, Property, serial.model.Object]]]) -> None
|
||||
if item_types is not None:
|
||||
if isinstance(item_types, (type, Property)):
|
||||
item_types = (item_types,)
|
||||
if native_str is not str:
|
||||
if isinstance(item_types, collections.Callable):
|
||||
_types = item_types
|
||||
|
||||
def item_types(d):
|
||||
# type: (Any) -> Any
|
||||
ts = _types(d)
|
||||
if (ts is not None) and (str in ts) and (native_str not in ts):
|
||||
ts = tuple(chain(*(
|
||||
((t, native_str) if (t is str) else (t,))
|
||||
for t in ts
|
||||
)))
|
||||
return ts
|
||||
elif (str in item_types) and (native_str is not str) and (native_str not in item_types):
|
||||
item_types = chain(*(
|
||||
((t, native_str) if (t is str) else (t,))
|
||||
for t in item_types
|
||||
))
|
||||
if not callable(item_types):
|
||||
item_types = tuple(item_types)
|
||||
self._item_types = item_types
|
||||
|
||||
|
||||
class Properties(OrderedDict):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
items=(
|
||||
None
|
||||
) # type: Optional[Union[Dict[str, Property], List[Tuple[str, Property]]]]
|
||||
):
|
||||
if items is None:
|
||||
super().__init__()
|
||||
else:
|
||||
if isinstance(items, OrderedDict):
|
||||
items = items.items()
|
||||
elif isinstance(items, dict):
|
||||
items = sorted(items.items())
|
||||
super().__init__(items)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# type: (str, Property) -> None
|
||||
if not isinstance(value, Property):
|
||||
raise ValueError(value)
|
||||
super().__setitem__(key, value)
|
||||
|
||||
def __copy__(self):
|
||||
# type: () -> Properties
|
||||
return self.__class__(self)
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
# type: (dict) -> Properties
|
||||
return self.__class__(
|
||||
tuple(
|
||||
(k, deepcopy(v, memo=memo))
|
||||
for k, v in self.items()
|
||||
)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
representation = [
|
||||
qualified_name(type(self)) + '('
|
||||
]
|
||||
items = tuple(self.items())
|
||||
if len(items) > 0:
|
||||
representation[0] += '['
|
||||
for k, v in items:
|
||||
rv = (
|
||||
qualified_name(v) if isinstance(v, type) else
|
||||
repr(v)
|
||||
)
|
||||
rvls = rv.split('\n')
|
||||
if len(rvls) > 1:
|
||||
rvs = [rvls[0]]
|
||||
for rvl in rvls[1:]:
|
||||
rvs.append(' ' + rvl)
|
||||
rv = '\n'.join(rvs)
|
||||
representation += [
|
||||
' (',
|
||||
' %s,' % repr(k),
|
||||
' %s' % rv,
|
||||
' ),'
|
||||
]
|
||||
else:
|
||||
representation.append(
|
||||
' (%s, %s),' % (repr(k), rv)
|
||||
)
|
||||
representation[-1] = representation[-1][:-1]
|
||||
representation.append(
|
||||
']'
|
||||
)
|
||||
representation[-1] += ')'
|
||||
if len(representation) > 2:
|
||||
return '\n'.join(representation)
|
||||
else:
|
||||
return ''.join(representation)
|
||||
|
||||
|
||||
def read(
|
||||
model # type: Union[type, serial.abc.model.Model]
|
||||
):
|
||||
# type: (...) -> Optional[Meta]
|
||||
|
||||
if isinstance(
|
||||
model,
|
||||
Model
|
||||
):
|
||||
|
||||
return model._meta or read(type(model))
|
||||
|
||||
elif isinstance(model, type) and issubclass(model, Model):
|
||||
|
||||
return model._meta
|
||||
|
||||
else:
|
||||
|
||||
try:
|
||||
|
||||
repr_model = repr(model)
|
||||
|
||||
except:
|
||||
|
||||
repr_model = object.__repr__(model)
|
||||
|
||||
raise TypeError(
|
||||
'%s requires a parameter which is an instance or sub-class of `%s`, not%s' % (
|
||||
serial.utilities.calling_function_qualified_name(),
|
||||
qualified_name(Model),
|
||||
(
|
||||
':\n' + repr_model
|
||||
if '\n' in repr_model else
|
||||
' `%s`' % repr_model
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def writable(
|
||||
model # type: Union[type, serial.abc.model.Model]
|
||||
):
|
||||
# type: (...) -> Optional[Meta]
|
||||
if isinstance(model, Model):
|
||||
if model._meta is None:
|
||||
model._meta = deepcopy(writable(type(model)))
|
||||
elif isinstance(model, type) and issubclass(model, Model):
|
||||
if model._meta is None:
|
||||
model._meta = (
|
||||
Object()
|
||||
if issubclass(model, serial.model.Object) else
|
||||
Array()
|
||||
if issubclass(model, serial.model.Array) else
|
||||
Dictionary()
|
||||
if issubclass(model, serial.model.Dictionary)
|
||||
else None
|
||||
)
|
||||
else:
|
||||
for b in model.__bases__:
|
||||
if hasattr(b, '_meta') and (model._meta is b._meta):
|
||||
model._meta = deepcopy(model._meta)
|
||||
break
|
||||
else:
|
||||
repr_model = repr(model)
|
||||
raise TypeError(
|
||||
'%s requires a parameter which is an instance or sub-class of `%s`, not%s' % (
|
||||
serial.utilities.calling_function_qualified_name(),
|
||||
qualified_name(Model),
|
||||
(
|
||||
':\n' + repr_model
|
||||
if '\n' in repr_model else
|
||||
' `%s`' % repr_model
|
||||
)
|
||||
)
|
||||
)
|
||||
return model._meta
|
||||
|
||||
|
||||
def write(
|
||||
model, # type: Union[type, serial.model.Object]
|
||||
meta # type: Meta
|
||||
):
|
||||
# type: (...) -> None
|
||||
if isinstance(model, Model):
|
||||
model_type = type(model)
|
||||
elif isinstance(model, type) and issubclass(model, Model):
|
||||
model_type = model
|
||||
else:
|
||||
repr_model = repr(model)
|
||||
raise TypeError(
|
||||
'%s requires a value for the parameter `model` which is an instance or sub-class of `%s`, not%s' % (
|
||||
serial.utilities.calling_function_qualified_name(),
|
||||
qualified_name(Model),
|
||||
(
|
||||
':\n' + repr_model
|
||||
if '\n' in repr_model else
|
||||
' `%s`' % repr_model
|
||||
)
|
||||
)
|
||||
)
|
||||
metadata_type = (
|
||||
Object
|
||||
if issubclass(model_type, serial.model.Object) else
|
||||
Array
|
||||
if issubclass(model_type, serial.model.Array) else
|
||||
Dictionary
|
||||
if issubclass(model_type, serial.model.Dictionary)
|
||||
else None
|
||||
)
|
||||
if not isinstance(meta, metadata_type):
|
||||
raise ValueError(
|
||||
'Metadata assigned to `%s` must be of type `%s`' % (
|
||||
qualified_name(model_type),
|
||||
qualified_name(metadata_type)
|
||||
)
|
||||
)
|
||||
model._meta = meta
|
||||
|
||||
|
||||
_UNIDENTIFIED = None
|
||||
|
||||
|
||||
def xpath(model, xpath_=_UNIDENTIFIED):
|
||||
# type: (serial.abc.model.Model, Optional[str]) -> Optional[str]
|
||||
"""
|
||||
Return the xpath at which the element represented by this object was found, relative to the root document. If
|
||||
the parameter `xpath_` is provided--set the value
|
||||
"""
|
||||
|
||||
if not isinstance(model, Model):
|
||||
|
||||
raise TypeError(
|
||||
'`model` must be an instance of `%s`, not %s.' % (qualified_name(Model), repr(model))
|
||||
)
|
||||
|
||||
if xpath_ is not _UNIDENTIFIED:
|
||||
|
||||
if not isinstance(xpath_, str):
|
||||
|
||||
if isinstance(xpath_, native_str):
|
||||
xpath_ = str(xpath_)
|
||||
else:
|
||||
raise TypeError(
|
||||
'`xpath_` must be a `str`, not %s.' % repr(xpath_)
|
||||
)
|
||||
|
||||
model._xpath = xpath_
|
||||
|
||||
if isinstance(model, serial.model.Dictionary):
|
||||
for k, v in model.items():
|
||||
if isinstance(v, Model):
|
||||
xpath(v, '%s/%s' % (xpath_, k))
|
||||
elif isinstance(model, serial.model.Object):
|
||||
for pn, p in read(model).properties.items():
|
||||
k = p.name or pn
|
||||
v = getattr(model, pn)
|
||||
if isinstance(v, Model):
|
||||
xpath(v, '%s/%s' % (xpath_, k))
|
||||
elif isinstance(model, serial.model.Array):
|
||||
for i in range(len(model)):
|
||||
v = model[i]
|
||||
if isinstance(v, Model):
|
||||
xpath(v, '%s[%s]' % (xpath_, str(i)))
|
||||
return model._xpath
|
||||
|
||||
|
||||
def pointer(model, pointer_=_UNIDENTIFIED):
|
||||
# type: (serial.abc.model.Model, Optional[str]) -> Optional[str]
|
||||
|
||||
if not isinstance(model, Model):
|
||||
raise TypeError(
|
||||
'`model` must be an instance of `%s`, not %s.' % (qualified_name(Model), repr(model))
|
||||
)
|
||||
|
||||
if pointer_ is not _UNIDENTIFIED:
|
||||
|
||||
if not isinstance(pointer_, (str, native_str)):
|
||||
raise TypeError(
|
||||
'`pointer_` must be a `str`, not %s (of type `%s`).' % (repr(pointer_), type(pointer_).__name__)
|
||||
)
|
||||
|
||||
model._pointer = pointer_
|
||||
|
||||
if isinstance(model, serial.model.Dictionary):
|
||||
|
||||
for k, v in model.items():
|
||||
if isinstance(v, Model):
|
||||
pointer(v, '%s/%s' % (pointer_, k.replace('~', '~0').replace('/', '~1')))
|
||||
|
||||
elif isinstance(model, serial.model.Object):
|
||||
|
||||
for pn, property in read(model).properties.items():
|
||||
|
||||
k = property.name or pn
|
||||
v = getattr(model, pn)
|
||||
|
||||
if isinstance(v, Model):
|
||||
pointer(v, '%s/%s' % (pointer_, k.replace('~', '~0').replace('/', '~1')))
|
||||
|
||||
elif isinstance(model, serial.model.Array):
|
||||
|
||||
for i in range(len(model)):
|
||||
|
||||
v = model[i]
|
||||
|
||||
if isinstance(v, Model):
|
||||
pointer(v, '%s[%s]' % (pointer_, str(i)))
|
||||
|
||||
return model._pointer
|
||||
|
||||
|
||||
def url(model, url_=_UNIDENTIFIED):
|
||||
# type: (serial.abc.model.Model, Optional[str]) -> Optional[str]
|
||||
if not isinstance(model, serial.abc.model.Model):
|
||||
raise TypeError(
|
||||
'`model` must be an instance of `%s`, not %s.' % (qualified_name(Model), repr(model))
|
||||
)
|
||||
if url_ is not _UNIDENTIFIED:
|
||||
if not isinstance(url_, str):
|
||||
raise TypeError(
|
||||
'`url_` must be a `str`, not %s.' % repr(url_)
|
||||
)
|
||||
model._url = url_
|
||||
if isinstance(model, serial.model.Dictionary):
|
||||
for v in model.values():
|
||||
if isinstance(v, Model):
|
||||
url(v, url_)
|
||||
elif isinstance(model, serial.model.Object):
|
||||
for pn in read(model).properties.keys():
|
||||
v = getattr(model, pn)
|
||||
if isinstance(v, Model):
|
||||
url(v, url_)
|
||||
elif isinstance(model, serial.model.Array):
|
||||
for v in model:
|
||||
if isinstance(v, Model):
|
||||
url(v, url_)
|
||||
return model._url
|
||||
|
||||
|
||||
def format_(model, serialization_format=_UNIDENTIFIED):
|
||||
|
||||
# type: (serial.abc.model.Model, Optional[str]) -> Optional[str]
|
||||
if not isinstance(model, Model):
|
||||
|
||||
raise TypeError(
|
||||
'`model` must be an instance of `%s`, not %s.' % (qualified_name(Model), repr(model))
|
||||
)
|
||||
|
||||
if serialization_format is not _UNIDENTIFIED:
|
||||
|
||||
if not isinstance(serialization_format, str):
|
||||
|
||||
if isinstance(serialization_format, native_str):
|
||||
serialization_format = str(serialization_format)
|
||||
else:
|
||||
raise TypeError(
|
||||
'`serialization_format` must be a `str`, not %s.' % repr(serialization_format)
|
||||
)
|
||||
|
||||
model._format = serialization_format
|
||||
|
||||
if isinstance(model, serial.model.Dictionary):
|
||||
for v in model.values():
|
||||
if isinstance(v, Model):
|
||||
format_(v, serialization_format)
|
||||
elif isinstance(model, serial.model.Object):
|
||||
for pn in read(model).properties.keys():
|
||||
v = getattr(model, pn)
|
||||
if isinstance(v, Model):
|
||||
format_(v, serialization_format)
|
||||
elif isinstance(model, serial.model.Array):
|
||||
for v in model:
|
||||
if isinstance(v, Model):
|
||||
format_(v, serialization_format)
|
||||
|
||||
return model._format
|
||||
|
||||
|
||||
def version(data, specification, version_number):
|
||||
# type: (serial.abc.model.Model, str, Union[str, int, Sequence[int]]) -> None
|
||||
"""
|
||||
Recursively alters model class or instance metadata based on version number metadata associated with an
|
||||
object's properties. This allows one data model to represent multiple versions of a specification and dynamically
|
||||
change based on the version of a specification represented.
|
||||
|
||||
Arguments:
|
||||
|
||||
- data (serial.abc.model.Model)
|
||||
|
||||
- specification (str):
|
||||
|
||||
The specification to which the `version_number` argument applies.
|
||||
|
||||
- version_number (str|int|[int]):
|
||||
|
||||
A version number represented as text (in the form of integers separated by periods), an integer, or a
|
||||
sequence of integers.
|
||||
"""
|
||||
if not isinstance(data, serial.abc.model.Model):
|
||||
raise TypeError(
|
||||
'The data provided is not an instance of serial.abc.model.Model: ' + repr(data)
|
||||
)
|
||||
|
||||
def version_match(property_):
|
||||
# type: (serial.properties.Property) -> bool
|
||||
if property_.versions is not None:
|
||||
version_matched = False
|
||||
specification_matched = False
|
||||
for applicable_version in property_.versions:
|
||||
if applicable_version.specification == specification:
|
||||
specification_matched = True
|
||||
if applicable_version == version_number:
|
||||
version_matched = True
|
||||
break
|
||||
if specification_matched and (not version_matched):
|
||||
return False
|
||||
return True
|
||||
|
||||
def version_properties(properties_):
|
||||
# type: (Sequence[serial.properties.Property]) -> Optional[Sequence[Meta]]
|
||||
changed = False
|
||||
nps = []
|
||||
|
||||
for property in properties_:
|
||||
|
||||
if isinstance(property, serial.properties.Property):
|
||||
if version_match(property):
|
||||
np = version_property(property)
|
||||
if np is not property:
|
||||
changed = True
|
||||
nps.append(np)
|
||||
else:
|
||||
changed = True
|
||||
else:
|
||||
nps.append(property)
|
||||
if changed:
|
||||
return tuple(nps)
|
||||
else:
|
||||
return None
|
||||
|
||||
def version_property(property):
|
||||
# type: (serial.properties.Property) -> Meta
|
||||
changed = False
|
||||
if isinstance(property, serial.properties.Array) and (property.item_types is not None):
|
||||
item_types = version_properties(property.item_types)
|
||||
if item_types is not None:
|
||||
if not changed:
|
||||
property = deepcopy(property)
|
||||
property.item_types = item_types
|
||||
changed = True
|
||||
elif isinstance(property, serial.properties.Dictionary) and (property.value_types is not None):
|
||||
value_types = version_properties(property.value_types)
|
||||
if value_types is not None:
|
||||
if not changed:
|
||||
property = deepcopy(property)
|
||||
property.value_types = value_types
|
||||
changed = True
|
||||
if property.types is not None:
|
||||
types = version_properties(property.types)
|
||||
if types is not None:
|
||||
if not changed:
|
||||
property = deepcopy(property)
|
||||
property.types = types
|
||||
return property
|
||||
|
||||
instance_meta = read(data)
|
||||
class_meta = read(type(data))
|
||||
|
||||
if isinstance(data, serial.abc.model.Object):
|
||||
|
||||
for property_name in tuple(instance_meta.properties.keys()):
|
||||
|
||||
property = instance_meta.properties[property_name]
|
||||
|
||||
if version_match(property):
|
||||
np = version_property(property)
|
||||
if np is not property:
|
||||
if instance_meta is class_meta:
|
||||
instance_meta = writable(data)
|
||||
instance_meta.properties[property_name] = np
|
||||
else:
|
||||
if instance_meta is class_meta:
|
||||
instance_meta = writable(data)
|
||||
del instance_meta.properties[property_name]
|
||||
version_ = getattr(data, property_name)
|
||||
if version_ is not None:
|
||||
raise serial.errors.VersionError(
|
||||
'%s - the property `%s` is not applicable in %s version %s:\n%s' % (
|
||||
qualified_name(type(data)),
|
||||
property_name,
|
||||
specification,
|
||||
version_number,
|
||||
str(data)
|
||||
)
|
||||
)
|
||||
|
||||
value = getattr(data, property_name)
|
||||
|
||||
if isinstance(value, serial.abc.model.Model):
|
||||
version(value, specification, version_number)
|
||||
|
||||
elif isinstance(data, serial.abc.model.Dictionary):
|
||||
|
||||
if instance_meta and instance_meta.value_types:
|
||||
|
||||
new_value_types = version_properties(instance_meta.value_types)
|
||||
|
||||
if new_value_types:
|
||||
|
||||
if instance_meta is class_meta:
|
||||
instance_meta = writable(data)
|
||||
|
||||
instance_meta.value_types = new_value_types
|
||||
|
||||
for value in data.values():
|
||||
if isinstance(value, serial.abc.model.Model):
|
||||
version(value, specification, version_number)
|
||||
|
||||
elif isinstance(data, serial.abc.model.Array):
|
||||
|
||||
if instance_meta and instance_meta.item_types:
|
||||
|
||||
new_item_types = version_properties(instance_meta.item_types)
|
||||
|
||||
if new_item_types:
|
||||
|
||||
if instance_meta is class_meta:
|
||||
instance_meta = writable(data)
|
||||
|
||||
instance_meta.item_types = new_item_types
|
||||
|
||||
for item in data:
|
||||
if isinstance(item, serial.abc.model.Model):
|
||||
version(item, specification, version_number)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,941 +0,0 @@
|
||||
# Tell the linters what's up:
|
||||
# pylint:disable=wrong-import-position,consider-using-enumerate,useless-object-inheritance
|
||||
# mccabe:options:max-complexity=999
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
|
||||
from .utilities.compatibility import backport
|
||||
|
||||
backport() # noqa
|
||||
|
||||
from future.utils import native_str
|
||||
|
||||
import numbers # noqa
|
||||
|
||||
from base64 import b64decode, b64encode # noqa
|
||||
from copy import deepcopy # noqa
|
||||
from datetime import date, datetime # noqa
|
||||
|
||||
try:
|
||||
from typing import Union, Optional, Sequence, Mapping, Set, Sequence, Callable, Dict, Any, Hashable, Collection,\
|
||||
Tuple
|
||||
except ImportError:
|
||||
Union = Optional = Sequence = Mapping = Set = Sequence = Callable = Dict = Any = Hashable = Collection = Tuple =\
|
||||
Iterable = None
|
||||
|
||||
import iso8601 # noqa
|
||||
|
||||
from .utilities import collections, collections_abc, qualified_name, properties_values, parameters_defaults,\
|
||||
calling_function_qualified_name
|
||||
|
||||
from serial import abc, errors, meta
|
||||
import serial
|
||||
|
||||
|
||||
NoneType = type(None)
|
||||
|
||||
|
||||
NULL = None
|
||||
|
||||
|
||||
class Null(object): # noqa - inheriting from object is intentional, as this is needed for python 2x compatibility
|
||||
"""
|
||||
Instances of this class represent an *explicit* null value, rather than the absence of a
|
||||
property/attribute/element, as would be inferred from a value of `None`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
if NULL is not None:
|
||||
raise errors.DefinitionExistsError(
|
||||
'%s may only be defined once.' % repr(self)
|
||||
)
|
||||
|
||||
def __bool__(self):
|
||||
# type: (...) -> bool
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (Any) -> bool
|
||||
return id(other) == id(self)
|
||||
|
||||
def __hash__(self):
|
||||
# type: (...) -> int
|
||||
return 0
|
||||
|
||||
def __str__(self):
|
||||
# type: (...) -> str
|
||||
return 'null'
|
||||
|
||||
def _marshal(self):
|
||||
# type: (...) -> None
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
# type: (...) -> str
|
||||
return (
|
||||
'NULL'
|
||||
if self.__module__ in ('__main__', 'builtins', '__builtin__') else
|
||||
'%s.NULL' % self.__module__
|
||||
)
|
||||
|
||||
def __copy__(self):
|
||||
# type: (...) -> Null
|
||||
return self
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# type: (Dict[Hashable, Any]) -> Null
|
||||
return self
|
||||
|
||||
|
||||
NULL = Null()
|
||||
|
||||
|
||||
def _validate_type_or_property(type_or_property):
|
||||
# type: (Union[type, Property]) -> (Union[type, Property])
|
||||
|
||||
if not isinstance(type_or_property, (type, Property)):
|
||||
raise TypeError(type_or_property)
|
||||
|
||||
if not (
|
||||
(type_or_property is Null) or
|
||||
(
|
||||
isinstance(type_or_property, type) and
|
||||
issubclass(
|
||||
type_or_property,
|
||||
(
|
||||
abc.model.Model,
|
||||
str,
|
||||
native_str,
|
||||
bytes,
|
||||
numbers.Number,
|
||||
date,
|
||||
datetime,
|
||||
Null,
|
||||
collections_abc.Iterable,
|
||||
dict,
|
||||
collections.OrderedDict,
|
||||
bool
|
||||
)
|
||||
)
|
||||
) or
|
||||
isinstance(type_or_property, Property)
|
||||
):
|
||||
raise TypeError(type_or_property)
|
||||
|
||||
return type_or_property
|
||||
|
||||
|
||||
class Types(list):
|
||||
"""
|
||||
Instances of this class are lists which will only take values which are valid types for describing a property
|
||||
definition.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
property_, # type: Property
|
||||
items=None # type: Optional[Union[Sequence[Union[type, Property], Types], type, Property]]
|
||||
):
|
||||
# (...) -> None
|
||||
if not isinstance(property_, Property):
|
||||
raise TypeError(
|
||||
'The parameter `property` must be a `type`, or an instance of `%s`.' % qualified_name(Property)
|
||||
)
|
||||
|
||||
self.property_ = property_
|
||||
|
||||
if isinstance(items, (type, Property)):
|
||||
items = (items,)
|
||||
|
||||
if items is None:
|
||||
super().__init__()
|
||||
else:
|
||||
super().__init__(items)
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
# type: (int, Union[type, Property]) -> None
|
||||
super().__setitem__(index, _validate_type_or_property(value))
|
||||
if value is str and (native_str is not str) and (native_str not in self):
|
||||
super().append(native_str)
|
||||
|
||||
def append(self, value):
|
||||
# type: (Union[type, Property]) -> None
|
||||
super().append(_validate_type_or_property(value))
|
||||
if value is str and (native_str is not str) and (native_str not in self):
|
||||
super().append(native_str)
|
||||
|
||||
def __delitem__(self, index):
|
||||
# type: (int) -> None
|
||||
value = self[index]
|
||||
super().__delitem__(index)
|
||||
if (value is str) and (native_str in self):
|
||||
self.remove(native_str)
|
||||
|
||||
def pop(self, index=-1):
|
||||
# type: (int) -> Union[type, Property]
|
||||
value = super().pop(index)
|
||||
if (value is str) and (native_str in self):
|
||||
self.remove(native_str)
|
||||
return value
|
||||
|
||||
def __copy__(self):
|
||||
# type: () -> Types
|
||||
return self.__class__(self.property_, self)
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
# type: (dict) -> Types
|
||||
return self.__class__(
|
||||
self.property_,
|
||||
tuple(
|
||||
deepcopy(v, memo=memo)
|
||||
for v in self
|
||||
)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
representation = [
|
||||
qualified_name(type(self)) + '('
|
||||
]
|
||||
|
||||
if self:
|
||||
|
||||
representation[0] += '['
|
||||
|
||||
for v in self:
|
||||
|
||||
rv = (
|
||||
qualified_name(v) if isinstance(v, type) else
|
||||
repr(v)
|
||||
)
|
||||
rvls = rv.split('\n')
|
||||
|
||||
if len(rvls) > 1:
|
||||
|
||||
rvs = [rvls[0]]
|
||||
|
||||
for rvl in rvls[1:]:
|
||||
rvs.append(' ' + rvl)
|
||||
|
||||
rv = '\n'.join(rvs)
|
||||
representation += [
|
||||
' %s' % rv,
|
||||
]
|
||||
|
||||
else:
|
||||
|
||||
representation.append(
|
||||
' %s,' % rv
|
||||
)
|
||||
|
||||
representation[-1] = representation[-1][:-1]
|
||||
representation.append(
|
||||
']'
|
||||
)
|
||||
|
||||
representation[-1] += ')'
|
||||
|
||||
return '\n'.join(representation) if len(representation) > 2 else ''.join(representation)
|
||||
|
||||
|
||||
class Property(object):
|
||||
"""
|
||||
This is the base class for defining a property.
|
||||
|
||||
Properties
|
||||
|
||||
- value_types ([type|Property]): One or more expected value_types or `Property` instances. Values are checked,
|
||||
sequentially, against each type or `Property` instance, and the first appropriate match is used.
|
||||
|
||||
- required (bool|collections.Callable): If `True`--dumping the json_object will throw an error if this value
|
||||
is `None`.
|
||||
|
||||
- versions ([str]|{str:Property}): The property should be one of the following:
|
||||
|
||||
- A set/tuple/list of version numbers to which this property applies.
|
||||
- A mapping of version numbers to an instance of `Property` applicable to that version.
|
||||
|
||||
Version numbers prefixed by "<" indicate any version less than the one specified, so "<3.0" indicates that
|
||||
this property is available in versions prior to 3.0. The inverse is true for version numbers prefixed by ">".
|
||||
">=" and "<=" have similar meanings, but are inclusive.
|
||||
|
||||
Versioning can be applied to an json_object by calling `serial.meta.set_version` in the `__init__` method of
|
||||
an `serial.model.Object` sub-class. For an example, see `oapi.model.OpenAPI.__init__`.
|
||||
|
||||
- name (str): The name of the property when loaded from or dumped into a JSON/YAML/XML json_object. Specifying a
|
||||
`name` facilitates mapping of PEP8 compliant property to JSON or YAML attribute names, or XML element names,
|
||||
which are either camelCased, are python keywords, or otherwise not appropriate for usage in python code.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
types=None, # type: Sequence[Union[type, Property]]
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Sequence[Union[str, meta.Version]]]
|
||||
):
|
||||
self._types = None # type: Optional[Sequence[Union[type, Property]]]
|
||||
self.types = types
|
||||
self.name = name
|
||||
self.required = required
|
||||
self._versions = None # type: Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
|
||||
self.versions = versions # type: Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
return self._types
|
||||
|
||||
@types.setter
|
||||
def types(self, types_or_properties):
|
||||
# type: (Optional[Sequence[Union[type, Property, abc.model.Model]]]) -> None
|
||||
|
||||
if types_or_properties is not None:
|
||||
|
||||
if callable(types_or_properties):
|
||||
|
||||
if native_str is not str:
|
||||
|
||||
_types_or_properties = types_or_properties
|
||||
|
||||
def types_or_properties(d):
|
||||
# type: (Sequence[Union[type, Property, abc.model.Model]]) -> Types
|
||||
return Types(self, _types_or_properties(d))
|
||||
|
||||
else:
|
||||
|
||||
types_or_properties = Types(self, types_or_properties)
|
||||
|
||||
self._types = types_or_properties
|
||||
|
||||
@property
|
||||
def versions(self):
|
||||
# type: () -> Optional[Sequence[meta.Version]]
|
||||
return self._versions
|
||||
|
||||
@versions.setter
|
||||
def versions(
|
||||
self,
|
||||
versions # type: Optional[Sequence[Union[str, collections_abc.Iterable, meta.Version]]]
|
||||
):
|
||||
# type: (...) -> Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
|
||||
if versions is not None:
|
||||
|
||||
if isinstance(versions, (str, Number, meta.Version)):
|
||||
versions = (versions,)
|
||||
|
||||
if isinstance(versions, collections_abc.Iterable):
|
||||
versions = tuple(
|
||||
(v if isinstance(v, meta.Version) else meta.Version(v))
|
||||
for v in versions
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
repr_versions = repr(versions)
|
||||
|
||||
raise TypeError(
|
||||
(
|
||||
'`%s` requires a sequence of version strings or ' %
|
||||
calling_function_qualified_name()
|
||||
) + (
|
||||
'`%s` instances, not' % qualified_name(meta.Version)
|
||||
) + (
|
||||
':\n' + repr_versions
|
||||
if '\n' in repr_versions else
|
||||
' `%s`.' % repr_versions
|
||||
)
|
||||
)
|
||||
|
||||
self._versions = versions
|
||||
|
||||
def unmarshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
# return data if self.types is None else unmarshal(data, types=self.types)
|
||||
|
||||
if isinstance(
|
||||
data,
|
||||
collections_abc.Iterable
|
||||
) and not isinstance(
|
||||
data,
|
||||
(str, bytes, bytearray, native_str)
|
||||
) and not isinstance(
|
||||
data,
|
||||
abc.model.Model
|
||||
):
|
||||
|
||||
if isinstance(data, (dict, collections.OrderedDict)):
|
||||
|
||||
for k, v in data.items():
|
||||
if v is None:
|
||||
data[k] = NULL
|
||||
|
||||
else:
|
||||
|
||||
data = tuple((NULL if i is None else i) for i in data)
|
||||
|
||||
return serial.marshal.unmarshal(data, types=self.types)
|
||||
|
||||
def marshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
return serial.marshal.marshal(data, types=self.types) #, types=self.types, value_types=self.value_types)
|
||||
|
||||
def __repr__(self):
|
||||
representation = [qualified_name(type(self)) + '(']
|
||||
pd = parameters_defaults(self.__init__)
|
||||
for p, v in properties_values(self):
|
||||
if (p not in pd) or pd[p] == v:
|
||||
continue
|
||||
if (v is not None) and (v is not NULL):
|
||||
if isinstance(v, collections_abc.Sequence) and not isinstance(v, (str, bytes)):
|
||||
rvs = ['(']
|
||||
for i in v:
|
||||
ri = (
|
||||
qualified_name(i)
|
||||
if isinstance(i, type) else
|
||||
"'%s'" % str(i)
|
||||
if isinstance(i, meta.Version) else
|
||||
repr(i)
|
||||
)
|
||||
rils = ri.split('\n')
|
||||
if len(rils) > 1:
|
||||
ris = [rils[0]]
|
||||
for ril in rils[1:]:
|
||||
ris.append(' ' + ril)
|
||||
ri = '\n'.join(ris)
|
||||
rvs.append(' %s,' % ri)
|
||||
if len(v) > 1:
|
||||
rvs[-1] = rvs[-1][:-1]
|
||||
rvs.append(' )')
|
||||
rv = '\n'.join(rvs)
|
||||
else:
|
||||
rv = (
|
||||
qualified_name(v)
|
||||
if isinstance(v, type) else
|
||||
"'%s'" % str(v)
|
||||
if isinstance(v, meta.Version) else
|
||||
repr(v)
|
||||
)
|
||||
rvls = rv.split('\n')
|
||||
if len(rvls) > 2:
|
||||
rvs = [rvls[0]]
|
||||
for rvl in rvls[1:]:
|
||||
rvs.append(' ' + rvl)
|
||||
rv = '\n'.join(rvs)
|
||||
representation.append(
|
||||
' %s=%s,' % (p, rv)
|
||||
)
|
||||
representation.append(')')
|
||||
if len(representation) > 2:
|
||||
return '\n'.join(representation)
|
||||
else:
|
||||
return ''.join(representation)
|
||||
|
||||
def __copy__(self):
|
||||
|
||||
new_instance = self.__class__()
|
||||
|
||||
for a in dir(self):
|
||||
|
||||
if a[0] != '_' and a != 'data':
|
||||
|
||||
v = getattr(self, a)
|
||||
|
||||
if not callable(v):
|
||||
setattr(new_instance, a, v)
|
||||
|
||||
return new_instance
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# type: (dict) -> Property
|
||||
|
||||
new_instance = self.__class__()
|
||||
|
||||
for a, v in properties_values(self):
|
||||
setattr(new_instance, a, deepcopy(v, memo))
|
||||
|
||||
return new_instance
|
||||
|
||||
|
||||
abc.properties.Property.register(Property)
|
||||
|
||||
|
||||
class String(Property):
|
||||
"""
|
||||
See `serial.properties.Property`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
super().__init__(
|
||||
types=(str,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
|
||||
class Date(Property):
|
||||
"""
|
||||
See `serial.properties.Property`
|
||||
|
||||
Additional Properties:
|
||||
|
||||
- marshal (collections.Callable): A function, taking one argument (a python `date` json_object), and returning
|
||||
a date string in the desired format. The default is `date.isoformat`--returning an iso8601 compliant date
|
||||
string.
|
||||
|
||||
- unmarshal (collections.Callable): A function, taking one argument (a date string), and returning a python
|
||||
`date` json_object. By default, this is `iso8601.parse_date`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
date2str=date.isoformat, # type: Optional[Callable]
|
||||
str2date=iso8601.parse_date # type: Callable
|
||||
):
|
||||
super().__init__(
|
||||
types=(date,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
self.date2str = date2str
|
||||
self.str2date = str2date
|
||||
|
||||
def unmarshal(self, data):
|
||||
# type: (Optional[str]) -> Union[date, NoneType]
|
||||
if data is None:
|
||||
return data
|
||||
else:
|
||||
if isinstance(data, date):
|
||||
date_ = data
|
||||
elif isinstance(data, str):
|
||||
date_ = self.str2date(data)
|
||||
else:
|
||||
raise TypeError(
|
||||
'%s is not a `str`.' % repr(data)
|
||||
)
|
||||
if isinstance(date_, date):
|
||||
return date_
|
||||
else:
|
||||
raise TypeError(
|
||||
'"%s" is not a properly formatted date string.' % data
|
||||
)
|
||||
|
||||
def marshal(self, data):
|
||||
# type: (Optional[date]) -> Optional[str]
|
||||
if data is None:
|
||||
return data
|
||||
else:
|
||||
ds = self.date2str(data)
|
||||
if not isinstance(ds, str):
|
||||
if isinstance(ds, native_str):
|
||||
ds = str(ds)
|
||||
else:
|
||||
raise TypeError(
|
||||
'The date2str function should return a `str`, not a `%s`: %s' % (
|
||||
type(ds).__name__,
|
||||
repr(ds)
|
||||
)
|
||||
)
|
||||
return ds
|
||||
|
||||
|
||||
class DateTime(Property):
|
||||
"""
|
||||
See `serial.properties.Property`
|
||||
|
||||
Additional Properties:
|
||||
|
||||
- marshal (collections.Callable): A function, taking one argument (a python `datetime` json_object), and
|
||||
returning a date-time string in the desired format. The default is `datetime.isoformat`--returning an
|
||||
iso8601 compliant date-time string.
|
||||
|
||||
- unmarshal (collections.Callable): A function, taking one argument (a datetime string), and returning a python
|
||||
`datetime` json_object. By default, this is `iso8601.parse_date`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
datetime2str=datetime.isoformat, # type: Optional[Callable]
|
||||
str2datetime=iso8601.parse_date # type: Callable
|
||||
):
|
||||
self.datetime2str = datetime2str
|
||||
self.str2datetime = str2datetime
|
||||
super().__init__(
|
||||
types=(datetime,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
def unmarshal(self, data):
|
||||
# type: (Optional[str]) -> Union[datetime, NoneType]
|
||||
if data is None:
|
||||
return data
|
||||
else:
|
||||
if isinstance(data, datetime):
|
||||
datetime_ = data
|
||||
elif isinstance(data, str):
|
||||
datetime_ = self.str2datetime(data)
|
||||
else:
|
||||
raise TypeError(
|
||||
'%s is not a `str`.' % repr(data)
|
||||
)
|
||||
if isinstance(datetime_, datetime):
|
||||
return datetime_
|
||||
else:
|
||||
raise TypeError(
|
||||
'"%s" is not a properly formatted date-time string.' % data
|
||||
)
|
||||
|
||||
def marshal(self, data):
|
||||
# type: (Optional[datetime]) -> Optional[str]
|
||||
if data is None:
|
||||
return data
|
||||
else:
|
||||
datetime_string = self.datetime2str(data)
|
||||
if not isinstance(datetime_string, str):
|
||||
if isinstance(datetime_string, native_str):
|
||||
datetime_string = str(datetime_string)
|
||||
else:
|
||||
repr_datetime_string = repr(datetime_string).strip()
|
||||
raise TypeError(
|
||||
'The datetime2str function should return a `str`, not:' + (
|
||||
'\n'
|
||||
if '\n' in repr_datetime_string else
|
||||
' '
|
||||
) + repr_datetime_string
|
||||
)
|
||||
return datetime_string
|
||||
|
||||
|
||||
class Bytes(Property):
|
||||
"""
|
||||
See `serial.properties.Property`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: bool
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
super().__init__(
|
||||
types=(bytes, bytearray),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
def unmarshal(self, data):
|
||||
# type: (str) -> Optional[bytes]
|
||||
"""
|
||||
Un-marshal a base-64 encoded string into bytes
|
||||
"""
|
||||
if data is None:
|
||||
return data
|
||||
elif isinstance(data, str):
|
||||
return b64decode(data)
|
||||
elif isinstance(data, bytes):
|
||||
return data
|
||||
else:
|
||||
raise TypeError(
|
||||
'`data` must be a base64 encoded `str` or `bytes`--not `%s`' % qualified_name(type(data))
|
||||
)
|
||||
|
||||
def marshal(self, data):
|
||||
# type: (bytes) -> str
|
||||
"""
|
||||
Marshal bytes into a base-64 encoded string
|
||||
"""
|
||||
if (data is None) or isinstance(data, str):
|
||||
return data
|
||||
elif isinstance(data, bytes):
|
||||
return str(b64encode(data), 'ascii')
|
||||
else:
|
||||
raise TypeError(
|
||||
'`data` must be a base64 encoded `str` or `bytes`--not `%s`' % qualified_name(type(data))
|
||||
)
|
||||
|
||||
|
||||
class Enumerated(Property):
|
||||
"""
|
||||
See `serial.properties.Property`...
|
||||
|
||||
+ Properties:
|
||||
|
||||
- values ([Any]): A list of possible values. If the parameter `types` is specified--typing is
|
||||
applied prior to validation.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
types=None, # type: Optional[Sequence[Union[type, Property]]]
|
||||
values=None, # type: Optional[Union[Sequence, Set]]
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
self._values = None
|
||||
|
||||
super().__init__(
|
||||
types=types,
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions
|
||||
)
|
||||
|
||||
self.values = values # type: Optional[Sequence]
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
# type: () -> Optional[Union[Tuple, Callable]]
|
||||
return self._values
|
||||
|
||||
@values.setter
|
||||
def values(self, values):
|
||||
# type: (Iterable) -> None
|
||||
|
||||
if not ((values is None) or callable(values)):
|
||||
|
||||
if (values is not None) and (not isinstance(values, (collections_abc.Sequence, collections_abc.Set))):
|
||||
raise TypeError(
|
||||
'`values` must be a finite set or sequence, not `%s`.' % qualified_name(type(values))
|
||||
)
|
||||
|
||||
if values is not None:
|
||||
values = [
|
||||
serial.marshal.unmarshal(v, types=self.types)
|
||||
for v in values
|
||||
]
|
||||
|
||||
self._values = values
|
||||
|
||||
def unmarshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
|
||||
if self.types is not None:
|
||||
data = serial.marshal.unmarshal(data, types=self.types)
|
||||
|
||||
if (
|
||||
(data is not None) and
|
||||
(self.values is not None) and
|
||||
(data not in self.values)
|
||||
):
|
||||
raise ValueError(
|
||||
'The value provided is not a valid option:\n%s\n\n' % repr(data) +
|
||||
'Valid options include:\n%s' % (
|
||||
', '.join(repr(t) for t in self.values)
|
||||
)
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
class Number(Property):
|
||||
"""
|
||||
See `serial.properties.Property`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
# type: (...) -> None
|
||||
super().__init__(
|
||||
types=(numbers.Number,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
|
||||
class Integer(Property):
|
||||
"""
|
||||
See `serial.properties.Property`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
super().__init__(
|
||||
types=(int,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
# def unmarshal(self, data):
|
||||
# # type: (Any) -> Any
|
||||
# if data is None:
|
||||
# return data
|
||||
# else:
|
||||
# return int(data)
|
||||
#
|
||||
# def marshal(self, data):
|
||||
# # type: (Any) -> Any
|
||||
# if data is None:
|
||||
# return data
|
||||
# else:
|
||||
# return int(data)
|
||||
|
||||
|
||||
class Boolean(Property):
|
||||
"""
|
||||
See `serial.properties.Property`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
# type: (...) -> None
|
||||
super().__init__(
|
||||
types=(bool,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
|
||||
class Array(Property):
|
||||
"""
|
||||
See `serial.properties.Property`...
|
||||
|
||||
+ Properties:
|
||||
|
||||
- item_types (type|Property|[type|Property]): The type(s) of values/objects contained in the array. Similar to
|
||||
`serial.properties.Property().value_types`, but applied to items in the array, not the array itself.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
item_types=None, # type: Optional[Union[type, Sequence[Union[type, Property]]]]
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
self._item_types = None
|
||||
self.item_types = item_types
|
||||
super().__init__(
|
||||
types=(serial.model.Array,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
def unmarshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
return serial.marshal.unmarshal(data, types=self.types, item_types=self.item_types)
|
||||
|
||||
def marshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
return serial.marshal.marshal(data, types=self.types, item_types=self.item_types)
|
||||
|
||||
@property
|
||||
def item_types(self):
|
||||
return self._item_types
|
||||
|
||||
@item_types.setter
|
||||
def item_types(self, item_types):
|
||||
# type: (Optional[Sequence[Union[type, Property, abc.model.Object]]]) -> None
|
||||
if item_types is not None:
|
||||
if callable(item_types):
|
||||
if native_str is not str:
|
||||
_item_types = item_types
|
||||
|
||||
def item_types(d):
|
||||
# type: (Sequence[Union[type, Property, abc.model.Object]]) -> Types
|
||||
return Types(self, _item_types(d))
|
||||
else:
|
||||
item_types = Types(self, item_types)
|
||||
self._item_types = item_types
|
||||
|
||||
|
||||
class Dictionary(Property):
|
||||
"""
|
||||
See `serial.properties.Property`...
|
||||
|
||||
+ Properties:
|
||||
|
||||
- value_types (type|Property|[type|Property]): The type(s) of values/objects comprising the mapped
|
||||
values. Similar to `serial.properties.Property.types`, but applies to *values* in the dictionary
|
||||
object, not the dictionary itself.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value_types=None, # type: Optional[Union[type, Sequence[Union[type, Property]]]]
|
||||
name=None, # type: Optional[str]
|
||||
required=False, # type: Union[bool, collections.Callable]
|
||||
versions=None, # type: Optional[Collection]
|
||||
):
|
||||
self._value_types = None
|
||||
self.value_types = value_types
|
||||
super().__init__(
|
||||
types=(serial.model.Dictionary,),
|
||||
name=name,
|
||||
required=required,
|
||||
versions=versions,
|
||||
)
|
||||
|
||||
def unmarshal(self, data):
|
||||
# type: (Any) -> Any
|
||||
return serial.marshal.unmarshal(data, types=self.types, value_types=self.value_types)
|
||||
|
||||
@property
|
||||
def value_types(self):
|
||||
return self._value_types
|
||||
|
||||
@value_types.setter
|
||||
def value_types(self, value_types_):
|
||||
# type: (Optional[Sequence[Union[type, Property, abc.model.Object]]]) -> None
|
||||
"""
|
||||
The `types` can be either:
|
||||
|
||||
- A sequence of types and/or `serial.properties.Property` instances.
|
||||
|
||||
- A function which accepts exactly one argument (a dictionary), and which returns a sequence of types and/or
|
||||
`serial.properties.Property` instances.
|
||||
|
||||
If more than one type or property definition is provided, un-marshalling is attempted using each `value_type`,
|
||||
in sequential order. If a value could be cast into more than one of the `types` without throwing a
|
||||
`ValueError`, `TypeError`, or `serial.errors.ValidationError`, the value type occuring *first* in the sequence
|
||||
will be used.
|
||||
"""
|
||||
|
||||
if value_types_ is not None:
|
||||
|
||||
if callable(value_types_):
|
||||
|
||||
if native_str is not str:
|
||||
|
||||
original_value_types_ = value_types_
|
||||
|
||||
def value_types_(data):
|
||||
# type: (Sequence[Union[type, Property, abc.model.Object]]) -> Types
|
||||
return Types(self, original_value_types_(data))
|
||||
|
||||
else:
|
||||
|
||||
value_types_ = Types(self, value_types_)
|
||||
|
||||
self._value_types = value_types_
|
||||
@@ -1,451 +0,0 @@
|
||||
"""
|
||||
This module extends the functionality of `urllib.request.Request` to support multipart requests, to support passing
|
||||
instances of serial models to the `data` parameter/property for `urllib.request.Request`, and to
|
||||
support casting requests as `str` or `bytes` (typically for debugging purposes and/or to aid in producing
|
||||
non-language-specific API documentation).
|
||||
"""
|
||||
# region Backwards Compatibility
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
from .utilities.compatibility import backport
|
||||
|
||||
backport() # noqa
|
||||
|
||||
from future.utils import native_str
|
||||
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
from typing import Dict, Sequence, Set, Iterable
|
||||
except ImportError:
|
||||
Dict = Sequence = Set = None
|
||||
|
||||
from serial.marshal import serialize
|
||||
from .abc.model import Model
|
||||
from .utilities import collections_abc
|
||||
|
||||
|
||||
class Headers(object):
|
||||
"""
|
||||
A dictionary of headers for a `Request`, `Part`, or `MultipartRequest` instance.
|
||||
"""
|
||||
|
||||
def __init__(self, items, request):
|
||||
# type: (Dict[str, str], Union[Part, Request]) -> None
|
||||
self._dict = {}
|
||||
self.request = request # type: Data
|
||||
self.update(items)
|
||||
|
||||
def pop(self, key, default=None):
|
||||
# type: (str, Optional[str]) -> str
|
||||
key = key.capitalize()
|
||||
if hasattr(self.request, '_boundary'):
|
||||
self.request._boundary = None
|
||||
if hasattr(self.request, '_bytes'):
|
||||
self.request._bytes = None
|
||||
return self._dict.pop(key, default=default)
|
||||
|
||||
def popitem(self):
|
||||
# type: (str, Optional[str]) -> str
|
||||
if hasattr(self.request, '_boundary'):
|
||||
self.request._boundary = None
|
||||
if hasattr(self.request, '_bytes'):
|
||||
self.request._bytes = None
|
||||
return self._dict.popitem()
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
# type: (str, Optional[str]) -> str
|
||||
key = key.capitalize()
|
||||
if hasattr(self.request, '_boundary'):
|
||||
self.request._boundary = None
|
||||
if hasattr(self.request, '_bytes'):
|
||||
self.request._bytes = None
|
||||
return self._dict.setdefault(key, default=default)
|
||||
|
||||
def update(self, iterable=None, **kwargs):
|
||||
# type: (Union[Dict[str, str], Sequence[Tuple[str, str]]], Union[Dict[str, str]]) -> None
|
||||
cd = {}
|
||||
if iterable is None:
|
||||
d = kwargs
|
||||
else:
|
||||
d = dict(iterable, **kwargs)
|
||||
for k, v in d.items():
|
||||
cd[k.capitalize()] = v
|
||||
if hasattr(self.request, '_boundary'):
|
||||
self.request._boundary = None
|
||||
if hasattr(self.request, '_bytes'):
|
||||
self.request._bytes = None
|
||||
return self._dict.update(cd)
|
||||
|
||||
def __delitem__(self, key):
|
||||
# type: (str) -> None
|
||||
key = key.capitalize()
|
||||
if hasattr(self.request, '_boundary'):
|
||||
self.request._boundary = None
|
||||
if hasattr(self.request, '_bytes'):
|
||||
self.request._bytes = None
|
||||
del self._dict[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# type: (str, str) -> None
|
||||
key = key.capitalize()
|
||||
if key != 'Content-length':
|
||||
if hasattr(self.request, '_boundary'):
|
||||
self.request._boundary = None
|
||||
if hasattr(self.request, '_bytes'):
|
||||
self.request._bytes = None
|
||||
return self._dict.__setitem__(key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# type: (str) -> None
|
||||
key = key.capitalize()
|
||||
if key == 'Content-length':
|
||||
data = self.request.data
|
||||
if data is None:
|
||||
content_length = 0
|
||||
else:
|
||||
content_length = len(data)
|
||||
value = str(content_length)
|
||||
else:
|
||||
try:
|
||||
value = self._dict.__getitem__(key)
|
||||
except KeyError as e:
|
||||
if key == 'Content-type':
|
||||
if hasattr(self.request, 'parts') and self.request.parts:
|
||||
value = 'multipart/form-data'
|
||||
if (
|
||||
(value is not None) and
|
||||
value.strip().lower()[:9] == 'multipart' and
|
||||
hasattr(self.request, 'boundary')
|
||||
):
|
||||
value += '; boundary=' + str(self.request.boundary, encoding='utf-8')
|
||||
return value
|
||||
|
||||
def keys(self):
|
||||
# type: (...) -> Iterable[str]
|
||||
return (k for k in self)
|
||||
|
||||
def values(self):
|
||||
return (self[k] for k in self)
|
||||
|
||||
def __len__(self):
|
||||
return len(tuple(self))
|
||||
|
||||
def __iter__(self):
|
||||
# type: (...) -> Iterable[str]
|
||||
keys = set()
|
||||
for k in self._dict.keys():
|
||||
keys.add(k)
|
||||
yield k
|
||||
if type(self.request) is not Part:
|
||||
# *Always* include "Content-length"
|
||||
if 'Content-length' not in keys:
|
||||
yield 'Content-length'
|
||||
if (
|
||||
hasattr(self.request, 'parts') and
|
||||
self.request.parts and
|
||||
('Content-type' not in keys)
|
||||
):
|
||||
yield 'Content-type'
|
||||
|
||||
def __contains__(self, key):
|
||||
# type: (str) -> bool
|
||||
return True if key in self.keys() else False
|
||||
|
||||
def items(self):
|
||||
# type: (...) -> Iterable[Tuple[str, str]]
|
||||
for k in self:
|
||||
yield k, self[k]
|
||||
|
||||
def copy(self):
|
||||
# type: (...) -> Headers
|
||||
return self.__class__(
|
||||
self._dict,
|
||||
request=self.request
|
||||
)
|
||||
|
||||
def __copy__(self):
|
||||
# type: (...) -> Headers
|
||||
return self.copy()
|
||||
|
||||
|
||||
class Data(object):
|
||||
"""
|
||||
One of a multipart request's parts.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data=None, # type: Optional[Union[bytes, str, Sequence, Set, dict, Model]]
|
||||
headers=None # type: Optional[Dict[str, str]]
|
||||
):
|
||||
"""
|
||||
Parameters:
|
||||
|
||||
- data (bytes|str|collections.Sequence|collections.Set|dict|serial.abc.Model): The payload.
|
||||
|
||||
- headers ({str: str}): A dictionary of headers (for this part of the request body, not the main request).
|
||||
This should (almost) always include values for "Content-Disposition" and "Content-Type".
|
||||
"""
|
||||
self._bytes = None # type: Optional[bytes]
|
||||
self._headers = None
|
||||
self._data = None
|
||||
self.headers = headers # type: Dict[str, str]
|
||||
self.data = data # type: Optional[bytes]
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return self._headers
|
||||
|
||||
@headers.setter
|
||||
def headers(self, headers):
|
||||
self._bytes = None
|
||||
if headers is None:
|
||||
headers = Headers({}, self)
|
||||
elif isinstance(headers, Headers):
|
||||
headers.request = self
|
||||
else:
|
||||
headers = Headers(headers, self)
|
||||
self._headers = headers
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
# type: (Optional[Union[bytes, str, Sequence, Set, dict, Model]]) -> None
|
||||
self._bytes = None
|
||||
if data is not None:
|
||||
serialize_type = None
|
||||
if 'Content-type' in self.headers:
|
||||
ct = self.headers['Content-type']
|
||||
if re.search(r'/json\b', ct) is not None:
|
||||
serialize_type = 'json'
|
||||
if re.search(r'/xml\b', ct) is not None:
|
||||
serialize_type = 'xml'
|
||||
if re.search(r'/yaml\b', ct) is not None:
|
||||
serialize_type = 'yaml'
|
||||
if isinstance(data, (Model, dict)) or (
|
||||
isinstance(data, (collections_abc.Sequence, collections_abc.Set)) and not
|
||||
isinstance(data, (str, bytes))
|
||||
):
|
||||
data = serialize(data, serialize_type or 'json')
|
||||
if isinstance(data, str):
|
||||
data = bytes(data, encoding='utf-8')
|
||||
self._data = data
|
||||
|
||||
def __bytes__(self):
|
||||
if self._bytes is None:
|
||||
lines = []
|
||||
for k, v in self.headers.items():
|
||||
lines.append(bytes(
|
||||
'%s: %s' % (k, v),
|
||||
encoding='utf-8'
|
||||
))
|
||||
lines.append(b'')
|
||||
data = self.data
|
||||
if data:
|
||||
lines.append(self.data)
|
||||
self._bytes = b'\r\n'.join(lines) + b'\r\n'
|
||||
return self._bytes
|
||||
|
||||
def __str__(self):
|
||||
b = self.__bytes__()
|
||||
if not isinstance(b, native_str):
|
||||
b = repr(b)[2:-1].replace('\\r\\n', '\r\n').replace('\\n', '\n')
|
||||
return b
|
||||
|
||||
|
||||
class Part(Data):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data=None, # type: Optional[Union[bytes, str, Sequence, Set, dict, Model]]
|
||||
headers=None, # type: Optional[Dict[str, str]]
|
||||
parts=None # type: Optional[Sequence[Part]]
|
||||
):
|
||||
"""
|
||||
Parameters:
|
||||
|
||||
- data (bytes|str|collections.Sequence|collections.Set|dict|serial.abc.Model): The payload.
|
||||
|
||||
- headers ({str: str}): A dictionary of headers (for this part of the request body, not the main request).
|
||||
This should (almost) always include values for "Content-Disposition" and "Content-Type".
|
||||
"""
|
||||
self._boundary = None # type: Optional[bytes]
|
||||
self._parts = None # type: Optional[Parts]
|
||||
self.parts = parts
|
||||
Data.__init__(self, data=data, headers=headers)
|
||||
|
||||
@property
|
||||
def boundary(self):
|
||||
"""
|
||||
Calculates a boundary which is not contained in any of the request parts.
|
||||
"""
|
||||
if self._boundary is None:
|
||||
data = b'\r\n'.join(
|
||||
[self._data or b''] +
|
||||
[bytes(p) for p in self.parts]
|
||||
)
|
||||
boundary = b''.join(
|
||||
bytes(
|
||||
random.choice(string.digits + string.ascii_letters),
|
||||
encoding='utf-8'
|
||||
)
|
||||
for i in range(16)
|
||||
)
|
||||
while boundary in data:
|
||||
boundary += bytes(
|
||||
random.choice(string.digits + string.ascii_letters),
|
||||
encoding='utf-8'
|
||||
)
|
||||
self._boundary = boundary
|
||||
return self._boundary
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
# type: (bytes) -> None
|
||||
if self.parts:
|
||||
data = (b'\r\n--' + self.boundary + b'\r\n').join(
|
||||
[self._data or b''] +
|
||||
[bytes(p).rstrip() for p in self.parts]
|
||||
) + (b'\r\n--' + self.boundary + b'--')
|
||||
else:
|
||||
data = self._data
|
||||
return data
|
||||
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
return Data.data.__set__(self, data)
|
||||
|
||||
@property
|
||||
def parts(self):
|
||||
# type: (...) -> Parts
|
||||
return self._parts
|
||||
|
||||
@parts.setter
|
||||
def parts(self, parts):
|
||||
# type: (Optional[Sequence[Part]]) -> None
|
||||
if parts is None:
|
||||
parts = Parts([], request=self)
|
||||
elif isinstance(parts, Parts):
|
||||
parts.request = self
|
||||
else:
|
||||
parts = Parts(parts, request=self)
|
||||
self._boundary = None
|
||||
self._parts = parts
|
||||
|
||||
|
||||
class Parts(list):
|
||||
|
||||
def __init__(self, items, request):
|
||||
# type: (typing.Sequence[Part], MultipartRequest) -> None
|
||||
self.request = request
|
||||
super().__init__(items)
|
||||
|
||||
def append(self, item):
|
||||
# type: (Part) -> None
|
||||
self.request._boundary = None
|
||||
self.request._bytes = None
|
||||
super().append(item)
|
||||
|
||||
def clear(self):
|
||||
# type: (...) -> None
|
||||
self.request._boundary = None
|
||||
self.request._bytes = None
|
||||
super().clear()
|
||||
|
||||
def extend(self, items):
|
||||
# type: (Iterable[Part]) -> None
|
||||
self.request._boundary = None
|
||||
self.request._bytes = None
|
||||
super().extend(items)
|
||||
|
||||
def reverse(self):
|
||||
# type: (...) -> None
|
||||
self.request._boundary = None
|
||||
self.request._bytes = None
|
||||
super().reverse()
|
||||
|
||||
def __delitem__(self, key):
|
||||
# type: (str) -> None
|
||||
self.request._boundary = None
|
||||
self.request._bytes = None
|
||||
super().__delitem__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# type: (str) -> None
|
||||
self.request._boundary = None
|
||||
self.request._bytes = None
|
||||
super().__setitem__(key, value)
|
||||
|
||||
|
||||
class Request(Data, urllib.request.Request):
|
||||
"""
|
||||
A sub-class of `urllib.request.Request` which accommodates additional data types, and serializes `data` in
|
||||
accordance with what is indicated by the request's "Content-Type" header.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url,
|
||||
data=None, # type: Optional[Union[bytes, str, Sequence, Set, dict, Model]]
|
||||
headers=None, # type: Optional[Dict[str, str]]
|
||||
origin_req_host=None, # type: Optional[str]
|
||||
unverifiable=False, # type: bool
|
||||
method=None # type: Optional[str]
|
||||
):
|
||||
# type: (...) -> None
|
||||
self._bytes = None # type: Optional[bytes]
|
||||
self._headers = None
|
||||
self._data = None
|
||||
self.headers = headers
|
||||
urllib.request.Request.__init__(
|
||||
self,
|
||||
url,
|
||||
data=data,
|
||||
headers=headers,
|
||||
origin_req_host=origin_req_host,
|
||||
unverifiable=unverifiable,
|
||||
method=method
|
||||
)
|
||||
|
||||
|
||||
class MultipartRequest(Part, Request):
|
||||
"""
|
||||
A sub-class of `Request` which adds a property (and initialization parameter) to hold the `parts` of a
|
||||
multipart request.
|
||||
|
||||
https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url,
|
||||
data=None, # type: Optional[Union[bytes, str, Sequence, Set, dict, Model]]
|
||||
headers=None, # type: Optional[Dict[str, str]]
|
||||
origin_req_host=None, # type: Optional[str]
|
||||
unverifiable=False, # type: bool
|
||||
method=None, # type: Optional[str]
|
||||
parts=None # type: Optional[Sequence[Part]]
|
||||
):
|
||||
# type: (...) -> None
|
||||
Part.__init__(
|
||||
self,
|
||||
data=data,
|
||||
headers=headers,
|
||||
parts=parts
|
||||
)
|
||||
Request.__init__(
|
||||
self,
|
||||
url,
|
||||
data=data,
|
||||
headers=headers,
|
||||
origin_req_host=origin_req_host,
|
||||
unverifiable=unverifiable,
|
||||
method=method
|
||||
)
|
||||
1346
backend/venv/lib/python3.6/site-packages/serial/rfc2217.py
Normal file
1346
backend/venv/lib/python3.6/site-packages/serial/rfc2217.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
backend/venv/lib/python3.6/site-packages/serial/rfc2217.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/rfc2217.pyc
Normal file
Binary file not shown.
92
backend/venv/lib/python3.6/site-packages/serial/rs485.py
Normal file
92
backend/venv/lib/python3.6/site-packages/serial/rs485.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# RS485 support
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
The settings for RS485 are stored in a dedicated object that can be applied to
|
||||
serial ports (where supported).
|
||||
NOTE: Some implementations may only support a subset of the settings.
|
||||
"""
|
||||
|
||||
import time
|
||||
import serial
|
||||
|
||||
|
||||
class RS485Settings(object):
|
||||
def __init__(
|
||||
self,
|
||||
rts_level_for_tx=True,
|
||||
rts_level_for_rx=False,
|
||||
loopback=False,
|
||||
delay_before_tx=None,
|
||||
delay_before_rx=None):
|
||||
self.rts_level_for_tx = rts_level_for_tx
|
||||
self.rts_level_for_rx = rts_level_for_rx
|
||||
self.loopback = loopback
|
||||
self.delay_before_tx = delay_before_tx
|
||||
self.delay_before_rx = delay_before_rx
|
||||
|
||||
|
||||
class RS485(serial.Serial):
|
||||
"""\
|
||||
A subclass that replaces the write method with one that toggles RTS
|
||||
according to the RS485 settings.
|
||||
|
||||
NOTE: This may work unreliably on some serial ports (control signals not
|
||||
synchronized or delayed compared to data). Using delays may be
|
||||
unreliable (varying times, larger than expected) as the OS may not
|
||||
support very fine grained delays (no smaller than in the order of
|
||||
tens of milliseconds).
|
||||
|
||||
NOTE: Some implementations support this natively. Better performance
|
||||
can be expected when the native version is used.
|
||||
|
||||
NOTE: The loopback property is ignored by this implementation. The actual
|
||||
behavior depends on the used hardware.
|
||||
|
||||
Usage:
|
||||
|
||||
ser = RS485(...)
|
||||
ser.rs485_mode = RS485Settings(...)
|
||||
ser.write(b'hello')
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RS485, self).__init__(*args, **kwargs)
|
||||
self._alternate_rs485_settings = None
|
||||
|
||||
def write(self, b):
|
||||
"""Write to port, controlling RTS before and after transmitting."""
|
||||
if self._alternate_rs485_settings is not None:
|
||||
# apply level for TX and optional delay
|
||||
self.setRTS(self._alternate_rs485_settings.rts_level_for_tx)
|
||||
if self._alternate_rs485_settings.delay_before_tx is not None:
|
||||
time.sleep(self._alternate_rs485_settings.delay_before_tx)
|
||||
# write and wait for data to be written
|
||||
super(RS485, self).write(b)
|
||||
super(RS485, self).flush()
|
||||
# optional delay and apply level for RX
|
||||
if self._alternate_rs485_settings.delay_before_rx is not None:
|
||||
time.sleep(self._alternate_rs485_settings.delay_before_rx)
|
||||
self.setRTS(self._alternate_rs485_settings.rts_level_for_rx)
|
||||
else:
|
||||
super(RS485, self).write(b)
|
||||
|
||||
# redirect where the property stores the settings so that underlying Serial
|
||||
# instance does not see them
|
||||
@property
|
||||
def rs485_mode(self):
|
||||
"""\
|
||||
Enable RS485 mode and apply new settings, set to None to disable.
|
||||
See serial.rs485.RS485Settings for more info about the value.
|
||||
"""
|
||||
return self._alternate_rs485_settings
|
||||
|
||||
@rs485_mode.setter
|
||||
def rs485_mode(self, rs485_settings):
|
||||
self._alternate_rs485_settings = rs485_settings
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/rs485.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/rs485.pyc
Normal file
Binary file not shown.
251
backend/venv/lib/python3.6/site-packages/serial/serialcli.py
Normal file
251
backend/venv/lib/python3.6/site-packages/serial/serialcli.py
Normal file
@@ -0,0 +1,251 @@
|
||||
#! python
|
||||
#
|
||||
# Backend for .NET/Mono (IronPython), .NET >= 2
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2008-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import System
|
||||
import System.IO.Ports
|
||||
from serial.serialutil import *
|
||||
|
||||
# must invoke function with byte array, make a helper to convert strings
|
||||
# to byte arrays
|
||||
sab = System.Array[System.Byte]
|
||||
|
||||
|
||||
def as_byte_array(string):
|
||||
return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for .NET/Mono."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
try:
|
||||
self._port_handle = System.IO.Ports.SerialPort(self.portstr)
|
||||
except Exception as msg:
|
||||
self._port_handle = None
|
||||
raise SerialException("could not open port %s: %s" % (self.portstr, msg))
|
||||
|
||||
# if RTS and/or DTR are not set before open, they default to True
|
||||
if self._rts_state is None:
|
||||
self._rts_state = True
|
||||
if self._dtr_state is None:
|
||||
self._dtr_state = True
|
||||
|
||||
self._reconfigure_port()
|
||||
self._port_handle.Open()
|
||||
self.is_open = True
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
self.reset_input_buffer()
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self._port_handle:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
#~ self._port_handle.ReceivedBytesThreshold = 1
|
||||
|
||||
if self._timeout is None:
|
||||
self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
|
||||
else:
|
||||
self._port_handle.ReadTimeout = int(self._timeout * 1000)
|
||||
|
||||
# if self._timeout != 0 and self._interCharTimeout is not None:
|
||||
# timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:]
|
||||
|
||||
if self._write_timeout is None:
|
||||
self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
|
||||
else:
|
||||
self._port_handle.WriteTimeout = int(self._write_timeout * 1000)
|
||||
|
||||
# Setup the connection info.
|
||||
try:
|
||||
self._port_handle.BaudRate = self._baudrate
|
||||
except IOError as e:
|
||||
# catch errors from illegal baudrate settings
|
||||
raise ValueError(str(e))
|
||||
|
||||
if self._bytesize == FIVEBITS:
|
||||
self._port_handle.DataBits = 5
|
||||
elif self._bytesize == SIXBITS:
|
||||
self._port_handle.DataBits = 6
|
||||
elif self._bytesize == SEVENBITS:
|
||||
self._port_handle.DataBits = 7
|
||||
elif self._bytesize == EIGHTBITS:
|
||||
self._port_handle.DataBits = 8
|
||||
else:
|
||||
raise ValueError("Unsupported number of data bits: %r" % self._bytesize)
|
||||
|
||||
if self._parity == PARITY_NONE:
|
||||
self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k
|
||||
elif self._parity == PARITY_EVEN:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Even
|
||||
elif self._parity == PARITY_ODD:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Odd
|
||||
elif self._parity == PARITY_MARK:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Mark
|
||||
elif self._parity == PARITY_SPACE:
|
||||
self._port_handle.Parity = System.IO.Ports.Parity.Space
|
||||
else:
|
||||
raise ValueError("Unsupported parity mode: %r" % self._parity)
|
||||
|
||||
if self._stopbits == STOPBITS_ONE:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.One
|
||||
elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive
|
||||
elif self._stopbits == STOPBITS_TWO:
|
||||
self._port_handle.StopBits = System.IO.Ports.StopBits.Two
|
||||
else:
|
||||
raise ValueError("Unsupported number of stop bits: %r" % self._stopbits)
|
||||
|
||||
if self._rtscts and self._xonxoff:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff
|
||||
elif self._rtscts:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend
|
||||
elif self._xonxoff:
|
||||
self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff
|
||||
else:
|
||||
self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k
|
||||
|
||||
#~ def __del__(self):
|
||||
#~ self.close()
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self._port_handle:
|
||||
try:
|
||||
self._port_handle.Close()
|
||||
except System.IO.Ports.InvalidOperationException:
|
||||
# ignore errors. can happen for unplugged USB serial devices
|
||||
pass
|
||||
self._port_handle = None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of characters currently in the input buffer."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
return self._port_handle.BytesToRead
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
# must use single byte reads as this is the only way to read
|
||||
# without applying encodings
|
||||
data = bytearray()
|
||||
while size:
|
||||
try:
|
||||
data.append(self._port_handle.ReadByte())
|
||||
except System.TimeoutException:
|
||||
break
|
||||
else:
|
||||
size -= 1
|
||||
return bytes(data)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
#~ if not isinstance(data, (bytes, bytearray)):
|
||||
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
try:
|
||||
# must call overloaded method with byte array argument
|
||||
# as this is the only one not applying encodings
|
||||
self._port_handle.Write(as_byte_array(data), 0, len(data))
|
||||
except System.TimeoutException:
|
||||
raise writeTimeoutError
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
self._port_handle.DiscardInBuffer()
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
self._port_handle.DiscardOutBuffer()
|
||||
|
||||
def _update_break_state(self):
|
||||
"""
|
||||
Set break: Controls TXD. When active, to transmitting is possible.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
self._port_handle.BreakState = bool(self._break_state)
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
self._port_handle.RtsEnable = bool(self._rts_state)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
self._port_handle.DtrEnable = bool(self._dtr_state)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
return self._port_handle.CtsHolding
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
return self._port_handle.DsrHolding
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
#~ return self._port_handle.XXX
|
||||
return False # XXX an error would be better
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
return self._port_handle.CDHolding
|
||||
|
||||
# - - platform specific - - - -
|
||||
# none
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/serialcli.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/serialcli.pyc
Normal file
Binary file not shown.
249
backend/venv/lib/python3.6/site-packages/serial/serialjava.py
Normal file
249
backend/venv/lib/python3.6/site-packages/serial/serialjava.py
Normal file
@@ -0,0 +1,249 @@
|
||||
#!jython
|
||||
#
|
||||
# Backend Jython with JavaComm
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from serial.serialutil import *
|
||||
|
||||
|
||||
def my_import(name):
|
||||
mod = __import__(name)
|
||||
components = name.split('.')
|
||||
for comp in components[1:]:
|
||||
mod = getattr(mod, comp)
|
||||
return mod
|
||||
|
||||
|
||||
def detect_java_comm(names):
|
||||
"""try given list of modules and return that imports"""
|
||||
for name in names:
|
||||
try:
|
||||
mod = my_import(name)
|
||||
mod.SerialPort
|
||||
return mod
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
raise ImportError("No Java Communications API implementation found")
|
||||
|
||||
|
||||
# Java Communications API implementations
|
||||
# http://mho.republika.pl/java/comm/
|
||||
|
||||
comm = detect_java_comm([
|
||||
'javax.comm', # Sun/IBM
|
||||
'gnu.io', # RXTX
|
||||
])
|
||||
|
||||
|
||||
def device(portnumber):
|
||||
"""Turn a port number into a device name"""
|
||||
enum = comm.CommPortIdentifier.getPortIdentifiers()
|
||||
ports = []
|
||||
while enum.hasMoreElements():
|
||||
el = enum.nextElement()
|
||||
if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL:
|
||||
ports.append(el)
|
||||
return ports[portnumber].getName()
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""\
|
||||
Serial port class, implemented with Java Communications API and
|
||||
thus usable with jython and the appropriate java extension.
|
||||
"""
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
if type(self._port) == type(''): # strings are taken directly
|
||||
portId = comm.CommPortIdentifier.getPortIdentifier(self._port)
|
||||
else:
|
||||
portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port)) # numbers are transformed to a comport id obj
|
||||
try:
|
||||
self.sPort = portId.open("python serial module", 10)
|
||||
except Exception as msg:
|
||||
self.sPort = None
|
||||
raise SerialException("Could not open port: %s" % msg)
|
||||
self._reconfigurePort()
|
||||
self._instream = self.sPort.getInputStream()
|
||||
self._outstream = self.sPort.getOutputStream()
|
||||
self.is_open = True
|
||||
|
||||
def _reconfigurePort(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self.sPort:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
self.sPort.enableReceiveTimeout(30)
|
||||
if self._bytesize == FIVEBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_5
|
||||
elif self._bytesize == SIXBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_6
|
||||
elif self._bytesize == SEVENBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_7
|
||||
elif self._bytesize == EIGHTBITS:
|
||||
jdatabits = comm.SerialPort.DATABITS_8
|
||||
else:
|
||||
raise ValueError("unsupported bytesize: %r" % self._bytesize)
|
||||
|
||||
if self._stopbits == STOPBITS_ONE:
|
||||
jstopbits = comm.SerialPort.STOPBITS_1
|
||||
elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
|
||||
jstopbits = comm.SerialPort.STOPBITS_1_5
|
||||
elif self._stopbits == STOPBITS_TWO:
|
||||
jstopbits = comm.SerialPort.STOPBITS_2
|
||||
else:
|
||||
raise ValueError("unsupported number of stopbits: %r" % self._stopbits)
|
||||
|
||||
if self._parity == PARITY_NONE:
|
||||
jparity = comm.SerialPort.PARITY_NONE
|
||||
elif self._parity == PARITY_EVEN:
|
||||
jparity = comm.SerialPort.PARITY_EVEN
|
||||
elif self._parity == PARITY_ODD:
|
||||
jparity = comm.SerialPort.PARITY_ODD
|
||||
elif self._parity == PARITY_MARK:
|
||||
jparity = comm.SerialPort.PARITY_MARK
|
||||
elif self._parity == PARITY_SPACE:
|
||||
jparity = comm.SerialPort.PARITY_SPACE
|
||||
else:
|
||||
raise ValueError("unsupported parity type: %r" % self._parity)
|
||||
|
||||
jflowin = jflowout = 0
|
||||
if self._rtscts:
|
||||
jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN
|
||||
jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT
|
||||
if self._xonxoff:
|
||||
jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN
|
||||
jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT
|
||||
|
||||
self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity)
|
||||
self.sPort.setFlowControlMode(jflowin | jflowout)
|
||||
|
||||
if self._timeout >= 0:
|
||||
self.sPort.enableReceiveTimeout(int(self._timeout*1000))
|
||||
else:
|
||||
self.sPort.disableReceiveTimeout()
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self.sPort:
|
||||
self._instream.close()
|
||||
self._outstream.close()
|
||||
self.sPort.close()
|
||||
self.sPort = None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of characters currently in the input buffer."""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
return self._instream.available()
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
read = bytearray()
|
||||
if size > 0:
|
||||
while len(read) < size:
|
||||
x = self._instream.read()
|
||||
if x == -1:
|
||||
if self.timeout >= 0:
|
||||
break
|
||||
else:
|
||||
read.append(x)
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given string over the serial port."""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
if not isinstance(data, (bytes, bytearray)):
|
||||
raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
self._outstream.write(data)
|
||||
return len(data)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self._instream.skip(self._instream.available())
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and
|
||||
discarding all that is in the buffer.
|
||||
"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self._outstream.flush()
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""Send break condition. Timed, returns to idle state after given duration."""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self.sPort.sendBreak(duration*1000.0)
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is possible."""
|
||||
if self.fd is None:
|
||||
raise portNotOpenError
|
||||
raise SerialException("The _update_break_state function is not implemented in java.")
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self.sPort.setRTS(self._rts_state)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self.sPort.setDTR(self._dtr_state)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self.sPort.isCTS()
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self.sPort.isDSR()
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self.sPort.isRI()
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.sPort:
|
||||
raise portNotOpenError
|
||||
self.sPort.isCD()
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/serialjava.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/serialjava.pyc
Normal file
Binary file not shown.
811
backend/venv/lib/python3.6/site-packages/serial/serialposix.py
Normal file
811
backend/venv/lib/python3.6/site-packages/serial/serialposix.py
Normal file
@@ -0,0 +1,811 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# backend for serial IO for POSIX compatible systems, like Linux, OSX
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# parts based on code from Grant B. Edwards <grante@visi.com>:
|
||||
# ftp://ftp.visi.com/users/grante/python/PosixSerial.py
|
||||
#
|
||||
# references: http://www.easysw.com/~mike/serial/serial.html
|
||||
|
||||
# Collection of port names (was previously used by number_to_device which was
|
||||
# removed.
|
||||
# - Linux /dev/ttyS%d (confirmed)
|
||||
# - cygwin/win32 /dev/com%d (confirmed)
|
||||
# - openbsd (OpenBSD) /dev/cua%02d
|
||||
# - bsd*, freebsd* /dev/cuad%d
|
||||
# - darwin (OS X) /dev/cuad%d
|
||||
# - netbsd /dev/dty%02d (NetBSD 1.6 testing by Erk)
|
||||
# - irix (IRIX) /dev/ttyf%d (partially tested) names depending on flow control
|
||||
# - hp (HP-UX) /dev/tty%dp0 (not tested)
|
||||
# - sunos (Solaris/SunOS) /dev/tty%c (letters, 'a'..'z') (confirmed)
|
||||
# - aix (AIX) /dev/tty%d
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
import select
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
|
||||
import serial
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, \
|
||||
portNotOpenError, writeTimeoutError, Timeout
|
||||
|
||||
|
||||
class PlatformSpecificBase(object):
|
||||
BAUDRATE_CONSTANTS = {}
|
||||
|
||||
def _set_special_baudrate(self, baudrate):
|
||||
raise NotImplementedError('non-standard baudrates are not supported on this platform')
|
||||
|
||||
def _set_rs485_mode(self, rs485_settings):
|
||||
raise NotImplementedError('RS485 not supported on this platform')
|
||||
|
||||
|
||||
# some systems support an extra flag to enable the two in POSIX unsupported
|
||||
# paritiy settings for MARK and SPACE
|
||||
CMSPAR = 0 # default, for unsupported platforms, override below
|
||||
|
||||
# try to detect the OS so that a device can be selected...
|
||||
# this code block should supply a device() and set_special_baudrate() function
|
||||
# for the platform
|
||||
plat = sys.platform.lower()
|
||||
|
||||
if plat[:5] == 'linux': # Linux (confirmed) # noqa
|
||||
import array
|
||||
|
||||
# extra termios flags
|
||||
CMSPAR = 0o10000000000 # Use "stick" (mark/space) parity
|
||||
|
||||
# baudrate ioctls
|
||||
TCGETS2 = 0x802C542A
|
||||
TCSETS2 = 0x402C542B
|
||||
BOTHER = 0o010000
|
||||
|
||||
# RS485 ioctls
|
||||
TIOCGRS485 = 0x542E
|
||||
TIOCSRS485 = 0x542F
|
||||
SER_RS485_ENABLED = 0b00000001
|
||||
SER_RS485_RTS_ON_SEND = 0b00000010
|
||||
SER_RS485_RTS_AFTER_SEND = 0b00000100
|
||||
SER_RS485_RX_DURING_TX = 0b00010000
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
BAUDRATE_CONSTANTS = {
|
||||
0: 0o000000, # hang up
|
||||
50: 0o000001,
|
||||
75: 0o000002,
|
||||
110: 0o000003,
|
||||
134: 0o000004,
|
||||
150: 0o000005,
|
||||
200: 0o000006,
|
||||
300: 0o000007,
|
||||
600: 0o000010,
|
||||
1200: 0o000011,
|
||||
1800: 0o000012,
|
||||
2400: 0o000013,
|
||||
4800: 0o000014,
|
||||
9600: 0o000015,
|
||||
19200: 0o000016,
|
||||
38400: 0o000017,
|
||||
57600: 0o010001,
|
||||
115200: 0o010002,
|
||||
230400: 0o010003,
|
||||
460800: 0o010004,
|
||||
500000: 0o010005,
|
||||
576000: 0o010006,
|
||||
921600: 0o010007,
|
||||
1000000: 0o010010,
|
||||
1152000: 0o010011,
|
||||
1500000: 0o010012,
|
||||
2000000: 0o010013,
|
||||
2500000: 0o010014,
|
||||
3000000: 0o010015,
|
||||
3500000: 0o010016,
|
||||
4000000: 0o010017
|
||||
}
|
||||
|
||||
def _set_special_baudrate(self, baudrate):
|
||||
# right size is 44 on x86_64, allow for some growth
|
||||
buf = array.array('i', [0] * 64)
|
||||
try:
|
||||
# get serial_struct
|
||||
fcntl.ioctl(self.fd, TCGETS2, buf)
|
||||
# set custom speed
|
||||
buf[2] &= ~termios.CBAUD
|
||||
buf[2] |= BOTHER
|
||||
buf[9] = buf[10] = baudrate
|
||||
|
||||
# set serial_struct
|
||||
fcntl.ioctl(self.fd, TCSETS2, buf)
|
||||
except IOError as e:
|
||||
raise ValueError('Failed to set custom baud rate ({}): {}'.format(baudrate, e))
|
||||
|
||||
def _set_rs485_mode(self, rs485_settings):
|
||||
buf = array.array('i', [0] * 8) # flags, delaytx, delayrx, padding
|
||||
try:
|
||||
fcntl.ioctl(self.fd, TIOCGRS485, buf)
|
||||
buf[0] |= SER_RS485_ENABLED
|
||||
if rs485_settings is not None:
|
||||
if rs485_settings.loopback:
|
||||
buf[0] |= SER_RS485_RX_DURING_TX
|
||||
else:
|
||||
buf[0] &= ~SER_RS485_RX_DURING_TX
|
||||
if rs485_settings.rts_level_for_tx:
|
||||
buf[0] |= SER_RS485_RTS_ON_SEND
|
||||
else:
|
||||
buf[0] &= ~SER_RS485_RTS_ON_SEND
|
||||
if rs485_settings.rts_level_for_rx:
|
||||
buf[0] |= SER_RS485_RTS_AFTER_SEND
|
||||
else:
|
||||
buf[0] &= ~SER_RS485_RTS_AFTER_SEND
|
||||
if rs485_settings.delay_before_tx is not None:
|
||||
buf[1] = int(rs485_settings.delay_before_tx * 1000)
|
||||
if rs485_settings.delay_before_rx is not None:
|
||||
buf[2] = int(rs485_settings.delay_before_rx * 1000)
|
||||
else:
|
||||
buf[0] = 0 # clear SER_RS485_ENABLED
|
||||
fcntl.ioctl(self.fd, TIOCSRS485, buf)
|
||||
except IOError as e:
|
||||
raise ValueError('Failed to set RS485 mode: {}'.format(e))
|
||||
|
||||
|
||||
elif plat == 'cygwin': # cygwin/win32 (confirmed)
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
BAUDRATE_CONSTANTS = {
|
||||
128000: 0x01003,
|
||||
256000: 0x01005,
|
||||
500000: 0x01007,
|
||||
576000: 0x01008,
|
||||
921600: 0x01009,
|
||||
1000000: 0x0100a,
|
||||
1152000: 0x0100b,
|
||||
1500000: 0x0100c,
|
||||
2000000: 0x0100d,
|
||||
2500000: 0x0100e,
|
||||
3000000: 0x0100f
|
||||
}
|
||||
|
||||
|
||||
elif plat[:6] == 'darwin': # OS X
|
||||
import array
|
||||
IOSSIOSPEED = 0x80045402 # _IOW('T', 2, speed_t)
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
osx_version = os.uname()[2].split('.')
|
||||
# Tiger or above can support arbitrary serial speeds
|
||||
if int(osx_version[0]) >= 8:
|
||||
def _set_special_baudrate(self, baudrate):
|
||||
# use IOKit-specific call to set up high speeds
|
||||
buf = array.array('i', [baudrate])
|
||||
fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1)
|
||||
|
||||
elif plat[:3] == 'bsd' or \
|
||||
plat[:7] == 'freebsd' or \
|
||||
plat[:6] == 'netbsd' or \
|
||||
plat[:7] == 'openbsd':
|
||||
|
||||
class ReturnBaudrate(object):
|
||||
def __getitem__(self, key):
|
||||
return key
|
||||
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
# Only tested on FreeBSD:
|
||||
# The baud rate may be passed in as
|
||||
# a literal value.
|
||||
BAUDRATE_CONSTANTS = ReturnBaudrate()
|
||||
|
||||
else:
|
||||
class PlatformSpecific(PlatformSpecificBase):
|
||||
pass
|
||||
|
||||
|
||||
# load some constants for later use.
|
||||
# try to use values from termios, use defaults from linux otherwise
|
||||
TIOCMGET = getattr(termios, 'TIOCMGET', 0x5415)
|
||||
TIOCMBIS = getattr(termios, 'TIOCMBIS', 0x5416)
|
||||
TIOCMBIC = getattr(termios, 'TIOCMBIC', 0x5417)
|
||||
TIOCMSET = getattr(termios, 'TIOCMSET', 0x5418)
|
||||
|
||||
# TIOCM_LE = getattr(termios, 'TIOCM_LE', 0x001)
|
||||
TIOCM_DTR = getattr(termios, 'TIOCM_DTR', 0x002)
|
||||
TIOCM_RTS = getattr(termios, 'TIOCM_RTS', 0x004)
|
||||
# TIOCM_ST = getattr(termios, 'TIOCM_ST', 0x008)
|
||||
# TIOCM_SR = getattr(termios, 'TIOCM_SR', 0x010)
|
||||
|
||||
TIOCM_CTS = getattr(termios, 'TIOCM_CTS', 0x020)
|
||||
TIOCM_CAR = getattr(termios, 'TIOCM_CAR', 0x040)
|
||||
TIOCM_RNG = getattr(termios, 'TIOCM_RNG', 0x080)
|
||||
TIOCM_DSR = getattr(termios, 'TIOCM_DSR', 0x100)
|
||||
TIOCM_CD = getattr(termios, 'TIOCM_CD', TIOCM_CAR)
|
||||
TIOCM_RI = getattr(termios, 'TIOCM_RI', TIOCM_RNG)
|
||||
# TIOCM_OUT1 = getattr(termios, 'TIOCM_OUT1', 0x2000)
|
||||
# TIOCM_OUT2 = getattr(termios, 'TIOCM_OUT2', 0x4000)
|
||||
if hasattr(termios, 'TIOCINQ'):
|
||||
TIOCINQ = termios.TIOCINQ
|
||||
else:
|
||||
TIOCINQ = getattr(termios, 'FIONREAD', 0x541B)
|
||||
TIOCOUTQ = getattr(termios, 'TIOCOUTQ', 0x5411)
|
||||
|
||||
TIOCM_zero_str = struct.pack('I', 0)
|
||||
TIOCM_RTS_str = struct.pack('I', TIOCM_RTS)
|
||||
TIOCM_DTR_str = struct.pack('I', TIOCM_DTR)
|
||||
|
||||
TIOCSBRK = getattr(termios, 'TIOCSBRK', 0x5427)
|
||||
TIOCCBRK = getattr(termios, 'TIOCCBRK', 0x5428)
|
||||
|
||||
|
||||
class Serial(SerialBase, PlatformSpecific):
|
||||
"""\
|
||||
Serial port class POSIX implementation. Serial port configuration is
|
||||
done with termios and fcntl. Runs on Linux and many other Un*x like
|
||||
systems.
|
||||
"""
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened."""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
self.fd = None
|
||||
# open
|
||||
try:
|
||||
self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
|
||||
except OSError as msg:
|
||||
self.fd = None
|
||||
raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
|
||||
#~ fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # set blocking
|
||||
|
||||
try:
|
||||
self._reconfigure_port(force_update=True)
|
||||
except:
|
||||
try:
|
||||
os.close(self.fd)
|
||||
except:
|
||||
# ignore any exception when closing the port
|
||||
# also to keep original exception that happened when setting up
|
||||
pass
|
||||
self.fd = None
|
||||
raise
|
||||
else:
|
||||
self.is_open = True
|
||||
try:
|
||||
if not self._dsrdtr:
|
||||
self._update_dtr_state()
|
||||
if not self._rtscts:
|
||||
self._update_rts_state()
|
||||
except IOError as e:
|
||||
if e.errno in (errno.EINVAL, errno.ENOTTY):
|
||||
# ignore Invalid argument and Inappropriate ioctl
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
self.reset_input_buffer()
|
||||
self.pipe_abort_read_r, self.pipe_abort_read_w = os.pipe()
|
||||
self.pipe_abort_write_r, self.pipe_abort_write_w = os.pipe()
|
||||
fcntl.fcntl(self.pipe_abort_read_r, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
fcntl.fcntl(self.pipe_abort_write_r, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
|
||||
def _reconfigure_port(self, force_update=False):
|
||||
"""Set communication parameters on opened port."""
|
||||
if self.fd is None:
|
||||
raise SerialException("Can only operate on a valid file descriptor")
|
||||
|
||||
# if exclusive lock is requested, create it before we modify anything else
|
||||
if self._exclusive is not None:
|
||||
if self._exclusive:
|
||||
try:
|
||||
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except IOError as msg:
|
||||
raise SerialException(msg.errno, "Could not exclusively lock port {}: {}".format(self._port, msg))
|
||||
else:
|
||||
fcntl.flock(self.fd, fcntl.LOCK_UN)
|
||||
|
||||
custom_baud = None
|
||||
|
||||
vmin = vtime = 0 # timeout is done via select
|
||||
if self._inter_byte_timeout is not None:
|
||||
vmin = 1
|
||||
vtime = int(self._inter_byte_timeout * 10)
|
||||
try:
|
||||
orig_attr = termios.tcgetattr(self.fd)
|
||||
iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
|
||||
except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here
|
||||
raise SerialException("Could not configure port: {}".format(msg))
|
||||
# set up raw mode / no echo / binary
|
||||
cflag |= (termios.CLOCAL | termios.CREAD)
|
||||
lflag &= ~(termios.ICANON | termios.ECHO | termios.ECHOE |
|
||||
termios.ECHOK | termios.ECHONL |
|
||||
termios.ISIG | termios.IEXTEN) # |termios.ECHOPRT
|
||||
for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk
|
||||
if hasattr(termios, flag):
|
||||
lflag &= ~getattr(termios, flag)
|
||||
|
||||
oflag &= ~(termios.OPOST | termios.ONLCR | termios.OCRNL)
|
||||
iflag &= ~(termios.INLCR | termios.IGNCR | termios.ICRNL | termios.IGNBRK)
|
||||
if hasattr(termios, 'IUCLC'):
|
||||
iflag &= ~termios.IUCLC
|
||||
if hasattr(termios, 'PARMRK'):
|
||||
iflag &= ~termios.PARMRK
|
||||
|
||||
# setup baud rate
|
||||
try:
|
||||
ispeed = ospeed = getattr(termios, 'B{}'.format(self._baudrate))
|
||||
except AttributeError:
|
||||
try:
|
||||
ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate]
|
||||
except KeyError:
|
||||
#~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
|
||||
# may need custom baud rate, it isn't in our list.
|
||||
ispeed = ospeed = getattr(termios, 'B38400')
|
||||
try:
|
||||
custom_baud = int(self._baudrate) # store for later
|
||||
except ValueError:
|
||||
raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate))
|
||||
else:
|
||||
if custom_baud < 0:
|
||||
raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate))
|
||||
|
||||
# setup char len
|
||||
cflag &= ~termios.CSIZE
|
||||
if self._bytesize == 8:
|
||||
cflag |= termios.CS8
|
||||
elif self._bytesize == 7:
|
||||
cflag |= termios.CS7
|
||||
elif self._bytesize == 6:
|
||||
cflag |= termios.CS6
|
||||
elif self._bytesize == 5:
|
||||
cflag |= termios.CS5
|
||||
else:
|
||||
raise ValueError('Invalid char len: {!r}'.format(self._bytesize))
|
||||
# setup stop bits
|
||||
if self._stopbits == serial.STOPBITS_ONE:
|
||||
cflag &= ~(termios.CSTOPB)
|
||||
elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
|
||||
cflag |= (termios.CSTOPB) # XXX same as TWO.. there is no POSIX support for 1.5
|
||||
elif self._stopbits == serial.STOPBITS_TWO:
|
||||
cflag |= (termios.CSTOPB)
|
||||
else:
|
||||
raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits))
|
||||
# setup parity
|
||||
iflag &= ~(termios.INPCK | termios.ISTRIP)
|
||||
if self._parity == serial.PARITY_NONE:
|
||||
cflag &= ~(termios.PARENB | termios.PARODD | CMSPAR)
|
||||
elif self._parity == serial.PARITY_EVEN:
|
||||
cflag &= ~(termios.PARODD | CMSPAR)
|
||||
cflag |= (termios.PARENB)
|
||||
elif self._parity == serial.PARITY_ODD:
|
||||
cflag &= ~CMSPAR
|
||||
cflag |= (termios.PARENB | termios.PARODD)
|
||||
elif self._parity == serial.PARITY_MARK and CMSPAR:
|
||||
cflag |= (termios.PARENB | CMSPAR | termios.PARODD)
|
||||
elif self._parity == serial.PARITY_SPACE and CMSPAR:
|
||||
cflag |= (termios.PARENB | CMSPAR)
|
||||
cflag &= ~(termios.PARODD)
|
||||
else:
|
||||
raise ValueError('Invalid parity: {!r}'.format(self._parity))
|
||||
# setup flow control
|
||||
# xonxoff
|
||||
if hasattr(termios, 'IXANY'):
|
||||
if self._xonxoff:
|
||||
iflag |= (termios.IXON | termios.IXOFF) # |termios.IXANY)
|
||||
else:
|
||||
iflag &= ~(termios.IXON | termios.IXOFF | termios.IXANY)
|
||||
else:
|
||||
if self._xonxoff:
|
||||
iflag |= (termios.IXON | termios.IXOFF)
|
||||
else:
|
||||
iflag &= ~(termios.IXON | termios.IXOFF)
|
||||
# rtscts
|
||||
if hasattr(termios, 'CRTSCTS'):
|
||||
if self._rtscts:
|
||||
cflag |= (termios.CRTSCTS)
|
||||
else:
|
||||
cflag &= ~(termios.CRTSCTS)
|
||||
elif hasattr(termios, 'CNEW_RTSCTS'): # try it with alternate constant name
|
||||
if self._rtscts:
|
||||
cflag |= (termios.CNEW_RTSCTS)
|
||||
else:
|
||||
cflag &= ~(termios.CNEW_RTSCTS)
|
||||
# XXX should there be a warning if setting up rtscts (and xonxoff etc) fails??
|
||||
|
||||
# buffer
|
||||
# vmin "minimal number of characters to be read. 0 for non blocking"
|
||||
if vmin < 0 or vmin > 255:
|
||||
raise ValueError('Invalid vmin: {!r}'.format(vmin))
|
||||
cc[termios.VMIN] = vmin
|
||||
# vtime
|
||||
if vtime < 0 or vtime > 255:
|
||||
raise ValueError('Invalid vtime: {!r}'.format(vtime))
|
||||
cc[termios.VTIME] = vtime
|
||||
# activate settings
|
||||
if force_update or [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr:
|
||||
termios.tcsetattr(
|
||||
self.fd,
|
||||
termios.TCSANOW,
|
||||
[iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
|
||||
|
||||
# apply custom baud rate, if any
|
||||
if custom_baud is not None:
|
||||
self._set_special_baudrate(custom_baud)
|
||||
|
||||
if self._rs485_mode is not None:
|
||||
self._set_rs485_mode(self._rs485_mode)
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
if self.fd is not None:
|
||||
os.close(self.fd)
|
||||
self.fd = None
|
||||
os.close(self.pipe_abort_read_w)
|
||||
os.close(self.pipe_abort_read_r)
|
||||
os.close(self.pipe_abort_write_w)
|
||||
os.close(self.pipe_abort_write_r)
|
||||
self.pipe_abort_read_r, self.pipe_abort_read_w = None, None
|
||||
self.pipe_abort_write_r, self.pipe_abort_write_w = None, None
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
#~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str)
|
||||
s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0]
|
||||
|
||||
# select based implementation, proved to work on many systems
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
read = bytearray()
|
||||
timeout = Timeout(self._timeout)
|
||||
while len(read) < size:
|
||||
try:
|
||||
ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left())
|
||||
if self.pipe_abort_read_r in ready:
|
||||
os.read(self.pipe_abort_read_r, 1000)
|
||||
break
|
||||
# If select was used with a timeout, and the timeout occurs, it
|
||||
# returns with empty lists -> thus abort read operation.
|
||||
# For timeout == 0 (non-blocking operation) also abort when
|
||||
# there is nothing to read.
|
||||
if not ready:
|
||||
break # timeout
|
||||
buf = os.read(self.fd, size - len(read))
|
||||
# read should always return some data as select reported it was
|
||||
# ready to read when we get to this point.
|
||||
if not buf:
|
||||
# Disconnected devices, at least on Linux, show the
|
||||
# behavior that they are always ready to read immediately
|
||||
# but reading returns nothing.
|
||||
raise SerialException(
|
||||
'device reports readiness to read but returned no data '
|
||||
'(device disconnected or multiple access on port?)')
|
||||
read.extend(buf)
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
except select.error as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('read failed: {}'.format(e))
|
||||
if timeout.expired():
|
||||
break
|
||||
return bytes(read)
|
||||
|
||||
def cancel_read(self):
|
||||
if self.is_open:
|
||||
os.write(self.pipe_abort_read_w, b"x")
|
||||
|
||||
def cancel_write(self):
|
||||
if self.is_open:
|
||||
os.write(self.pipe_abort_write_w, b"x")
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given byte string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
d = to_bytes(data)
|
||||
tx_len = length = len(d)
|
||||
timeout = Timeout(self._write_timeout)
|
||||
while tx_len > 0:
|
||||
try:
|
||||
n = os.write(self.fd, d)
|
||||
if timeout.is_non_blocking:
|
||||
# Zero timeout indicates non-blocking - simply return the
|
||||
# number of bytes of data actually written
|
||||
return n
|
||||
elif not timeout.is_infinite:
|
||||
# when timeout is set, use select to wait for being ready
|
||||
# with the time left as timeout
|
||||
if timeout.expired():
|
||||
raise writeTimeoutError
|
||||
abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], timeout.time_left())
|
||||
if abort:
|
||||
os.read(self.pipe_abort_write_r, 1000)
|
||||
break
|
||||
if not ready:
|
||||
raise writeTimeoutError
|
||||
else:
|
||||
assert timeout.time_left() is None
|
||||
# wait for write operation
|
||||
abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], None)
|
||||
if abort:
|
||||
os.read(self.pipe_abort_write_r, 1)
|
||||
break
|
||||
if not ready:
|
||||
raise SerialException('write failed (select)')
|
||||
d = d[n:]
|
||||
tx_len -= n
|
||||
except SerialException:
|
||||
raise
|
||||
except OSError as e:
|
||||
# this is for Python 3.x where select.error is a subclass of
|
||||
# OSError ignore BlockingIOErrors and EINTR. other errors are shown
|
||||
# https://www.python.org/dev/peps/pep-0475.
|
||||
if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
except select.error as e:
|
||||
# this is for Python 2.x
|
||||
# ignore BlockingIOErrors and EINTR. all errors are shown
|
||||
# see also http://www.python.org/dev/peps/pep-3151/#select
|
||||
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
|
||||
raise SerialException('write failed: {}'.format(e))
|
||||
if not timeout.is_non_blocking and timeout.expired():
|
||||
raise writeTimeoutError
|
||||
return length - len(d)
|
||||
|
||||
def flush(self):
|
||||
"""\
|
||||
Flush of file like objects. In this case, wait until all data
|
||||
is written.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
termios.tcdrain(self.fd)
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
termios.tcflush(self.fd, termios.TCIFLUSH)
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and discarding all
|
||||
that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
termios.tcflush(self.fd, termios.TCOFLUSH)
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""\
|
||||
Send break condition. Timed, returns to idle state after given
|
||||
duration.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
termios.tcsendbreak(self.fd, int(duration / 0.25))
|
||||
|
||||
def _update_break_state(self):
|
||||
"""\
|
||||
Set break: Controls TXD. When active, no transmitting is possible.
|
||||
"""
|
||||
if self._break_state:
|
||||
fcntl.ioctl(self.fd, TIOCSBRK)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, TIOCCBRK)
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self._rts_state:
|
||||
fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self._dtr_state:
|
||||
fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str)
|
||||
else:
|
||||
fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str)
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_CTS != 0
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_DSR != 0
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_RI != 0
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0] & TIOCM_CD != 0
|
||||
|
||||
# - - platform specific - - - -
|
||||
|
||||
@property
|
||||
def out_waiting(self):
|
||||
"""Return the number of bytes currently in the output buffer."""
|
||||
#~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str)
|
||||
s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str)
|
||||
return struct.unpack('I', s)[0]
|
||||
|
||||
def fileno(self):
|
||||
"""\
|
||||
For easier use of the serial port instance with select.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
return self.fd
|
||||
|
||||
def set_input_flow_control(self, enable=True):
|
||||
"""\
|
||||
Manually control flow - when software flow control is enabled.
|
||||
This will send XON (true) or XOFF (false) to the other device.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
if enable:
|
||||
termios.tcflow(self.fd, termios.TCION)
|
||||
else:
|
||||
termios.tcflow(self.fd, termios.TCIOFF)
|
||||
|
||||
def set_output_flow_control(self, enable=True):
|
||||
"""\
|
||||
Manually control flow of outgoing data - when hardware or software flow
|
||||
control is enabled.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
if enable:
|
||||
termios.tcflow(self.fd, termios.TCOON)
|
||||
else:
|
||||
termios.tcflow(self.fd, termios.TCOOFF)
|
||||
|
||||
def nonblocking(self):
|
||||
"""DEPRECATED - has no use"""
|
||||
import warnings
|
||||
warnings.warn("nonblocking() has no effect, already nonblocking", DeprecationWarning)
|
||||
|
||||
|
||||
class PosixPollSerial(Serial):
|
||||
"""\
|
||||
Poll based read implementation. Not all systems support poll properly.
|
||||
However this one has better handling of errors, such as a device
|
||||
disconnecting while it's in use (e.g. USB-serial unplugged).
|
||||
"""
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
read = bytearray()
|
||||
poll = select.poll()
|
||||
poll.register(self.fd, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
|
||||
if size > 0:
|
||||
while len(read) < size:
|
||||
# print "\tread(): size",size, "have", len(read) #debug
|
||||
# wait until device becomes ready to read (or something fails)
|
||||
for fd, event in poll.poll(self._timeout * 1000):
|
||||
if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
|
||||
raise SerialException('device reports error (poll)')
|
||||
# we don't care if it is select.POLLIN or timeout, that's
|
||||
# handled below
|
||||
buf = os.read(self.fd, size - len(read))
|
||||
read.extend(buf)
|
||||
if ((self._timeout is not None and self._timeout >= 0) or
|
||||
(self._inter_byte_timeout is not None and self._inter_byte_timeout > 0)) and not buf:
|
||||
break # early abort on timeout
|
||||
return bytes(read)
|
||||
|
||||
|
||||
class VTIMESerial(Serial):
|
||||
"""\
|
||||
Implement timeout using vtime of tty device instead of using select.
|
||||
This means that no inter character timeout can be specified and that
|
||||
the error handling is degraded.
|
||||
|
||||
Overall timeout is disabled when inter-character timeout is used.
|
||||
"""
|
||||
|
||||
def _reconfigure_port(self, force_update=True):
|
||||
"""Set communication parameters on opened port."""
|
||||
super(VTIMESerial, self)._reconfigure_port()
|
||||
fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # clear O_NONBLOCK
|
||||
|
||||
if self._inter_byte_timeout is not None:
|
||||
vmin = 1
|
||||
vtime = int(self._inter_byte_timeout * 10)
|
||||
elif self._timeout is None:
|
||||
vmin = 1
|
||||
vtime = 0
|
||||
else:
|
||||
vmin = 0
|
||||
vtime = int(self._timeout * 10)
|
||||
try:
|
||||
orig_attr = termios.tcgetattr(self.fd)
|
||||
iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
|
||||
except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here
|
||||
raise serial.SerialException("Could not configure port: {}".format(msg))
|
||||
|
||||
if vtime < 0 or vtime > 255:
|
||||
raise ValueError('Invalid vtime: {!r}'.format(vtime))
|
||||
cc[termios.VTIME] = vtime
|
||||
cc[termios.VMIN] = vmin
|
||||
|
||||
termios.tcsetattr(
|
||||
self.fd,
|
||||
termios.TCSANOW,
|
||||
[iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
read = bytearray()
|
||||
while len(read) < size:
|
||||
buf = os.read(self.fd, size - len(read))
|
||||
if not buf:
|
||||
break
|
||||
read.extend(buf)
|
||||
return bytes(read)
|
||||
|
||||
# hack to make hasattr return false
|
||||
cancel_read = property()
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/serialposix.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/serialposix.pyc
Normal file
Binary file not shown.
693
backend/venv/lib/python3.6/site-packages/serial/serialutil.py
Normal file
693
backend/venv/lib/python3.6/site-packages/serial/serialutil.py
Normal file
@@ -0,0 +1,693 @@
|
||||
#! python
|
||||
#
|
||||
# Base class and support functions used by various backends.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import io
|
||||
import time
|
||||
|
||||
# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)``
|
||||
# isn't returning the contents (very unfortunate). Therefore we need special
|
||||
# cases and test for it. Ensure that there is a ``memoryview`` object for older
|
||||
# Python versions. This is easier than making every test dependent on its
|
||||
# existence.
|
||||
try:
|
||||
memoryview
|
||||
except (NameError, AttributeError):
|
||||
# implementation does not matter as we do not really use it.
|
||||
# it just must not inherit from something else we might care for.
|
||||
class memoryview(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
pass
|
||||
|
||||
try:
|
||||
unicode
|
||||
except (NameError, AttributeError):
|
||||
unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
try:
|
||||
basestring
|
||||
except (NameError, AttributeError):
|
||||
basestring = (str,) # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
|
||||
# "for byte in data" fails for python3 as it returns ints instead of bytes
|
||||
def iterbytes(b):
|
||||
"""Iterate over bytes, returning bytes instead of ints (python3)"""
|
||||
if isinstance(b, memoryview):
|
||||
b = b.tobytes()
|
||||
i = 0
|
||||
while True:
|
||||
a = b[i:i + 1]
|
||||
i += 1
|
||||
if a:
|
||||
yield a
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11'
|
||||
# so a simple ``bytes(sequence)`` doesn't work for all versions
|
||||
def to_bytes(seq):
|
||||
"""convert a sequence to a bytes type"""
|
||||
if isinstance(seq, bytes):
|
||||
return seq
|
||||
elif isinstance(seq, bytearray):
|
||||
return bytes(seq)
|
||||
elif isinstance(seq, memoryview):
|
||||
return seq.tobytes()
|
||||
elif isinstance(seq, unicode):
|
||||
raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq))
|
||||
else:
|
||||
# handle list of integers and bytes (one or more items) for Python 2 and 3
|
||||
return bytes(bytearray(seq))
|
||||
|
||||
|
||||
# create control bytes
|
||||
XON = to_bytes([17])
|
||||
XOFF = to_bytes([19])
|
||||
|
||||
CR = to_bytes([13])
|
||||
LF = to_bytes([10])
|
||||
|
||||
|
||||
PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
|
||||
STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
|
||||
FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8)
|
||||
|
||||
PARITY_NAMES = {
|
||||
PARITY_NONE: 'None',
|
||||
PARITY_EVEN: 'Even',
|
||||
PARITY_ODD: 'Odd',
|
||||
PARITY_MARK: 'Mark',
|
||||
PARITY_SPACE: 'Space',
|
||||
}
|
||||
|
||||
|
||||
class SerialException(IOError):
|
||||
"""Base class for serial port related exceptions."""
|
||||
|
||||
|
||||
class SerialTimeoutException(SerialException):
|
||||
"""Write timeouts give an exception"""
|
||||
|
||||
|
||||
writeTimeoutError = SerialTimeoutException('Write timeout')
|
||||
portNotOpenError = SerialException('Attempting to use a port that is not open')
|
||||
|
||||
|
||||
class Timeout(object):
|
||||
"""\
|
||||
Abstraction for timeout operations. Using time.monotonic() if available
|
||||
or time.time() in all other cases.
|
||||
|
||||
The class can also be initialized with 0 or None, in order to support
|
||||
non-blocking and fully blocking I/O operations. The attributes
|
||||
is_non_blocking and is_infinite are set accordingly.
|
||||
"""
|
||||
if hasattr(time, 'monotonic'):
|
||||
# Timeout implementation with time.monotonic(). This function is only
|
||||
# supported by Python 3.3 and above. It returns a time in seconds
|
||||
# (float) just as time.time(), but is not affected by system clock
|
||||
# adjustments.
|
||||
TIME = time.monotonic
|
||||
else:
|
||||
# Timeout implementation with time.time(). This is compatible with all
|
||||
# Python versions but has issues if the clock is adjusted while the
|
||||
# timeout is running.
|
||||
TIME = time.time
|
||||
|
||||
def __init__(self, duration):
|
||||
"""Initialize a timeout with given duration"""
|
||||
self.is_infinite = (duration is None)
|
||||
self.is_non_blocking = (duration == 0)
|
||||
self.duration = duration
|
||||
if duration is not None:
|
||||
self.target_time = self.TIME() + duration
|
||||
else:
|
||||
self.target_time = None
|
||||
|
||||
def expired(self):
|
||||
"""Return a boolean, telling if the timeout has expired"""
|
||||
return self.target_time is not None and self.time_left() <= 0
|
||||
|
||||
def time_left(self):
|
||||
"""Return how many seconds are left until the timeout expires"""
|
||||
if self.is_non_blocking:
|
||||
return 0
|
||||
elif self.is_infinite:
|
||||
return None
|
||||
else:
|
||||
delta = self.target_time - self.TIME()
|
||||
if delta > self.duration:
|
||||
# clock jumped, recalculate
|
||||
self.target_time = self.TIME() + self.duration
|
||||
return self.duration
|
||||
else:
|
||||
return max(0, delta)
|
||||
|
||||
def restart(self, duration):
|
||||
"""\
|
||||
Restart a timeout, only supported if a timeout was already set up
|
||||
before.
|
||||
"""
|
||||
self.duration = duration
|
||||
self.target_time = self.TIME() + duration
|
||||
|
||||
|
||||
class SerialBase(io.RawIOBase):
|
||||
"""\
|
||||
Serial port base class. Provides __init__ function and properties to
|
||||
get/set port settings.
|
||||
"""
|
||||
|
||||
# default values, may be overridden in subclasses that do not support all values
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000,
|
||||
576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000,
|
||||
3000000, 3500000, 4000000)
|
||||
BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
|
||||
PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE)
|
||||
STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
|
||||
|
||||
def __init__(self,
|
||||
port=None,
|
||||
baudrate=9600,
|
||||
bytesize=EIGHTBITS,
|
||||
parity=PARITY_NONE,
|
||||
stopbits=STOPBITS_ONE,
|
||||
timeout=None,
|
||||
xonxoff=False,
|
||||
rtscts=False,
|
||||
write_timeout=None,
|
||||
dsrdtr=False,
|
||||
inter_byte_timeout=None,
|
||||
exclusive=None,
|
||||
**kwargs):
|
||||
"""\
|
||||
Initialize comm port object. If a "port" is given, then the port will be
|
||||
opened immediately. Otherwise a Serial port object in closed state
|
||||
is returned.
|
||||
"""
|
||||
|
||||
self.is_open = False
|
||||
self.portstr = None
|
||||
self.name = None
|
||||
# correct values are assigned below through properties
|
||||
self._port = None
|
||||
self._baudrate = None
|
||||
self._bytesize = None
|
||||
self._parity = None
|
||||
self._stopbits = None
|
||||
self._timeout = None
|
||||
self._write_timeout = None
|
||||
self._xonxoff = None
|
||||
self._rtscts = None
|
||||
self._dsrdtr = None
|
||||
self._inter_byte_timeout = None
|
||||
self._rs485_mode = None # disabled by default
|
||||
self._rts_state = True
|
||||
self._dtr_state = True
|
||||
self._break_state = False
|
||||
self._exclusive = None
|
||||
|
||||
# assign values using get/set methods using the properties feature
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.bytesize = bytesize
|
||||
self.parity = parity
|
||||
self.stopbits = stopbits
|
||||
self.timeout = timeout
|
||||
self.write_timeout = write_timeout
|
||||
self.xonxoff = xonxoff
|
||||
self.rtscts = rtscts
|
||||
self.dsrdtr = dsrdtr
|
||||
self.inter_byte_timeout = inter_byte_timeout
|
||||
self.exclusive = exclusive
|
||||
|
||||
# watch for backward compatible kwargs
|
||||
if 'writeTimeout' in kwargs:
|
||||
self.write_timeout = kwargs.pop('writeTimeout')
|
||||
if 'interCharTimeout' in kwargs:
|
||||
self.inter_byte_timeout = kwargs.pop('interCharTimeout')
|
||||
if kwargs:
|
||||
raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs))
|
||||
|
||||
if port is not None:
|
||||
self.open()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# to be implemented by subclasses:
|
||||
# def open(self):
|
||||
# def close(self):
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
"""\
|
||||
Get the current port setting. The value that was passed on init or using
|
||||
setPort() is passed back.
|
||||
"""
|
||||
return self._port
|
||||
|
||||
@port.setter
|
||||
def port(self, port):
|
||||
"""\
|
||||
Change the port.
|
||||
"""
|
||||
if port is not None and not isinstance(port, basestring):
|
||||
raise ValueError('"port" must be None or a string, not {}'.format(type(port)))
|
||||
was_open = self.is_open
|
||||
if was_open:
|
||||
self.close()
|
||||
self.portstr = port
|
||||
self._port = port
|
||||
self.name = self.portstr
|
||||
if was_open:
|
||||
self.open()
|
||||
|
||||
@property
|
||||
def baudrate(self):
|
||||
"""Get the current baud rate setting."""
|
||||
return self._baudrate
|
||||
|
||||
@baudrate.setter
|
||||
def baudrate(self, baudrate):
|
||||
"""\
|
||||
Change baud rate. It raises a ValueError if the port is open and the
|
||||
baud rate is not possible. If the port is closed, then the value is
|
||||
accepted and the exception is raised when the port is opened.
|
||||
"""
|
||||
try:
|
||||
b = int(baudrate)
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
|
||||
else:
|
||||
if b < 0:
|
||||
raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
|
||||
self._baudrate = b
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def bytesize(self):
|
||||
"""Get the current byte size setting."""
|
||||
return self._bytesize
|
||||
|
||||
@bytesize.setter
|
||||
def bytesize(self, bytesize):
|
||||
"""Change byte size."""
|
||||
if bytesize not in self.BYTESIZES:
|
||||
raise ValueError("Not a valid byte size: {!r}".format(bytesize))
|
||||
self._bytesize = bytesize
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def exclusive(self):
|
||||
"""Get the current exclusive access setting."""
|
||||
return self._exclusive
|
||||
|
||||
@exclusive.setter
|
||||
def exclusive(self, exclusive):
|
||||
"""Change the exclusive access setting."""
|
||||
self._exclusive = exclusive
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def parity(self):
|
||||
"""Get the current parity setting."""
|
||||
return self._parity
|
||||
|
||||
@parity.setter
|
||||
def parity(self, parity):
|
||||
"""Change parity setting."""
|
||||
if parity not in self.PARITIES:
|
||||
raise ValueError("Not a valid parity: {!r}".format(parity))
|
||||
self._parity = parity
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def stopbits(self):
|
||||
"""Get the current stop bits setting."""
|
||||
return self._stopbits
|
||||
|
||||
@stopbits.setter
|
||||
def stopbits(self, stopbits):
|
||||
"""Change stop bits size."""
|
||||
if stopbits not in self.STOPBITS:
|
||||
raise ValueError("Not a valid stop bit size: {!r}".format(stopbits))
|
||||
self._stopbits = stopbits
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""Get the current timeout setting."""
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, timeout):
|
||||
"""Change timeout setting."""
|
||||
if timeout is not None:
|
||||
try:
|
||||
timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
if timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
self._timeout = timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def write_timeout(self):
|
||||
"""Get the current timeout setting."""
|
||||
return self._write_timeout
|
||||
|
||||
@write_timeout.setter
|
||||
def write_timeout(self, timeout):
|
||||
"""Change timeout setting."""
|
||||
if timeout is not None:
|
||||
if timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
try:
|
||||
timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(timeout))
|
||||
|
||||
self._write_timeout = timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def inter_byte_timeout(self):
|
||||
"""Get the current inter-character timeout setting."""
|
||||
return self._inter_byte_timeout
|
||||
|
||||
@inter_byte_timeout.setter
|
||||
def inter_byte_timeout(self, ic_timeout):
|
||||
"""Change inter-byte timeout setting."""
|
||||
if ic_timeout is not None:
|
||||
if ic_timeout < 0:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
|
||||
try:
|
||||
ic_timeout + 1 # test if it's a number, will throw a TypeError if not...
|
||||
except TypeError:
|
||||
raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
|
||||
|
||||
self._inter_byte_timeout = ic_timeout
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def xonxoff(self):
|
||||
"""Get the current XON/XOFF setting."""
|
||||
return self._xonxoff
|
||||
|
||||
@xonxoff.setter
|
||||
def xonxoff(self, xonxoff):
|
||||
"""Change XON/XOFF setting."""
|
||||
self._xonxoff = xonxoff
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def rtscts(self):
|
||||
"""Get the current RTS/CTS flow control setting."""
|
||||
return self._rtscts
|
||||
|
||||
@rtscts.setter
|
||||
def rtscts(self, rtscts):
|
||||
"""Change RTS/CTS flow control setting."""
|
||||
self._rtscts = rtscts
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def dsrdtr(self):
|
||||
"""Get the current DSR/DTR flow control setting."""
|
||||
return self._dsrdtr
|
||||
|
||||
@dsrdtr.setter
|
||||
def dsrdtr(self, dsrdtr=None):
|
||||
"""Change DsrDtr flow control setting."""
|
||||
if dsrdtr is None:
|
||||
# if not set, keep backwards compatibility and follow rtscts setting
|
||||
self._dsrdtr = self._rtscts
|
||||
else:
|
||||
# if defined independently, follow its value
|
||||
self._dsrdtr = dsrdtr
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
@property
|
||||
def rts(self):
|
||||
return self._rts_state
|
||||
|
||||
@rts.setter
|
||||
def rts(self, value):
|
||||
self._rts_state = value
|
||||
if self.is_open:
|
||||
self._update_rts_state()
|
||||
|
||||
@property
|
||||
def dtr(self):
|
||||
return self._dtr_state
|
||||
|
||||
@dtr.setter
|
||||
def dtr(self, value):
|
||||
self._dtr_state = value
|
||||
if self.is_open:
|
||||
self._update_dtr_state()
|
||||
|
||||
@property
|
||||
def break_condition(self):
|
||||
return self._break_state
|
||||
|
||||
@break_condition.setter
|
||||
def break_condition(self, value):
|
||||
self._break_state = value
|
||||
if self.is_open:
|
||||
self._update_break_state()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# functions useful for RS-485 adapters
|
||||
|
||||
@property
|
||||
def rs485_mode(self):
|
||||
"""\
|
||||
Enable RS485 mode and apply new settings, set to None to disable.
|
||||
See serial.rs485.RS485Settings for more info about the value.
|
||||
"""
|
||||
return self._rs485_mode
|
||||
|
||||
@rs485_mode.setter
|
||||
def rs485_mode(self, rs485_settings):
|
||||
self._rs485_mode = rs485_settings
|
||||
if self.is_open:
|
||||
self._reconfigure_port()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
_SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
|
||||
'dsrdtr', 'rtscts', 'timeout', 'write_timeout',
|
||||
'inter_byte_timeout')
|
||||
|
||||
def get_settings(self):
|
||||
"""\
|
||||
Get current port settings as a dictionary. For use with
|
||||
apply_settings().
|
||||
"""
|
||||
return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS])
|
||||
|
||||
def apply_settings(self, d):
|
||||
"""\
|
||||
Apply stored settings from a dictionary returned from
|
||||
get_settings(). It's allowed to delete keys from the dictionary. These
|
||||
values will simply left unchanged.
|
||||
"""
|
||||
for key in self._SAVED_SETTINGS:
|
||||
if key in d and d[key] != getattr(self, '_' + key): # check against internal "_" value
|
||||
setattr(self, key, d[key]) # set non "_" value to use properties write function
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of the current port settings and its state."""
|
||||
return '{name}<id=0x{id:x}, open={p.is_open}>(port={p.portstr!r}, ' \
|
||||
'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \
|
||||
'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \
|
||||
'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format(
|
||||
name=self.__class__.__name__, id=id(self), p=self)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# compatibility with io library
|
||||
# pylint: disable=invalid-name,missing-docstring
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def seekable(self):
|
||||
return False
|
||||
|
||||
def readinto(self, b):
|
||||
data = self.read(len(b))
|
||||
n = len(data)
|
||||
try:
|
||||
b[:n] = data
|
||||
except TypeError as err:
|
||||
import array
|
||||
if not isinstance(b, array.array):
|
||||
raise err
|
||||
b[:n] = array.array('b', data)
|
||||
return n
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# context manager
|
||||
|
||||
def __enter__(self):
|
||||
if not self.is_open:
|
||||
self.open()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.close()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def send_break(self, duration=0.25):
|
||||
"""\
|
||||
Send break condition. Timed, returns to idle state after given
|
||||
duration.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
self.break_condition = True
|
||||
time.sleep(duration)
|
||||
self.break_condition = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# backwards compatibility / deprecated functions
|
||||
|
||||
def flushInput(self):
|
||||
self.reset_input_buffer()
|
||||
|
||||
def flushOutput(self):
|
||||
self.reset_output_buffer()
|
||||
|
||||
def inWaiting(self):
|
||||
return self.in_waiting
|
||||
|
||||
def sendBreak(self, duration=0.25):
|
||||
self.send_break(duration)
|
||||
|
||||
def setRTS(self, value=1):
|
||||
self.rts = value
|
||||
|
||||
def setDTR(self, value=1):
|
||||
self.dtr = value
|
||||
|
||||
def getCTS(self):
|
||||
return self.cts
|
||||
|
||||
def getDSR(self):
|
||||
return self.dsr
|
||||
|
||||
def getRI(self):
|
||||
return self.ri
|
||||
|
||||
def getCD(self):
|
||||
return self.cd
|
||||
|
||||
def setPort(self, port):
|
||||
self.port = port
|
||||
|
||||
@property
|
||||
def writeTimeout(self):
|
||||
return self.write_timeout
|
||||
|
||||
@writeTimeout.setter
|
||||
def writeTimeout(self, timeout):
|
||||
self.write_timeout = timeout
|
||||
|
||||
@property
|
||||
def interCharTimeout(self):
|
||||
return self.inter_byte_timeout
|
||||
|
||||
@interCharTimeout.setter
|
||||
def interCharTimeout(self, interCharTimeout):
|
||||
self.inter_byte_timeout = interCharTimeout
|
||||
|
||||
def getSettingsDict(self):
|
||||
return self.get_settings()
|
||||
|
||||
def applySettingsDict(self, d):
|
||||
self.apply_settings(d)
|
||||
|
||||
def isOpen(self):
|
||||
return self.is_open
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# additional functionality
|
||||
|
||||
def read_all(self):
|
||||
"""\
|
||||
Read all bytes currently available in the buffer of the OS.
|
||||
"""
|
||||
return self.read(self.in_waiting)
|
||||
|
||||
def read_until(self, terminator=LF, size=None):
|
||||
"""\
|
||||
Read until a termination sequence is found ('\n' by default), the size
|
||||
is exceeded or until timeout occurs.
|
||||
"""
|
||||
lenterm = len(terminator)
|
||||
line = bytearray()
|
||||
timeout = Timeout(self._timeout)
|
||||
while True:
|
||||
c = self.read(1)
|
||||
if c:
|
||||
line += c
|
||||
if line[-lenterm:] == terminator:
|
||||
break
|
||||
if size is not None and len(line) >= size:
|
||||
break
|
||||
else:
|
||||
break
|
||||
if timeout.expired():
|
||||
break
|
||||
return bytes(line)
|
||||
|
||||
def iread_until(self, *args, **kwargs):
|
||||
"""\
|
||||
Read lines, implemented as generator. It will raise StopIteration on
|
||||
timeout (empty read).
|
||||
"""
|
||||
while True:
|
||||
line = self.read_until(*args, **kwargs)
|
||||
if not line:
|
||||
break
|
||||
yield line
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
s = SerialBase()
|
||||
sys.stdout.write('port name: {}\n'.format(s.name))
|
||||
sys.stdout.write('baud rates: {}\n'.format(s.BAUDRATES))
|
||||
sys.stdout.write('byte sizes: {}\n'.format(s.BYTESIZES))
|
||||
sys.stdout.write('parities: {}\n'.format(s.PARITIES))
|
||||
sys.stdout.write('stop bits: {}\n'.format(s.STOPBITS))
|
||||
sys.stdout.write('{}\n'.format(s))
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/serialutil.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/serialutil.pyc
Normal file
Binary file not shown.
475
backend/venv/lib/python3.6/site-packages/serial/serialwin32.py
Normal file
475
backend/venv/lib/python3.6/site-packages/serial/serialwin32.py
Normal file
@@ -0,0 +1,475 @@
|
||||
#! python
|
||||
#
|
||||
# backend for Windows ("win32" incl. 32/64 bit support)
|
||||
#
|
||||
# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# Initial patch to use ctypes by Giovanni Bajo <rasky@develer.com>
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods
|
||||
import ctypes
|
||||
import time
|
||||
from serial import win32
|
||||
|
||||
import serial
|
||||
from serial.serialutil import SerialBase, SerialException, to_bytes, portNotOpenError, writeTimeoutError
|
||||
|
||||
|
||||
class Serial(SerialBase):
|
||||
"""Serial port implementation for Win32 based on ctypes."""
|
||||
|
||||
BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
|
||||
9600, 19200, 38400, 57600, 115200)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._port_handle = None
|
||||
self._overlapped_read = None
|
||||
self._overlapped_write = None
|
||||
super(Serial, self).__init__(*args, **kwargs)
|
||||
|
||||
def open(self):
|
||||
"""\
|
||||
Open port with current settings. This may throw a SerialException
|
||||
if the port cannot be opened.
|
||||
"""
|
||||
if self._port is None:
|
||||
raise SerialException("Port must be configured before it can be used.")
|
||||
if self.is_open:
|
||||
raise SerialException("Port is already open.")
|
||||
# the "\\.\COMx" format is required for devices other than COM1-COM8
|
||||
# not all versions of windows seem to support this properly
|
||||
# so that the first few ports are used with the DOS device name
|
||||
port = self.name
|
||||
try:
|
||||
if port.upper().startswith('COM') and int(port[3:]) > 8:
|
||||
port = '\\\\.\\' + port
|
||||
except ValueError:
|
||||
# for like COMnotanumber
|
||||
pass
|
||||
self._port_handle = win32.CreateFile(
|
||||
port,
|
||||
win32.GENERIC_READ | win32.GENERIC_WRITE,
|
||||
0, # exclusive access
|
||||
None, # no security
|
||||
win32.OPEN_EXISTING,
|
||||
win32.FILE_ATTRIBUTE_NORMAL | win32.FILE_FLAG_OVERLAPPED,
|
||||
0)
|
||||
if self._port_handle == win32.INVALID_HANDLE_VALUE:
|
||||
self._port_handle = None # 'cause __del__ is called anyway
|
||||
raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
|
||||
|
||||
try:
|
||||
self._overlapped_read = win32.OVERLAPPED()
|
||||
self._overlapped_read.hEvent = win32.CreateEvent(None, 1, 0, None)
|
||||
self._overlapped_write = win32.OVERLAPPED()
|
||||
#~ self._overlapped_write.hEvent = win32.CreateEvent(None, 1, 0, None)
|
||||
self._overlapped_write.hEvent = win32.CreateEvent(None, 0, 0, None)
|
||||
|
||||
# Setup a 4k buffer
|
||||
win32.SetupComm(self._port_handle, 4096, 4096)
|
||||
|
||||
# Save original timeout values:
|
||||
self._orgTimeouts = win32.COMMTIMEOUTS()
|
||||
win32.GetCommTimeouts(self._port_handle, ctypes.byref(self._orgTimeouts))
|
||||
|
||||
self._reconfigure_port()
|
||||
|
||||
# Clear buffers:
|
||||
# Remove anything that was there
|
||||
win32.PurgeComm(
|
||||
self._port_handle,
|
||||
win32.PURGE_TXCLEAR | win32.PURGE_TXABORT |
|
||||
win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
|
||||
except:
|
||||
try:
|
||||
self._close()
|
||||
except:
|
||||
# ignore any exception when closing the port
|
||||
# also to keep original exception that happened when setting up
|
||||
pass
|
||||
self._port_handle = None
|
||||
raise
|
||||
else:
|
||||
self.is_open = True
|
||||
|
||||
def _reconfigure_port(self):
|
||||
"""Set communication parameters on opened port."""
|
||||
if not self._port_handle:
|
||||
raise SerialException("Can only operate on a valid port handle")
|
||||
|
||||
# Set Windows timeout values
|
||||
# timeouts is a tuple with the following items:
|
||||
# (ReadIntervalTimeout,ReadTotalTimeoutMultiplier,
|
||||
# ReadTotalTimeoutConstant,WriteTotalTimeoutMultiplier,
|
||||
# WriteTotalTimeoutConstant)
|
||||
timeouts = win32.COMMTIMEOUTS()
|
||||
if self._timeout is None:
|
||||
pass # default of all zeros is OK
|
||||
elif self._timeout == 0:
|
||||
timeouts.ReadIntervalTimeout = win32.MAXDWORD
|
||||
else:
|
||||
timeouts.ReadTotalTimeoutConstant = max(int(self._timeout * 1000), 1)
|
||||
if self._timeout != 0 and self._inter_byte_timeout is not None:
|
||||
timeouts.ReadIntervalTimeout = max(int(self._inter_byte_timeout * 1000), 1)
|
||||
|
||||
if self._write_timeout is None:
|
||||
pass
|
||||
elif self._write_timeout == 0:
|
||||
timeouts.WriteTotalTimeoutConstant = win32.MAXDWORD
|
||||
else:
|
||||
timeouts.WriteTotalTimeoutConstant = max(int(self._write_timeout * 1000), 1)
|
||||
win32.SetCommTimeouts(self._port_handle, ctypes.byref(timeouts))
|
||||
|
||||
win32.SetCommMask(self._port_handle, win32.EV_ERR)
|
||||
|
||||
# Setup the connection info.
|
||||
# Get state and modify it:
|
||||
comDCB = win32.DCB()
|
||||
win32.GetCommState(self._port_handle, ctypes.byref(comDCB))
|
||||
comDCB.BaudRate = self._baudrate
|
||||
|
||||
if self._bytesize == serial.FIVEBITS:
|
||||
comDCB.ByteSize = 5
|
||||
elif self._bytesize == serial.SIXBITS:
|
||||
comDCB.ByteSize = 6
|
||||
elif self._bytesize == serial.SEVENBITS:
|
||||
comDCB.ByteSize = 7
|
||||
elif self._bytesize == serial.EIGHTBITS:
|
||||
comDCB.ByteSize = 8
|
||||
else:
|
||||
raise ValueError("Unsupported number of data bits: {!r}".format(self._bytesize))
|
||||
|
||||
if self._parity == serial.PARITY_NONE:
|
||||
comDCB.Parity = win32.NOPARITY
|
||||
comDCB.fParity = 0 # Disable Parity Check
|
||||
elif self._parity == serial.PARITY_EVEN:
|
||||
comDCB.Parity = win32.EVENPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_ODD:
|
||||
comDCB.Parity = win32.ODDPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_MARK:
|
||||
comDCB.Parity = win32.MARKPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
elif self._parity == serial.PARITY_SPACE:
|
||||
comDCB.Parity = win32.SPACEPARITY
|
||||
comDCB.fParity = 1 # Enable Parity Check
|
||||
else:
|
||||
raise ValueError("Unsupported parity mode: {!r}".format(self._parity))
|
||||
|
||||
if self._stopbits == serial.STOPBITS_ONE:
|
||||
comDCB.StopBits = win32.ONESTOPBIT
|
||||
elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
|
||||
comDCB.StopBits = win32.ONE5STOPBITS
|
||||
elif self._stopbits == serial.STOPBITS_TWO:
|
||||
comDCB.StopBits = win32.TWOSTOPBITS
|
||||
else:
|
||||
raise ValueError("Unsupported number of stop bits: {!r}".format(self._stopbits))
|
||||
|
||||
comDCB.fBinary = 1 # Enable Binary Transmission
|
||||
# Char. w/ Parity-Err are replaced with 0xff (if fErrorChar is set to TRUE)
|
||||
if self._rs485_mode is None:
|
||||
if self._rtscts:
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE
|
||||
else:
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE
|
||||
comDCB.fOutxCtsFlow = self._rtscts
|
||||
else:
|
||||
# checks for unsupported settings
|
||||
# XXX verify if platform really does not have a setting for those
|
||||
if not self._rs485_mode.rts_level_for_tx:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.rts_level_for_tx: {!r}'.format(
|
||||
self._rs485_mode.rts_level_for_tx,))
|
||||
if self._rs485_mode.rts_level_for_rx:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.rts_level_for_rx: {!r}'.format(
|
||||
self._rs485_mode.rts_level_for_rx,))
|
||||
if self._rs485_mode.delay_before_tx is not None:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.delay_before_tx: {!r}'.format(
|
||||
self._rs485_mode.delay_before_tx,))
|
||||
if self._rs485_mode.delay_before_rx is not None:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.delay_before_rx: {!r}'.format(
|
||||
self._rs485_mode.delay_before_rx,))
|
||||
if self._rs485_mode.loopback:
|
||||
raise ValueError(
|
||||
'Unsupported value for RS485Settings.loopback: {!r}'.format(
|
||||
self._rs485_mode.loopback,))
|
||||
comDCB.fRtsControl = win32.RTS_CONTROL_TOGGLE
|
||||
comDCB.fOutxCtsFlow = 0
|
||||
|
||||
if self._dsrdtr:
|
||||
comDCB.fDtrControl = win32.DTR_CONTROL_HANDSHAKE
|
||||
else:
|
||||
comDCB.fDtrControl = win32.DTR_CONTROL_ENABLE if self._dtr_state else win32.DTR_CONTROL_DISABLE
|
||||
comDCB.fOutxDsrFlow = self._dsrdtr
|
||||
comDCB.fOutX = self._xonxoff
|
||||
comDCB.fInX = self._xonxoff
|
||||
comDCB.fNull = 0
|
||||
comDCB.fErrorChar = 0
|
||||
comDCB.fAbortOnError = 0
|
||||
comDCB.XonChar = serial.XON
|
||||
comDCB.XoffChar = serial.XOFF
|
||||
|
||||
if not win32.SetCommState(self._port_handle, ctypes.byref(comDCB)):
|
||||
raise SerialException(
|
||||
'Cannot configure port, something went wrong. '
|
||||
'Original message: {!r}'.format(ctypes.WinError()))
|
||||
|
||||
#~ def __del__(self):
|
||||
#~ self.close()
|
||||
|
||||
def _close(self):
|
||||
"""internal close port helper"""
|
||||
if self._port_handle is not None:
|
||||
# Restore original timeout values:
|
||||
win32.SetCommTimeouts(self._port_handle, self._orgTimeouts)
|
||||
if self._overlapped_read is not None:
|
||||
self.cancel_read()
|
||||
win32.CloseHandle(self._overlapped_read.hEvent)
|
||||
self._overlapped_read = None
|
||||
if self._overlapped_write is not None:
|
||||
self.cancel_write()
|
||||
win32.CloseHandle(self._overlapped_write.hEvent)
|
||||
self._overlapped_write = None
|
||||
win32.CloseHandle(self._port_handle)
|
||||
self._port_handle = None
|
||||
|
||||
def close(self):
|
||||
"""Close port"""
|
||||
if self.is_open:
|
||||
self._close()
|
||||
self.is_open = False
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
"""Return the number of bytes currently in the input buffer."""
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
return comstat.cbInQue
|
||||
|
||||
def read(self, size=1):
|
||||
"""\
|
||||
Read size bytes from the serial port. If a timeout is set it may
|
||||
return less characters as requested. With no timeout it will block
|
||||
until the requested number of bytes is read.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
if size > 0:
|
||||
win32.ResetEvent(self._overlapped_read.hEvent)
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
n = min(comstat.cbInQue, size) if self.timeout == 0 else size
|
||||
if n > 0:
|
||||
buf = ctypes.create_string_buffer(n)
|
||||
rc = win32.DWORD()
|
||||
read_ok = win32.ReadFile(
|
||||
self._port_handle,
|
||||
buf,
|
||||
n,
|
||||
ctypes.byref(rc),
|
||||
ctypes.byref(self._overlapped_read))
|
||||
if not read_ok and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
raise SerialException("ReadFile failed ({!r})".format(ctypes.WinError()))
|
||||
result_ok = win32.GetOverlappedResult(
|
||||
self._port_handle,
|
||||
ctypes.byref(self._overlapped_read),
|
||||
ctypes.byref(rc),
|
||||
True)
|
||||
if not result_ok:
|
||||
if win32.GetLastError() != win32.ERROR_OPERATION_ABORTED:
|
||||
raise SerialException("GetOverlappedResult failed ({!r})".format(ctypes.WinError()))
|
||||
read = buf.raw[:rc.value]
|
||||
else:
|
||||
read = bytes()
|
||||
else:
|
||||
read = bytes()
|
||||
return bytes(read)
|
||||
|
||||
def write(self, data):
|
||||
"""Output the given byte string over the serial port."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
#~ if not isinstance(data, (bytes, bytearray)):
|
||||
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
|
||||
# convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview
|
||||
data = to_bytes(data)
|
||||
if data:
|
||||
#~ win32event.ResetEvent(self._overlapped_write.hEvent)
|
||||
n = win32.DWORD()
|
||||
success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write)
|
||||
if self._write_timeout != 0: # if blocking (None) or w/ write timeout (>0)
|
||||
if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
|
||||
|
||||
# Wait for the write to complete.
|
||||
#~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE)
|
||||
win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True)
|
||||
if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED:
|
||||
return n.value # canceled IO is no error
|
||||
if n.value != len(data):
|
||||
raise writeTimeoutError
|
||||
return n.value
|
||||
else:
|
||||
errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError()
|
||||
if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY,
|
||||
win32.ERROR_OPERATION_ABORTED):
|
||||
return 0
|
||||
elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
|
||||
# no info on true length provided by OS function in async mode
|
||||
return len(data)
|
||||
else:
|
||||
raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
|
||||
else:
|
||||
return 0
|
||||
|
||||
def flush(self):
|
||||
"""\
|
||||
Flush of file like objects. In this case, wait until all data
|
||||
is written.
|
||||
"""
|
||||
while self.out_waiting:
|
||||
time.sleep(0.05)
|
||||
# XXX could also use WaitCommEvent with mask EV_TXEMPTY, but it would
|
||||
# require overlapped IO and it's also only possible to set a single mask
|
||||
# on the port---
|
||||
|
||||
def reset_input_buffer(self):
|
||||
"""Clear input buffer, discarding all that is in the buffer."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
|
||||
|
||||
def reset_output_buffer(self):
|
||||
"""\
|
||||
Clear output buffer, aborting the current output and discarding all
|
||||
that is in the buffer.
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT)
|
||||
|
||||
def _update_break_state(self):
|
||||
"""Set break: Controls TXD. When active, to transmitting is possible."""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
if self._break_state:
|
||||
win32.SetCommBreak(self._port_handle)
|
||||
else:
|
||||
win32.ClearCommBreak(self._port_handle)
|
||||
|
||||
def _update_rts_state(self):
|
||||
"""Set terminal status line: Request To Send"""
|
||||
if self._rts_state:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETRTS)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.CLRRTS)
|
||||
|
||||
def _update_dtr_state(self):
|
||||
"""Set terminal status line: Data Terminal Ready"""
|
||||
if self._dtr_state:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETDTR)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.CLRDTR)
|
||||
|
||||
def _GetCommModemStatus(self):
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
stat = win32.DWORD()
|
||||
win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat))
|
||||
return stat.value
|
||||
|
||||
@property
|
||||
def cts(self):
|
||||
"""Read terminal status line: Clear To Send"""
|
||||
return win32.MS_CTS_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def dsr(self):
|
||||
"""Read terminal status line: Data Set Ready"""
|
||||
return win32.MS_DSR_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def ri(self):
|
||||
"""Read terminal status line: Ring Indicator"""
|
||||
return win32.MS_RING_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
@property
|
||||
def cd(self):
|
||||
"""Read terminal status line: Carrier Detect"""
|
||||
return win32.MS_RLSD_ON & self._GetCommModemStatus() != 0
|
||||
|
||||
# - - platform specific - - - -
|
||||
|
||||
def set_buffer_size(self, rx_size=4096, tx_size=None):
|
||||
"""\
|
||||
Recommend a buffer size to the driver (device driver can ignore this
|
||||
value). Must be called before the port is opened.
|
||||
"""
|
||||
if tx_size is None:
|
||||
tx_size = rx_size
|
||||
win32.SetupComm(self._port_handle, rx_size, tx_size)
|
||||
|
||||
def set_output_flow_control(self, enable=True):
|
||||
"""\
|
||||
Manually control flow - when software flow control is enabled.
|
||||
This will do the same as if XON (true) or XOFF (false) are received
|
||||
from the other device and control the transmission accordingly.
|
||||
WARNING: this function is not portable to different platforms!
|
||||
"""
|
||||
if not self.is_open:
|
||||
raise portNotOpenError
|
||||
if enable:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETXON)
|
||||
else:
|
||||
win32.EscapeCommFunction(self._port_handle, win32.SETXOFF)
|
||||
|
||||
@property
|
||||
def out_waiting(self):
|
||||
"""Return how many bytes the in the outgoing buffer"""
|
||||
flags = win32.DWORD()
|
||||
comstat = win32.COMSTAT()
|
||||
if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
|
||||
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
|
||||
return comstat.cbOutQue
|
||||
|
||||
def _cancel_overlapped_io(self, overlapped):
|
||||
"""Cancel a blocking read operation, may be called from other thread"""
|
||||
# check if read operation is pending
|
||||
rc = win32.DWORD()
|
||||
err = win32.GetOverlappedResult(
|
||||
self._port_handle,
|
||||
ctypes.byref(overlapped),
|
||||
ctypes.byref(rc),
|
||||
False)
|
||||
if not err and win32.GetLastError() in (win32.ERROR_IO_PENDING, win32.ERROR_IO_INCOMPLETE):
|
||||
# cancel, ignoring any errors (e.g. it may just have finished on its own)
|
||||
win32.CancelIoEx(self._port_handle, overlapped)
|
||||
|
||||
def cancel_read(self):
|
||||
"""Cancel a blocking read operation, may be called from other thread"""
|
||||
self._cancel_overlapped_io(self._overlapped_read)
|
||||
|
||||
def cancel_write(self):
|
||||
"""Cancel a blocking write operation, may be called from other thread"""
|
||||
self._cancel_overlapped_io(self._overlapped_write)
|
||||
|
||||
@SerialBase.exclusive.setter
|
||||
def exclusive(self, exclusive):
|
||||
"""Change the exclusive access setting."""
|
||||
if exclusive is not None and not exclusive:
|
||||
raise ValueError('win32 only supports exclusive access (not: {})'.format(exclusive))
|
||||
else:
|
||||
serial.SerialBase.exclusive.__set__(self, exclusive)
|
||||
BIN
backend/venv/lib/python3.6/site-packages/serial/serialwin32.pyc
Normal file
BIN
backend/venv/lib/python3.6/site-packages/serial/serialwin32.pyc
Normal file
Binary file not shown.
@@ -1,304 +0,0 @@
|
||||
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
||||
print_function, unicode_literals
|
||||
from .utilities.compatibility import backport
|
||||
|
||||
backport() # noqa
|
||||
|
||||
from future.utils import native_str
|
||||
|
||||
from serial.utilities import qualified_name, calling_function_qualified_name
|
||||
from serial.abc.model import Model
|
||||
|
||||
from warnings import warn
|
||||
|
||||
import collections
|
||||
import json as _json
|
||||
from itertools import chain
|
||||
|
||||
import yaml as _yaml
|
||||
|
||||
import serial
|
||||
|
||||
try:
|
||||
import typing
|
||||
except ImportError as e:
|
||||
typing = None
|
||||
|
||||
|
||||
def _object_discrepancies(a, b):
|
||||
# type: (serial.model.Object, serial.model.Object) -> dict
|
||||
discrepancies = {}
|
||||
a_properties = set(serial.meta.read(a).properties.keys())
|
||||
b_properties = set(serial.meta.read(b).properties.keys())
|
||||
for property_ in a_properties | b_properties:
|
||||
try:
|
||||
a_value = getattr(a, property_)
|
||||
except AttributeError:
|
||||
a_value = None
|
||||
try:
|
||||
b_value = getattr(b, property_)
|
||||
except AttributeError:
|
||||
b_value = None
|
||||
if a_value != b_value:
|
||||
discrepancies[property_] = (a_value, b_value)
|
||||
return discrepancies
|
||||
|
||||
|
||||
def json(
|
||||
model_instance, # type: serial.model.Model
|
||||
raise_validation_errors=True, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
model(
|
||||
model_instance=model_instance,
|
||||
format_='json',
|
||||
raise_validation_errors=raise_validation_errors
|
||||
)
|
||||
|
||||
|
||||
def yaml(
|
||||
model_instance, # type: serial.model.Model
|
||||
raise_validation_errors=True, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
model(
|
||||
model_instance=model_instance,
|
||||
format_='yaml',
|
||||
raise_validation_errors=raise_validation_errors
|
||||
)
|
||||
|
||||
|
||||
def xml(
|
||||
model_instance, # type: serial.model.Model
|
||||
raise_validation_errors=True, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
raise NotImplementedError()
|
||||
# model(
|
||||
# model_instance=model_instance,
|
||||
# format_='xml',
|
||||
# raise_validation_errors=raise_validation_errors
|
||||
# )
|
||||
|
||||
|
||||
def model(
|
||||
model_instance, # type: serial.model.Model
|
||||
format_, # type: str
|
||||
raise_validation_errors=True, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Tests an instance of a `serial.model.Model` sub-class.
|
||||
|
||||
Parameters:
|
||||
|
||||
- model_instance (serial.model.Model):
|
||||
|
||||
An instance of a `serial.model.Model` sub-class.
|
||||
|
||||
- format_ (str):
|
||||
|
||||
The serialization format being tested: 'json', 'yaml' or 'xml'.
|
||||
|
||||
- raise_validation_errors (bool):
|
||||
|
||||
The function `serial.model.validate` verifies that all required attributes are present, as well as any
|
||||
additional validations implemented using the model's validation hooks `after_validate` and/or
|
||||
`before_validate`.
|
||||
|
||||
- If `True`, errors resulting from `serial.model.validate` are raised.
|
||||
|
||||
- If `False`, errors resulting from `serial.model.validate` are expressed only as warnings.
|
||||
"""
|
||||
if not isinstance(model_instance, Model):
|
||||
|
||||
value_representation = repr(model_instance)
|
||||
|
||||
raise TypeError(
|
||||
'`%s` requires an instance of `%s` for the parameter `model_instance`, not%s' % (
|
||||
calling_function_qualified_name(),
|
||||
qualified_name(Model),
|
||||
(
|
||||
(':\n%s' if '\n' in value_representation else ' `%s`') %
|
||||
value_representation
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
serial.meta.format_(model_instance, format_)
|
||||
|
||||
if isinstance(model_instance, serial.model.Object):
|
||||
|
||||
errors = serial.marshal.validate(model_instance, raise_errors=raise_validation_errors)
|
||||
|
||||
if errors:
|
||||
warn('\n' + '\n'.join(errors))
|
||||
|
||||
model_type = type(model_instance)
|
||||
|
||||
string = str(model_instance)
|
||||
|
||||
assert string != ''
|
||||
|
||||
reloaded_model_instance = model_type(string)
|
||||
qualified_model_name = qualified_name(type(model_instance))
|
||||
|
||||
try:
|
||||
|
||||
assert model_instance == reloaded_model_instance
|
||||
|
||||
except AssertionError as e:
|
||||
|
||||
sa = serial.marshal.serialize(model_instance)
|
||||
sb = serial.marshal.serialize(reloaded_model_instance)
|
||||
|
||||
ra = ''.join(l.strip() for l in repr(model_instance).split('\n'))
|
||||
rb = ''.join(l.strip() for l in repr(reloaded_model_instance).split('\n'))
|
||||
|
||||
message = [
|
||||
'Discrepancies were found between the instance of `%s` provided and ' % qualified_model_name +
|
||||
'a serialized/deserialized clone:'
|
||||
]
|
||||
|
||||
if sa != sb:
|
||||
|
||||
message.append(
|
||||
'\n :\n\n %s\n !=\n %s' % (
|
||||
sa,
|
||||
sb
|
||||
)
|
||||
)
|
||||
|
||||
if ra != rb:
|
||||
|
||||
message.append(
|
||||
'\n %s\n !=\n %s\n' % (
|
||||
ra,
|
||||
rb
|
||||
)
|
||||
)
|
||||
|
||||
for k, a_b in _object_discrepancies(model_instance, reloaded_model_instance).items():
|
||||
|
||||
a, b = a_b
|
||||
|
||||
assert a != b
|
||||
|
||||
sa = serial.marshal.serialize(a)
|
||||
sb = serial.marshal.serialize(b)
|
||||
|
||||
if sa != sb:
|
||||
|
||||
message.append(
|
||||
'\n %s().%s:\n\n %s\n %s\n %s' % (
|
||||
qualified_model_name,
|
||||
k,
|
||||
sa,
|
||||
'==' if sa == sb else '!=',
|
||||
sb
|
||||
)
|
||||
)
|
||||
|
||||
ra = ''.join(l.strip() for l in repr(a).split('\n'))
|
||||
rb = ''.join(l.strip() for l in repr(b).split('\n'))
|
||||
|
||||
if ra != rb:
|
||||
|
||||
message.append(
|
||||
'\n %s\n %s\n %s' % (
|
||||
ra,
|
||||
'==' if ra == rb else '!=',
|
||||
rb
|
||||
)
|
||||
)
|
||||
|
||||
e.args = tuple(
|
||||
chain(
|
||||
(e.args[0] + '\n' + '\n'.join(message) if e.args else '\n'.join(message),),
|
||||
e.args[1:] if e.args else tuple()
|
||||
)
|
||||
)
|
||||
|
||||
raise e
|
||||
|
||||
reloaded_string = str(reloaded_model_instance)
|
||||
|
||||
try:
|
||||
assert string == reloaded_string
|
||||
except AssertionError as e:
|
||||
m = '\n%s\n!=\n%s' % (string, reloaded_string)
|
||||
if e.args:
|
||||
e.args = tuple(chain(
|
||||
(e.args[0] + '\n' + m,),
|
||||
e.args[1:]
|
||||
))
|
||||
else:
|
||||
e.args = (m,)
|
||||
raise e
|
||||
|
||||
if format_ == 'json':
|
||||
reloaded_marshalled_data = _json.loads(
|
||||
string,
|
||||
object_hook=collections.OrderedDict,
|
||||
object_pairs_hook=collections.OrderedDict
|
||||
)
|
||||
elif format_ == 'yaml':
|
||||
reloaded_marshalled_data = _yaml.load(string)
|
||||
elif format_ == 'xml':
|
||||
raise NotImplementedError()
|
||||
else:
|
||||
format_representation = repr(format_)
|
||||
raise ValueError(
|
||||
'Valid serialization types for parameter `format_` are "json", "yaml", or "xml'", not" + (
|
||||
(
|
||||
':\n%s' if '\n' in format_representation else ' %s.'
|
||||
) % format_representation
|
||||
)
|
||||
)
|
||||
|
||||
keys = set()
|
||||
|
||||
for property_name, property in serial.meta.read(model_instance).properties.items():
|
||||
|
||||
keys.add(property.name or property_name)
|
||||
property_value = getattr(model_instance, property_name)
|
||||
|
||||
if isinstance(property_value, Model) or (
|
||||
hasattr(property_value, '__iter__') and
|
||||
(not isinstance(property_value, (str, native_str, bytes)))
|
||||
):
|
||||
model(property_value, format_=format_, raise_validation_errors=raise_validation_errors)
|
||||
|
||||
for k in reloaded_marshalled_data.keys():
|
||||
|
||||
if k not in keys:
|
||||
raise KeyError(
|
||||
'"%s" not found in serialized/re-deserialized data: %s' % (
|
||||
k,
|
||||
string
|
||||
)
|
||||
)
|
||||
|
||||
elif isinstance(model_instance, serial.model.Array):
|
||||
|
||||
serial.marshal.validate(model_instance)
|
||||
|
||||
for item in model_instance:
|
||||
|
||||
if isinstance(item, Model) or (
|
||||
hasattr(item, '__iter__') and
|
||||
(not isinstance(item, (str, native_str, bytes)))
|
||||
):
|
||||
model(item, format_=format_, raise_validation_errors=raise_validation_errors)
|
||||
|
||||
elif isinstance(model_instance, serial.model.Dictionary):
|
||||
|
||||
serial.marshal.validate(model_instance)
|
||||
|
||||
for key, value in model_instance.items():
|
||||
|
||||
if isinstance(value, Model) or (
|
||||
hasattr(value, '__iter__') and
|
||||
(not isinstance(value, (str, native_str, bytes)))
|
||||
):
|
||||
model(value, format_=format_, raise_validation_errors=raise_validation_errors)
|
||||
@@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Working with threading and pySerial
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Support threading with serial ports.
|
||||
"""
|
||||
import serial
|
||||
import threading
|
||||
|
||||
|
||||
class Protocol(object):
|
||||
"""\
|
||||
Protocol as used by the ReaderThread. 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.
|
||||
"""
|
||||
if isinstance(exc, Exception):
|
||||
raise exc
|
||||
|
||||
|
||||
class Packetizer(Protocol):
|
||||
"""
|
||||
Read binary packets from serial port. Packets are expected to be terminated
|
||||
with a TERMINATOR byte (null byte by default).
|
||||
|
||||
The class also keeps track of the transport.
|
||||
"""
|
||||
|
||||
TERMINATOR = b'\0'
|
||||
|
||||
def __init__(self):
|
||||
self.buffer = bytearray()
|
||||
self.transport = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Store transport"""
|
||||
self.transport = transport
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Forget transport"""
|
||||
self.transport = None
|
||||
super(Packetizer, self).connection_lost(exc)
|
||||
|
||||
def data_received(self, data):
|
||||
"""Buffer received data, find TERMINATOR, call handle_packet"""
|
||||
self.buffer.extend(data)
|
||||
while self.TERMINATOR in self.buffer:
|
||||
packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
|
||||
self.handle_packet(packet)
|
||||
|
||||
def handle_packet(self, packet):
|
||||
"""Process packets - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_packet')
|
||||
|
||||
|
||||
class FramedPacket(Protocol):
|
||||
"""
|
||||
Read binary packets. Packets are expected to have a start and stop marker.
|
||||
|
||||
The class also keeps track of the transport.
|
||||
"""
|
||||
|
||||
START = b'('
|
||||
STOP = b')'
|
||||
|
||||
def __init__(self):
|
||||
self.packet = bytearray()
|
||||
self.in_packet = False
|
||||
self.transport = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Store transport"""
|
||||
self.transport = transport
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""Forget transport"""
|
||||
self.transport = None
|
||||
self.in_packet = False
|
||||
del self.packet[:]
|
||||
super(FramedPacket, self).connection_lost(exc)
|
||||
|
||||
def data_received(self, data):
|
||||
"""Find data enclosed in START/STOP, call handle_packet"""
|
||||
for byte in serial.iterbytes(data):
|
||||
if byte == self.START:
|
||||
self.in_packet = True
|
||||
elif byte == self.STOP:
|
||||
self.in_packet = False
|
||||
self.handle_packet(bytes(self.packet)) # make read-only copy
|
||||
del self.packet[:]
|
||||
elif self.in_packet:
|
||||
self.packet.extend(byte)
|
||||
else:
|
||||
self.handle_out_of_packet_data(byte)
|
||||
|
||||
def handle_packet(self, packet):
|
||||
"""Process packets - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_packet')
|
||||
|
||||
def handle_out_of_packet_data(self, data):
|
||||
"""Process data that is received outside of packets"""
|
||||
pass
|
||||
|
||||
|
||||
class LineReader(Packetizer):
|
||||
"""
|
||||
Read and write (Unicode) lines from/to serial port.
|
||||
The encoding is applied.
|
||||
"""
|
||||
|
||||
TERMINATOR = b'\r\n'
|
||||
ENCODING = 'utf-8'
|
||||
UNICODE_HANDLING = 'replace'
|
||||
|
||||
def handle_packet(self, packet):
|
||||
self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))
|
||||
|
||||
def handle_line(self, line):
|
||||
"""Process one line - to be overridden by subclassing"""
|
||||
raise NotImplementedError('please implement functionality in handle_line')
|
||||
|
||||
def write_line(self, text):
|
||||
"""
|
||||
Write text to the transport. ``text`` is a Unicode string and the encoding
|
||||
is applied before sending ans also the newline is append.
|
||||
"""
|
||||
# + is not the best choice but bytes does not support % or .format in py3 and we want a single write call
|
||||
self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR)
|
||||
|
||||
|
||||
class ReaderThread(threading.Thread):
|
||||
"""\
|
||||
Implement a serial port read loop and dispatch to a Protocol instance (like
|
||||
the asyncio.Protocol) but do it with threads.
|
||||
|
||||
Calls to close() will close the serial port but it is also possible to just
|
||||
stop() this thread and continue the serial port instance otherwise.
|
||||
"""
|
||||
|
||||
def __init__(self, serial_instance, protocol_factory):
|
||||
"""\
|
||||
Initialize thread.
|
||||
|
||||
Note that the serial_instance' timeout is set to one second!
|
||||
Other settings are not changed.
|
||||
"""
|
||||
super(ReaderThread, self).__init__()
|
||||
self.daemon = True
|
||||
self.serial = serial_instance
|
||||
self.protocol_factory = protocol_factory
|
||||
self.alive = True
|
||||
self._lock = threading.Lock()
|
||||
self._connection_made = threading.Event()
|
||||
self.protocol = None
|
||||
|
||||
def stop(self):
|
||||
"""Stop the reader thread"""
|
||||
self.alive = False
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.join(2)
|
||||
|
||||
def run(self):
|
||||
"""Reader loop"""
|
||||
if not hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.timeout = 1
|
||||
self.protocol = self.protocol_factory()
|
||||
try:
|
||||
self.protocol.connection_made(self)
|
||||
except Exception as e:
|
||||
self.alive = False
|
||||
self.protocol.connection_lost(e)
|
||||
self._connection_made.set()
|
||||
return
|
||||
error = None
|
||||
self._connection_made.set()
|
||||
while self.alive and self.serial.is_open:
|
||||
try:
|
||||
# read all that is there or wait for one byte (blocking)
|
||||
data = self.serial.read(self.serial.in_waiting or 1)
|
||||
except serial.SerialException as e:
|
||||
# probably some I/O problem such as disconnected USB serial
|
||||
# adapters -> exit
|
||||
error = e
|
||||
break
|
||||
else:
|
||||
if data:
|
||||
# make a separated try-except for called used code
|
||||
try:
|
||||
self.protocol.data_received(data)
|
||||
except Exception as e:
|
||||
error = e
|
||||
break
|
||||
self.alive = False
|
||||
self.protocol.connection_lost(error)
|
||||
self.protocol = 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()
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Wait until connection is set up and return the transport and protocol
|
||||
instances.
|
||||
"""
|
||||
if self.alive:
|
||||
self._connection_made.wait()
|
||||
if not self.alive:
|
||||
raise RuntimeError('connection_lost already called')
|
||||
return (self, self.protocol)
|
||||
else:
|
||||
raise RuntimeError('already stopped')
|
||||
|
||||
# - - context manager, returns protocol
|
||||
|
||||
def __enter__(self):
|
||||
"""\
|
||||
Enter context handler. May raise RuntimeError in case the connection
|
||||
could not be created.
|
||||
"""
|
||||
self.start()
|
||||
self._connection_made.wait()
|
||||
if not self.alive:
|
||||
raise RuntimeError('connection_lost already called')
|
||||
return self.protocol
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Leave context: close port"""
|
||||
self.close()
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
# pylint: disable=wrong-import-position
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
#~ PORT = 'spy:///dev/ttyUSB0'
|
||||
PORT = 'loop://'
|
||||
|
||||
class PrintLines(LineReader):
|
||||
def connection_made(self, transport):
|
||||
super(PrintLines, self).connection_made(transport)
|
||||
sys.stdout.write('port opened\n')
|
||||
self.write_line('hello world')
|
||||
|
||||
def handle_line(self, data):
|
||||
sys.stdout.write('line received: {!r}\n'.format(data))
|
||||
|
||||
def connection_lost(self, exc):
|
||||
if exc:
|
||||
traceback.print_exc(exc)
|
||||
sys.stdout.write('port closed\n')
|
||||
|
||||
ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
|
||||
with ReaderThread(ser, PrintLines) as protocol:
|
||||
protocol.write_line('hello')
|
||||
time.sleep(2)
|
||||
|
||||
# alternative usage
|
||||
ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
|
||||
t = ReaderThread(ser, PrintLines)
|
||||
t.start()
|
||||
transport, protocol = t.connect()
|
||||
protocol.write_line('hello')
|
||||
time.sleep(2)
|
||||
t.close()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,124 @@
|
||||
#! python
|
||||
#
|
||||
# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
"""\
|
||||
Python 'hex' Codec - 2-digit hex with spaces content transfer encoding.
|
||||
|
||||
Encode and decode may be a bit missleading at first sight...
|
||||
|
||||
The textual representation is a hex dump: e.g. "40 41"
|
||||
The "encoded" data of this is the binary form, e.g. b"@A"
|
||||
|
||||
Therefore decoding is binary to text and thus converting binary data to hex dump.
|
||||
|
||||
"""
|
||||
|
||||
import codecs
|
||||
import serial
|
||||
|
||||
|
||||
try:
|
||||
unicode
|
||||
except (NameError, AttributeError):
|
||||
unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name
|
||||
|
||||
|
||||
HEXDIGITS = '0123456789ABCDEF'
|
||||
|
||||
|
||||
# Codec APIs
|
||||
|
||||
def hex_encode(data, errors='strict'):
|
||||
"""'40 41 42' -> b'@ab'"""
|
||||
return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data))
|
||||
|
||||
|
||||
def hex_decode(data, errors='strict'):
|
||||
"""b'@ab' -> '40 41 42'"""
|
||||
return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data))
|
||||
|
||||
|
||||
class Codec(codecs.Codec):
|
||||
def encode(self, data, errors='strict'):
|
||||
"""'40 41 42' -> b'@ab'"""
|
||||
return serial.to_bytes([int(h, 16) for h in data.split()])
|
||||
|
||||
def decode(self, data, errors='strict'):
|
||||
"""b'@ab' -> '40 41 42'"""
|
||||
return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data)))
|
||||
|
||||
|
||||
class IncrementalEncoder(codecs.IncrementalEncoder):
|
||||
"""Incremental hex encoder"""
|
||||
|
||||
def __init__(self, errors='strict'):
|
||||
self.errors = errors
|
||||
self.state = 0
|
||||
|
||||
def reset(self):
|
||||
self.state = 0
|
||||
|
||||
def getstate(self):
|
||||
return self.state
|
||||
|
||||
def setstate(self, state):
|
||||
self.state = state
|
||||
|
||||
def encode(self, data, final=False):
|
||||
"""\
|
||||
Incremental encode, keep track of digits and emit a byte when a pair
|
||||
of hex digits is found. The space is optional unless the error
|
||||
handling is defined to be 'strict'.
|
||||
"""
|
||||
state = self.state
|
||||
encoded = []
|
||||
for c in data.upper():
|
||||
if c in HEXDIGITS:
|
||||
z = HEXDIGITS.index(c)
|
||||
if state:
|
||||
encoded.append(z + (state & 0xf0))
|
||||
state = 0
|
||||
else:
|
||||
state = 0x100 + (z << 4)
|
||||
elif c == ' ': # allow spaces to separate values
|
||||
if state and self.errors == 'strict':
|
||||
raise UnicodeError('odd number of hex digits')
|
||||
state = 0
|
||||
else:
|
||||
if self.errors == 'strict':
|
||||
raise UnicodeError('non-hex digit found: {!r}'.format(c))
|
||||
self.state = state
|
||||
return serial.to_bytes(encoded)
|
||||
|
||||
|
||||
class IncrementalDecoder(codecs.IncrementalDecoder):
|
||||
"""Incremental decoder"""
|
||||
def decode(self, data, final=False):
|
||||
return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data)))
|
||||
|
||||
|
||||
class StreamWriter(Codec, codecs.StreamWriter):
|
||||
"""Combination of hexlify codec and StreamWriter"""
|
||||
|
||||
|
||||
class StreamReader(Codec, codecs.StreamReader):
|
||||
"""Combination of hexlify codec and StreamReader"""
|
||||
|
||||
|
||||
def getregentry():
|
||||
"""encodings module API"""
|
||||
return codecs.CodecInfo(
|
||||
name='hexlify',
|
||||
encode=hex_encode,
|
||||
decode=hex_decode,
|
||||
incrementalencoder=IncrementalEncoder,
|
||||
incrementaldecoder=IncrementalDecoder,
|
||||
streamwriter=StreamWriter,
|
||||
streamreader=StreamReader,
|
||||
#~ _is_text_encoding=True,
|
||||
)
|
||||
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Serial port enumeration. Console tool and backend selection.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
This module will provide a function called comports that returns an
|
||||
iterable (generator or list) that will enumerate available com ports. Note that
|
||||
on some systems non-existent ports may be listed.
|
||||
|
||||
Additionally a grep function is supplied that can be used to search for ports
|
||||
based on their descriptions or hardware ID.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
# chose an implementation, depending on os
|
||||
#~ if sys.platform == 'cli':
|
||||
#~ else:
|
||||
if os.name == 'nt': # sys.platform == 'win32':
|
||||
from serial.tools.list_ports_windows import comports
|
||||
elif os.name == 'posix':
|
||||
from serial.tools.list_ports_posix import comports
|
||||
#~ elif os.name == 'java':
|
||||
else:
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
def grep(regexp, include_links=False):
|
||||
"""\
|
||||
Search for ports using a regular expression. Port name, description and
|
||||
hardware ID are searched. The function returns an iterable that returns the
|
||||
same tuples as comport() would do.
|
||||
"""
|
||||
r = re.compile(regexp, re.I)
|
||||
for info in comports(include_links):
|
||||
port, desc, hwid = info
|
||||
if r.search(port) or r.search(desc) or r.search(hwid):
|
||||
yield info
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Serial port enumeration')
|
||||
|
||||
parser.add_argument(
|
||||
'regexp',
|
||||
nargs='?',
|
||||
help='only show ports that match this regex')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='show more messages')
|
||||
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help='suppress all messages')
|
||||
|
||||
parser.add_argument(
|
||||
'-n',
|
||||
type=int,
|
||||
help='only output the N-th entry')
|
||||
|
||||
parser.add_argument(
|
||||
'-s', '--include-links',
|
||||
action='store_true',
|
||||
help='include entries that are symlinks to real devices')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
hits = 0
|
||||
# get iteraror w/ or w/o filter
|
||||
if args.regexp:
|
||||
if not args.quiet:
|
||||
sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp))
|
||||
iterator = sorted(grep(args.regexp, include_links=args.include_links))
|
||||
else:
|
||||
iterator = sorted(comports(include_links=args.include_links))
|
||||
# list them
|
||||
for n, (port, desc, hwid) in enumerate(iterator, 1):
|
||||
if args.n is None or args.n == n:
|
||||
sys.stdout.write("{:20}\n".format(port))
|
||||
if args.verbose:
|
||||
sys.stdout.write(" desc: {}\n".format(desc))
|
||||
sys.stdout.write(" hwid: {}\n".format(hwid))
|
||||
hits += 1
|
||||
if not args.quiet:
|
||||
if hits:
|
||||
sys.stderr.write("{} ports found\n".format(hits))
|
||||
else:
|
||||
sys.stderr.write("no ports found\n")
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a helper module for the various platform dependent list_port
|
||||
# implementations.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
import re
|
||||
import glob
|
||||
import os
|
||||
|
||||
|
||||
def numsplit(text):
|
||||
"""\
|
||||
Convert string into a list of texts and numbers in order to support a
|
||||
natural sorting.
|
||||
"""
|
||||
result = []
|
||||
for group in re.split(r'(\d+)', text):
|
||||
if group:
|
||||
try:
|
||||
group = int(group)
|
||||
except ValueError:
|
||||
pass
|
||||
result.append(group)
|
||||
return result
|
||||
|
||||
|
||||
class ListPortInfo(object):
|
||||
"""Info collection base class for serial ports"""
|
||||
|
||||
def __init__(self, device=None):
|
||||
self.device = device
|
||||
self.name = None
|
||||
self.description = 'n/a'
|
||||
self.hwid = 'n/a'
|
||||
# USB specific data
|
||||
self.vid = None
|
||||
self.pid = None
|
||||
self.serial_number = None
|
||||
self.location = None
|
||||
self.manufacturer = None
|
||||
self.product = None
|
||||
self.interface = None
|
||||
# special handling for links
|
||||
if device is not None and os.path.islink(device):
|
||||
self.hwid = 'LINK={}'.format(os.path.realpath(device))
|
||||
|
||||
def usb_description(self):
|
||||
"""return a short string to name the port based on USB info"""
|
||||
if self.interface is not None:
|
||||
return '{} - {}'.format(self.product, self.interface)
|
||||
elif self.product is not None:
|
||||
return self.product
|
||||
else:
|
||||
return self.name
|
||||
|
||||
def usb_info(self):
|
||||
"""return a string with USB related information about device"""
|
||||
return 'USB VID:PID={:04X}:{:04X}{}{}'.format(
|
||||
self.vid or 0,
|
||||
self.pid or 0,
|
||||
' SER={}'.format(self.serial_number) if self.serial_number is not None else '',
|
||||
' LOCATION={}'.format(self.location) if self.location is not None else '')
|
||||
|
||||
def apply_usb_info(self):
|
||||
"""update description and hwid from USB data"""
|
||||
self.description = self.usb_description()
|
||||
self.hwid = self.usb_info()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.device == other.device
|
||||
|
||||
def __lt__(self, other):
|
||||
return numsplit(self.device) < numsplit(other.device)
|
||||
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.device, self.description)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Item access: backwards compatible -> (port, desc, hwid)"""
|
||||
if index == 0:
|
||||
return self.device
|
||||
elif index == 1:
|
||||
return self.description
|
||||
elif index == 2:
|
||||
return self.hwid
|
||||
else:
|
||||
raise IndexError('{} > 2'.format(index))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def list_links(devices):
|
||||
"""\
|
||||
search all /dev devices and look for symlinks to known ports already
|
||||
listed in devices.
|
||||
"""
|
||||
links = []
|
||||
for device in glob.glob('/dev/*'):
|
||||
if os.path.islink(device) and os.path.realpath(device) in devices:
|
||||
links.append(device)
|
||||
return links
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
print(ListPortInfo('dummy'))
|
||||
Binary file not shown.
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports including details on
|
||||
# GNU/Linux systems.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import glob
|
||||
import os
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
|
||||
class SysFS(list_ports_common.ListPortInfo):
|
||||
"""Wrapper for easy sysfs access and device info"""
|
||||
|
||||
def __init__(self, device):
|
||||
super(SysFS, self).__init__(device)
|
||||
# special handling for links
|
||||
if device is not None and os.path.islink(device):
|
||||
device = os.path.realpath(device)
|
||||
is_link = True
|
||||
else:
|
||||
is_link = False
|
||||
self.name = os.path.basename(device)
|
||||
self.usb_device_path = None
|
||||
if os.path.exists('/sys/class/tty/{}/device'.format(self.name)):
|
||||
self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name))
|
||||
self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem')))
|
||||
else:
|
||||
self.device_path = None
|
||||
self.subsystem = None
|
||||
# check device type
|
||||
if self.subsystem == 'usb-serial':
|
||||
self.usb_interface_path = os.path.dirname(self.device_path)
|
||||
elif self.subsystem == 'usb':
|
||||
self.usb_interface_path = self.device_path
|
||||
else:
|
||||
self.usb_interface_path = None
|
||||
# fill-in info for USB devices
|
||||
if self.usb_interface_path is not None:
|
||||
self.usb_device_path = os.path.dirname(self.usb_interface_path)
|
||||
|
||||
try:
|
||||
num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces'))
|
||||
except ValueError:
|
||||
num_if = 1
|
||||
|
||||
self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16)
|
||||
self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16)
|
||||
self.serial_number = self.read_line(self.usb_device_path, 'serial')
|
||||
if num_if > 1: # multi interface devices like FT4232
|
||||
self.location = os.path.basename(self.usb_interface_path)
|
||||
else:
|
||||
self.location = os.path.basename(self.usb_device_path)
|
||||
|
||||
self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer')
|
||||
self.product = self.read_line(self.usb_device_path, 'product')
|
||||
self.interface = self.read_line(self.device_path, 'interface')
|
||||
|
||||
if self.subsystem in ('usb', 'usb-serial'):
|
||||
self.apply_usb_info()
|
||||
#~ elif self.subsystem in ('pnp', 'amba'): # PCI based devices, raspi
|
||||
elif self.subsystem == 'pnp': # PCI based devices
|
||||
self.description = self.name
|
||||
self.hwid = self.read_line(self.device_path, 'id')
|
||||
elif self.subsystem == 'amba': # raspi
|
||||
self.description = self.name
|
||||
self.hwid = os.path.basename(self.device_path)
|
||||
|
||||
if is_link:
|
||||
self.hwid += ' LINK={}'.format(device)
|
||||
|
||||
def read_line(self, *args):
|
||||
"""\
|
||||
Helper function to read a single line from a file.
|
||||
One or more parameters are allowed, they are joined with os.path.join.
|
||||
Returns None on errors..
|
||||
"""
|
||||
try:
|
||||
with open(os.path.join(*args)) as f:
|
||||
line = f.readline().strip()
|
||||
return line
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
devices = glob.glob('/dev/ttyS*') # built-in serial ports
|
||||
devices.extend(glob.glob('/dev/ttyUSB*')) # usb-serial with own driver
|
||||
devices.extend(glob.glob('/dev/ttyACM*')) # usb-serial with CDC-ACM profile
|
||||
devices.extend(glob.glob('/dev/ttyAMA*')) # ARM internal port (raspi)
|
||||
devices.extend(glob.glob('/dev/rfcomm*')) # BT serial devices
|
||||
devices.extend(glob.glob('/dev/ttyAP*')) # Advantech multi-port serial controllers
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [info
|
||||
for info in [SysFS(d) for d in devices]
|
||||
if info.subsystem != "platform"] # hide non-present internal serial ports
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for port, desc, hwid in sorted(comports()):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
Binary file not shown.
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports including details on OSX
|
||||
#
|
||||
# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools
|
||||
# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston
|
||||
# and modifications by cliechti, hoihu, hardkrash
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2013-2015
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
# List all of the callout devices in OS/X by querying IOKit.
|
||||
|
||||
# See the following for a reference of how to do this:
|
||||
# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD
|
||||
|
||||
# More help from darwin_hid.py
|
||||
|
||||
# Also see the 'IORegistryExplorer' for an idea of what we are actually searching
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit'))
|
||||
cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
|
||||
|
||||
kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
|
||||
kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
|
||||
|
||||
kCFStringEncodingMacRoman = 0
|
||||
|
||||
iokit.IOServiceMatching.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
|
||||
iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IORegistryEntryGetName.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
iokit.IOObjectGetClass.restype = ctypes.c_void_p
|
||||
|
||||
iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32]
|
||||
cf.CFStringCreateWithCString.restype = ctypes.c_void_p
|
||||
|
||||
cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
|
||||
cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
|
||||
|
||||
cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
|
||||
cf.CFNumberGetValue.restype = ctypes.c_void_p
|
||||
|
||||
# void CFRelease ( CFTypeRef cf );
|
||||
cf.CFRelease.argtypes = [ctypes.c_void_p]
|
||||
cf.CFRelease.restype = None
|
||||
|
||||
# CFNumber type defines
|
||||
kCFNumberSInt8Type = 1
|
||||
kCFNumberSInt16Type = 2
|
||||
kCFNumberSInt32Type = 3
|
||||
kCFNumberSInt64Type = 4
|
||||
|
||||
|
||||
def get_string_property(device_type, property):
|
||||
"""
|
||||
Search the given device for the specified string property
|
||||
|
||||
@param device_type Type of Device
|
||||
@param property String to search for
|
||||
@return Python string containing the value, or None if not found.
|
||||
"""
|
||||
key = cf.CFStringCreateWithCString(
|
||||
kCFAllocatorDefault,
|
||||
property.encode("mac_roman"),
|
||||
kCFStringEncodingMacRoman)
|
||||
|
||||
CFContainer = iokit.IORegistryEntryCreateCFProperty(
|
||||
device_type,
|
||||
key,
|
||||
kCFAllocatorDefault,
|
||||
0)
|
||||
output = None
|
||||
|
||||
if CFContainer:
|
||||
output = cf.CFStringGetCStringPtr(CFContainer, 0)
|
||||
if output is not None:
|
||||
output = output.decode('mac_roman')
|
||||
cf.CFRelease(CFContainer)
|
||||
return output
|
||||
|
||||
|
||||
def get_int_property(device_type, property, cf_number_type):
|
||||
"""
|
||||
Search the given device for the specified string property
|
||||
|
||||
@param device_type Device to search
|
||||
@param property String to search for
|
||||
@param cf_number_type CFType number
|
||||
|
||||
@return Python string containing the value, or None if not found.
|
||||
"""
|
||||
key = cf.CFStringCreateWithCString(
|
||||
kCFAllocatorDefault,
|
||||
property.encode("mac_roman"),
|
||||
kCFStringEncodingMacRoman)
|
||||
|
||||
CFContainer = iokit.IORegistryEntryCreateCFProperty(
|
||||
device_type,
|
||||
key,
|
||||
kCFAllocatorDefault,
|
||||
0)
|
||||
|
||||
if CFContainer:
|
||||
if (cf_number_type == kCFNumberSInt32Type):
|
||||
number = ctypes.c_uint32()
|
||||
elif (cf_number_type == kCFNumberSInt16Type):
|
||||
number = ctypes.c_uint16()
|
||||
cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number))
|
||||
cf.CFRelease(CFContainer)
|
||||
return number.value
|
||||
return None
|
||||
|
||||
|
||||
def IORegistryEntryGetName(device):
|
||||
pathname = ctypes.create_string_buffer(100) # TODO: Is this ok?
|
||||
iokit.IOObjectGetClass(device, ctypes.byref(pathname))
|
||||
return pathname.value
|
||||
|
||||
|
||||
def GetParentDeviceByType(device, parent_type):
|
||||
""" Find the first parent of a device that implements the parent_type
|
||||
@param IOService Service to inspect
|
||||
@return Pointer to the parent type, or None if it was not found.
|
||||
"""
|
||||
# First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
|
||||
parent_type = parent_type.encode('mac_roman')
|
||||
while IORegistryEntryGetName(device) != parent_type:
|
||||
parent = ctypes.c_void_p()
|
||||
response = iokit.IORegistryEntryGetParentEntry(
|
||||
device,
|
||||
"IOService".encode("mac_roman"),
|
||||
ctypes.byref(parent))
|
||||
# If we weren't able to find a parent for the device, we're done.
|
||||
if response != 0:
|
||||
return None
|
||||
device = parent
|
||||
return device
|
||||
|
||||
|
||||
def GetIOServicesByType(service_type):
|
||||
"""
|
||||
returns iterator over specified service_type
|
||||
"""
|
||||
serial_port_iterator = ctypes.c_void_p()
|
||||
|
||||
iokit.IOServiceGetMatchingServices(
|
||||
kIOMasterPortDefault,
|
||||
iokit.IOServiceMatching(service_type.encode('mac_roman')),
|
||||
ctypes.byref(serial_port_iterator))
|
||||
|
||||
services = []
|
||||
while iokit.IOIteratorIsValid(serial_port_iterator):
|
||||
service = iokit.IOIteratorNext(serial_port_iterator)
|
||||
if not service:
|
||||
break
|
||||
services.append(service)
|
||||
iokit.IOObjectRelease(serial_port_iterator)
|
||||
return services
|
||||
|
||||
|
||||
def location_to_string(locationID):
|
||||
"""
|
||||
helper to calculate port and bus number from locationID
|
||||
"""
|
||||
loc = ['{}-'.format(locationID >> 24)]
|
||||
while locationID & 0xf00000:
|
||||
if len(loc) > 1:
|
||||
loc.append('.')
|
||||
loc.append('{}'.format((locationID >> 20) & 0xf))
|
||||
locationID <<= 4
|
||||
return ''.join(loc)
|
||||
|
||||
|
||||
class SuitableSerialInterface(object):
|
||||
pass
|
||||
|
||||
|
||||
def scan_interfaces():
|
||||
"""
|
||||
helper function to scan USB interfaces
|
||||
returns a list of SuitableSerialInterface objects with name and id attributes
|
||||
"""
|
||||
interfaces = []
|
||||
for service in GetIOServicesByType('IOSerialBSDClient'):
|
||||
device = get_string_property(service, "IOCalloutDevice")
|
||||
if device:
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBInterface")
|
||||
if usb_device:
|
||||
name = get_string_property(usb_device, "USB Interface Name") or None
|
||||
locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or ''
|
||||
i = SuitableSerialInterface()
|
||||
i.id = locationID
|
||||
i.name = name
|
||||
interfaces.append(i)
|
||||
return interfaces
|
||||
|
||||
|
||||
def search_for_locationID_in_interfaces(serial_interfaces, locationID):
|
||||
for interface in serial_interfaces:
|
||||
if (interface.id == locationID):
|
||||
return interface.name
|
||||
return None
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
# XXX include_links is currently ignored. are links in /dev even supported here?
|
||||
# Scan for all iokit serial ports
|
||||
services = GetIOServicesByType('IOSerialBSDClient')
|
||||
ports = []
|
||||
serial_interfaces = scan_interfaces()
|
||||
for service in services:
|
||||
# First, add the callout device file.
|
||||
device = get_string_property(service, "IOCalloutDevice")
|
||||
if device:
|
||||
info = list_ports_common.ListPortInfo(device)
|
||||
# If the serial port is implemented by IOUSBDevice
|
||||
usb_device = GetParentDeviceByType(service, "IOUSBDevice")
|
||||
if usb_device:
|
||||
# fetch some useful informations from properties
|
||||
info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type)
|
||||
info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type)
|
||||
info.serial_number = get_string_property(usb_device, "USB Serial Number")
|
||||
info.product = get_string_property(usb_device, "USB Product Name") or 'n/a'
|
||||
info.manufacturer = get_string_property(usb_device, "USB Vendor Name")
|
||||
locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type)
|
||||
info.location = location_to_string(locationID)
|
||||
info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID)
|
||||
info.apply_usb_info()
|
||||
ports.append(info)
|
||||
return ports
|
||||
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for port, desc, hwid in sorted(comports()):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
Binary file not shown.
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This is a module that gathers a list of serial ports on POSIXy systems.
|
||||
# For some specific implementations, see also list_ports_linux, list_ports_osx
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
"""\
|
||||
The ``comports`` function is expected to return an iterable that yields tuples
|
||||
of 3 strings: port name, human readable description and a hardware ID.
|
||||
|
||||
As currently no method is known to get the second two strings easily, they are
|
||||
currently just identical to the port name.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
# try to detect the OS so that a device can be selected...
|
||||
plat = sys.platform.lower()
|
||||
|
||||
if plat[:5] == 'linux': # Linux (confirmed) # noqa
|
||||
from serial.tools.list_ports_linux import comports
|
||||
|
||||
elif plat[:6] == 'darwin': # OS X (confirmed)
|
||||
from serial.tools.list_ports_osx import comports
|
||||
|
||||
elif plat == 'cygwin': # cygwin/win32
|
||||
# cygwin accepts /dev/com* in many contexts
|
||||
# (such as 'open' call, explicit 'ls'), but 'glob.glob'
|
||||
# and bare 'ls' do not; so use /dev/ttyS* instead
|
||||
def comports(include_links=False):
|
||||
devices = glob.glob('/dev/ttyS*')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:7] == 'openbsd': # OpenBSD
|
||||
def comports(include_links=False):
|
||||
devices = glob.glob('/dev/cua*')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:3] == 'bsd' or plat[:7] == 'freebsd':
|
||||
def comports(include_links=False):
|
||||
devices = glob.glob('/dev/cua*[!.init][!.lock]')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:6] == 'netbsd': # NetBSD
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = glob.glob('/dev/dty*')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:4] == 'irix': # IRIX
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = glob.glob('/dev/ttyf*')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:2] == 'hp': # HP-UX (not tested)
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = glob.glob('/dev/tty*p0')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:5] == 'sunos': # Solaris/SunOS
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = glob.glob('/dev/tty*c')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
elif plat[:3] == 'aix': # AIX
|
||||
def comports(include_links=False):
|
||||
"""scan for available ports. return a list of device names."""
|
||||
devices = glob.glob('/dev/tty*')
|
||||
if include_links:
|
||||
devices.extend(list_ports_common.list_links(devices))
|
||||
return [list_ports_common.ListPortInfo(d) for d in devices]
|
||||
|
||||
else:
|
||||
# platform detection has failed...
|
||||
import serial
|
||||
sys.stderr.write("""\
|
||||
don't know how to enumerate ttys on this system.
|
||||
! I you know how the serial ports are named send this information to
|
||||
! the author of this module:
|
||||
|
||||
sys.platform = {!r}
|
||||
os.name = {!r}
|
||||
pySerial version = {}
|
||||
|
||||
also add the naming scheme of the serial ports and with a bit luck you can get
|
||||
this module running...
|
||||
""".format(sys.platform, os.name, serial.VERSION))
|
||||
raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
|
||||
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for port, desc, hwid in sorted(comports()):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
Binary file not shown.
@@ -0,0 +1,305 @@
|
||||
#! python
|
||||
#
|
||||
# Enumerate serial ports on Windows including a human readable description
|
||||
# and hardware information.
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# pylint: disable=invalid-name,too-few-public-methods
|
||||
import re
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL
|
||||
from ctypes.wintypes import HWND
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import WORD
|
||||
from ctypes.wintypes import LONG
|
||||
from ctypes.wintypes import ULONG
|
||||
from ctypes.wintypes import HKEY
|
||||
from ctypes.wintypes import BYTE
|
||||
import serial
|
||||
from serial.win32 import ULONG_PTR
|
||||
from serial.tools import list_ports_common
|
||||
|
||||
|
||||
def ValidHandle(value, func, arguments):
|
||||
if value == 0:
|
||||
raise ctypes.WinError()
|
||||
return value
|
||||
|
||||
|
||||
NULL = 0
|
||||
HDEVINFO = ctypes.c_void_p
|
||||
LPCTSTR = ctypes.c_wchar_p
|
||||
PCTSTR = ctypes.c_wchar_p
|
||||
PTSTR = ctypes.c_wchar_p
|
||||
LPDWORD = PDWORD = ctypes.POINTER(DWORD)
|
||||
#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE)
|
||||
LPBYTE = PBYTE = ctypes.c_void_p # XXX avoids error about types
|
||||
|
||||
ACCESS_MASK = DWORD
|
||||
REGSAM = ACCESS_MASK
|
||||
|
||||
|
||||
class GUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Data1', DWORD),
|
||||
('Data2', WORD),
|
||||
('Data3', WORD),
|
||||
('Data4', BYTE * 8),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format(
|
||||
self.Data1,
|
||||
self.Data2,
|
||||
self.Data3,
|
||||
''.join(["{:02x}".format(d) for d in self.Data4[:2]]),
|
||||
''.join(["{:02x}".format(d) for d in self.Data4[2:]]),
|
||||
)
|
||||
|
||||
|
||||
class SP_DEVINFO_DATA(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('cbSize', DWORD),
|
||||
('ClassGuid', GUID),
|
||||
('DevInst', DWORD),
|
||||
('Reserved', ULONG_PTR),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "ClassGuid:{} DevInst:{}".format(self.ClassGuid, self.DevInst)
|
||||
|
||||
|
||||
PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA)
|
||||
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p
|
||||
|
||||
setupapi = ctypes.windll.LoadLibrary("setupapi")
|
||||
SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
|
||||
SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO]
|
||||
SetupDiDestroyDeviceInfoList.restype = BOOL
|
||||
|
||||
SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW
|
||||
SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD]
|
||||
SetupDiClassGuidsFromName.restype = BOOL
|
||||
|
||||
SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
|
||||
SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA]
|
||||
SetupDiEnumDeviceInfo.restype = BOOL
|
||||
|
||||
SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW
|
||||
SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD]
|
||||
SetupDiGetClassDevs.restype = HDEVINFO
|
||||
SetupDiGetClassDevs.errcheck = ValidHandle
|
||||
|
||||
SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW
|
||||
SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD]
|
||||
SetupDiGetDeviceRegistryProperty.restype = BOOL
|
||||
|
||||
SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW
|
||||
SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD]
|
||||
SetupDiGetDeviceInstanceId.restype = BOOL
|
||||
|
||||
SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey
|
||||
SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM]
|
||||
SetupDiOpenDevRegKey.restype = HKEY
|
||||
|
||||
advapi32 = ctypes.windll.LoadLibrary("Advapi32")
|
||||
RegCloseKey = advapi32.RegCloseKey
|
||||
RegCloseKey.argtypes = [HKEY]
|
||||
RegCloseKey.restype = LONG
|
||||
|
||||
RegQueryValueEx = advapi32.RegQueryValueExW
|
||||
RegQueryValueEx.argtypes = [HKEY, LPCTSTR , LPDWORD, LPDWORD, LPBYTE, LPDWORD]
|
||||
RegQueryValueEx.restype = LONG
|
||||
|
||||
|
||||
DIGCF_PRESENT = 2
|
||||
DIGCF_DEVICEINTERFACE = 16
|
||||
INVALID_HANDLE_VALUE = 0
|
||||
ERROR_INSUFFICIENT_BUFFER = 122
|
||||
SPDRP_HARDWAREID = 1
|
||||
SPDRP_FRIENDLYNAME = 12
|
||||
SPDRP_LOCATION_PATHS = 35
|
||||
SPDRP_MFG = 11
|
||||
DICS_FLAG_GLOBAL = 1
|
||||
DIREG_DEV = 0x00000001
|
||||
KEY_READ = 0x20019
|
||||
|
||||
|
||||
def iterate_comports():
|
||||
"""Return a generator that yields descriptions for serial ports"""
|
||||
GUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough...
|
||||
guids_size = DWORD()
|
||||
if not SetupDiClassGuidsFromName(
|
||||
"Ports",
|
||||
GUIDs,
|
||||
ctypes.sizeof(GUIDs),
|
||||
ctypes.byref(guids_size)):
|
||||
raise ctypes.WinError()
|
||||
|
||||
# repeat for all possible GUIDs
|
||||
for index in range(guids_size.value):
|
||||
bInterfaceNumber = None
|
||||
g_hdi = SetupDiGetClassDevs(
|
||||
ctypes.byref(GUIDs[index]),
|
||||
None,
|
||||
NULL,
|
||||
DIGCF_PRESENT) # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports
|
||||
|
||||
devinfo = SP_DEVINFO_DATA()
|
||||
devinfo.cbSize = ctypes.sizeof(devinfo)
|
||||
index = 0
|
||||
while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)):
|
||||
index += 1
|
||||
|
||||
# get the real com port name
|
||||
hkey = SetupDiOpenDevRegKey(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
DICS_FLAG_GLOBAL,
|
||||
0,
|
||||
DIREG_DEV, # DIREG_DRV for SW info
|
||||
KEY_READ)
|
||||
port_name_buffer = ctypes.create_unicode_buffer(250)
|
||||
port_name_length = ULONG(ctypes.sizeof(port_name_buffer))
|
||||
RegQueryValueEx(
|
||||
hkey,
|
||||
"PortName",
|
||||
None,
|
||||
None,
|
||||
ctypes.byref(port_name_buffer),
|
||||
ctypes.byref(port_name_length))
|
||||
RegCloseKey(hkey)
|
||||
|
||||
# unfortunately does this method also include parallel ports.
|
||||
# we could check for names starting with COM or just exclude LPT
|
||||
# and hope that other "unknown" names are serial ports...
|
||||
if port_name_buffer.value.startswith('LPT'):
|
||||
continue
|
||||
|
||||
# hardware ID
|
||||
szHardwareID = ctypes.create_unicode_buffer(250)
|
||||
# try to get ID that includes serial number
|
||||
if not SetupDiGetDeviceInstanceId(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
#~ ctypes.byref(szHardwareID),
|
||||
szHardwareID,
|
||||
ctypes.sizeof(szHardwareID) - 1,
|
||||
None):
|
||||
# fall back to more generic hardware ID if that would fail
|
||||
if not SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_HARDWAREID,
|
||||
None,
|
||||
ctypes.byref(szHardwareID),
|
||||
ctypes.sizeof(szHardwareID) - 1,
|
||||
None):
|
||||
# Ignore ERROR_INSUFFICIENT_BUFFER
|
||||
if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
|
||||
raise ctypes.WinError()
|
||||
# stringify
|
||||
szHardwareID_str = szHardwareID.value
|
||||
|
||||
info = list_ports_common.ListPortInfo(port_name_buffer.value)
|
||||
|
||||
# in case of USB, make a more readable string, similar to that form
|
||||
# that we also generate on other platforms
|
||||
if szHardwareID_str.startswith('USB'):
|
||||
m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(\w+))?', szHardwareID_str, re.I)
|
||||
if m:
|
||||
info.vid = int(m.group(1), 16)
|
||||
if m.group(3):
|
||||
info.pid = int(m.group(3), 16)
|
||||
if m.group(5):
|
||||
bInterfaceNumber = int(m.group(5))
|
||||
if m.group(7):
|
||||
info.serial_number = m.group(7)
|
||||
# calculate a location string
|
||||
loc_path_str = ctypes.create_unicode_buffer(250)
|
||||
if SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_LOCATION_PATHS,
|
||||
None,
|
||||
ctypes.byref(loc_path_str),
|
||||
ctypes.sizeof(loc_path_str) - 1,
|
||||
None):
|
||||
m = re.finditer(r'USBROOT\((\w+)\)|#USB\((\w+)\)', loc_path_str.value)
|
||||
location = []
|
||||
for g in m:
|
||||
if g.group(1):
|
||||
location.append('{:d}'.format(int(g.group(1)) + 1))
|
||||
else:
|
||||
if len(location) > 1:
|
||||
location.append('.')
|
||||
else:
|
||||
location.append('-')
|
||||
location.append(g.group(2))
|
||||
if bInterfaceNumber is not None:
|
||||
location.append(':{}.{}'.format(
|
||||
'x', # XXX how to determine correct bConfigurationValue?
|
||||
bInterfaceNumber))
|
||||
if location:
|
||||
info.location = ''.join(location)
|
||||
info.hwid = info.usb_info()
|
||||
elif szHardwareID_str.startswith('FTDIBUS'):
|
||||
m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w+))?', szHardwareID_str, re.I)
|
||||
if m:
|
||||
info.vid = int(m.group(1), 16)
|
||||
info.pid = int(m.group(2), 16)
|
||||
if m.group(4):
|
||||
info.serial_number = m.group(4)
|
||||
# USB location is hidden by FDTI driver :(
|
||||
info.hwid = info.usb_info()
|
||||
else:
|
||||
info.hwid = szHardwareID_str
|
||||
|
||||
# friendly name
|
||||
szFriendlyName = ctypes.create_unicode_buffer(250)
|
||||
if SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_FRIENDLYNAME,
|
||||
#~ SPDRP_DEVICEDESC,
|
||||
None,
|
||||
ctypes.byref(szFriendlyName),
|
||||
ctypes.sizeof(szFriendlyName) - 1,
|
||||
None):
|
||||
info.description = szFriendlyName.value
|
||||
#~ else:
|
||||
# Ignore ERROR_INSUFFICIENT_BUFFER
|
||||
#~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
|
||||
#~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value))
|
||||
# ignore errors and still include the port in the list, friendly name will be same as port name
|
||||
|
||||
# manufacturer
|
||||
szManufacturer = ctypes.create_unicode_buffer(250)
|
||||
if SetupDiGetDeviceRegistryProperty(
|
||||
g_hdi,
|
||||
ctypes.byref(devinfo),
|
||||
SPDRP_MFG,
|
||||
#~ SPDRP_DEVICEDESC,
|
||||
None,
|
||||
ctypes.byref(szManufacturer),
|
||||
ctypes.sizeof(szManufacturer) - 1,
|
||||
None):
|
||||
info.manufacturer = szManufacturer.value
|
||||
yield info
|
||||
SetupDiDestroyDeviceInfoList(g_hdi)
|
||||
|
||||
|
||||
def comports(include_links=False):
|
||||
"""Return a list of info objects about serial ports"""
|
||||
return list(iterate_comports())
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
for port, desc, hwid in sorted(comports()):
|
||||
print("{}: {} [{}]".format(port, desc, hwid))
|
||||
Binary file not shown.
@@ -0,0 +1,976 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Very simple serial terminal
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import serial
|
||||
from serial.tools.list_ports import comports
|
||||
from serial.tools import hexlify_codec
|
||||
|
||||
# pylint: disable=wrong-import-order,wrong-import-position
|
||||
|
||||
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
# pylint: disable=redefined-builtin,invalid-name
|
||||
raw_input = input # in python3 it's "raw"
|
||||
unichr = chr
|
||||
|
||||
|
||||
def key_description(character):
|
||||
"""generate a readable description for a key"""
|
||||
ascii_code = ord(character)
|
||||
if ascii_code < 32:
|
||||
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
|
||||
else:
|
||||
return repr(character)
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
class ConsoleBase(object):
|
||||
"""OS abstraction for console (input/output codec, no echo)"""
|
||||
|
||||
def __init__(self):
|
||||
if sys.version_info >= (3, 0):
|
||||
self.byte_output = sys.stdout.buffer
|
||||
else:
|
||||
self.byte_output = sys.stdout
|
||||
self.output = sys.stdout
|
||||
|
||||
def setup(self):
|
||||
"""Set console to read single characters, no echo"""
|
||||
|
||||
def cleanup(self):
|
||||
"""Restore default console settings"""
|
||||
|
||||
def getkey(self):
|
||||
"""Read a single key from the console"""
|
||||
return None
|
||||
|
||||
def write_bytes(self, byte_string):
|
||||
"""Write bytes (already encoded)"""
|
||||
self.byte_output.write(byte_string)
|
||||
self.byte_output.flush()
|
||||
|
||||
def write(self, text):
|
||||
"""Write string"""
|
||||
self.output.write(text)
|
||||
self.output.flush()
|
||||
|
||||
def cancel(self):
|
||||
"""Cancel getkey operation"""
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# context manager:
|
||||
# switch terminal temporary to normal mode (e.g. to get user input)
|
||||
|
||||
def __enter__(self):
|
||||
self.cleanup()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.setup()
|
||||
|
||||
|
||||
if os.name == 'nt': # noqa
|
||||
import msvcrt
|
||||
import ctypes
|
||||
|
||||
class Out(object):
|
||||
"""file-like wrapper that uses os.write"""
|
||||
|
||||
def __init__(self, fd):
|
||||
self.fd = fd
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def write(self, s):
|
||||
os.write(self.fd, s)
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
|
||||
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
|
||||
ctypes.windll.kernel32.SetConsoleCP(65001)
|
||||
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
|
||||
# the change of the code page is not propagated to Python, manually fix it
|
||||
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
|
||||
sys.stdout = self.output
|
||||
self.output.encoding = 'UTF-8' # needed for input
|
||||
|
||||
def __del__(self):
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
|
||||
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
|
||||
|
||||
def getkey(self):
|
||||
while True:
|
||||
z = msvcrt.getwch()
|
||||
if z == unichr(13):
|
||||
return unichr(10)
|
||||
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
|
||||
msvcrt.getwch()
|
||||
else:
|
||||
return z
|
||||
|
||||
def cancel(self):
|
||||
# CancelIo, CancelSynchronousIo do not seem to work when using
|
||||
# getwch, so instead, send a key to the window with the console
|
||||
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
|
||||
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
|
||||
|
||||
elif os.name == 'posix':
|
||||
import atexit
|
||||
import termios
|
||||
import fcntl
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self.fd = sys.stdin.fileno()
|
||||
self.old = termios.tcgetattr(self.fd)
|
||||
atexit.register(self.cleanup)
|
||||
if sys.version_info < (3, 0):
|
||||
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
|
||||
else:
|
||||
self.enc_stdin = sys.stdin
|
||||
|
||||
def setup(self):
|
||||
new = termios.tcgetattr(self.fd)
|
||||
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
|
||||
new[6][termios.VMIN] = 1
|
||||
new[6][termios.VTIME] = 0
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, new)
|
||||
|
||||
def getkey(self):
|
||||
c = self.enc_stdin.read(1)
|
||||
if c == unichr(0x7f):
|
||||
c = unichr(8) # map the BS key (which yields DEL) to backspace
|
||||
return c
|
||||
|
||||
def cancel(self):
|
||||
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
|
||||
|
||||
def cleanup(self):
|
||||
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
class Transform(object):
|
||||
"""do-nothing: forward all data unchanged"""
|
||||
def rx(self, text):
|
||||
"""text received from serial port"""
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
"""text to be sent to serial port"""
|
||||
return text
|
||||
|
||||
def echo(self, text):
|
||||
"""text to be sent but displayed on console"""
|
||||
return text
|
||||
|
||||
|
||||
class CRLF(Transform):
|
||||
"""ENTER sends CR+LF"""
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r\n')
|
||||
|
||||
|
||||
class CR(Transform):
|
||||
"""ENTER sends CR"""
|
||||
|
||||
def rx(self, text):
|
||||
return text.replace('\r', '\n')
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r')
|
||||
|
||||
|
||||
class LF(Transform):
|
||||
"""ENTER sends LF"""
|
||||
|
||||
|
||||
class NoTerminal(Transform):
|
||||
"""remove typical terminal control codes from input"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
def rx(self, text):
|
||||
return text.translate(self.REPLACEMENT_MAP)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class NoControls(NoTerminal):
|
||||
"""Remove all control codes, incl. CR+LF"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x20: 0x2423, # visual space
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
|
||||
class Printable(Transform):
|
||||
"""Show decimal code for all non-ASCII characters and replace most control codes"""
|
||||
|
||||
def rx(self, text):
|
||||
r = []
|
||||
for c in text:
|
||||
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
|
||||
r.append(c)
|
||||
elif c < ' ':
|
||||
r.append(unichr(0x2400 + ord(c)))
|
||||
else:
|
||||
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
|
||||
r.append(' ')
|
||||
return ''.join(r)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class Colorize(Transform):
|
||||
"""Apply different colors for received and echo"""
|
||||
|
||||
def __init__(self):
|
||||
# XXX make it configurable, use colorama?
|
||||
self.input_color = '\x1b[37m'
|
||||
self.echo_color = '\x1b[31m'
|
||||
|
||||
def rx(self, text):
|
||||
return self.input_color + text
|
||||
|
||||
def echo(self, text):
|
||||
return self.echo_color + text
|
||||
|
||||
|
||||
class DebugIO(Transform):
|
||||
"""Print what is sent and received"""
|
||||
|
||||
def rx(self, text):
|
||||
sys.stderr.write(' [RX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
sys.stderr.write(' [TX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
|
||||
# other ideas:
|
||||
# - add date/time for each newline
|
||||
# - insert newline after: a) timeout b) packet end character
|
||||
|
||||
EOL_TRANSFORMATIONS = {
|
||||
'crlf': CRLF,
|
||||
'cr': CR,
|
||||
'lf': LF,
|
||||
}
|
||||
|
||||
TRANSFORMATIONS = {
|
||||
'direct': Transform, # no transformation
|
||||
'default': NoTerminal,
|
||||
'nocontrol': NoControls,
|
||||
'printable': Printable,
|
||||
'colorize': Colorize,
|
||||
'debug': DebugIO,
|
||||
}
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def ask_for_port():
|
||||
"""\
|
||||
Show a list of ports and ask the user for a choice. To make selection
|
||||
easier on systems with long device names, also allow the input of an
|
||||
index.
|
||||
"""
|
||||
sys.stderr.write('\n--- Available ports:\n')
|
||||
ports = []
|
||||
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
|
||||
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
|
||||
ports.append(port)
|
||||
while True:
|
||||
port = raw_input('--- Enter port index or full name: ')
|
||||
try:
|
||||
index = int(port) - 1
|
||||
if not 0 <= index < len(ports):
|
||||
sys.stderr.write('--- Invalid index!\n')
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
port = ports[index]
|
||||
return port
|
||||
|
||||
|
||||
class Miniterm(object):
|
||||
"""\
|
||||
Terminal application. Copy data from serial port to console and vice versa.
|
||||
Handle special keys from the console to show menu etc.
|
||||
"""
|
||||
|
||||
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
|
||||
self.console = Console()
|
||||
self.serial = serial_instance
|
||||
self.echo = echo
|
||||
self.raw = False
|
||||
self.input_encoding = 'UTF-8'
|
||||
self.output_encoding = 'UTF-8'
|
||||
self.eol = eol
|
||||
self.filters = filters
|
||||
self.update_transformations()
|
||||
self.exit_character = 0x1d # GS/CTRL+]
|
||||
self.menu_character = 0x14 # Menu: CTRL+T
|
||||
self.alive = None
|
||||
self._reader_alive = None
|
||||
self.receiver_thread = None
|
||||
self.rx_decoder = None
|
||||
self.tx_decoder = None
|
||||
|
||||
def _start_reader(self):
|
||||
"""Start reader thread"""
|
||||
self._reader_alive = True
|
||||
# start serial->console thread
|
||||
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
|
||||
self.receiver_thread.daemon = True
|
||||
self.receiver_thread.start()
|
||||
|
||||
def _stop_reader(self):
|
||||
"""Stop reader thread only, wait for clean exit of thread"""
|
||||
self._reader_alive = False
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def start(self):
|
||||
"""start worker threads"""
|
||||
self.alive = True
|
||||
self._start_reader()
|
||||
# enter console->serial loop
|
||||
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
|
||||
self.transmitter_thread.daemon = True
|
||||
self.transmitter_thread.start()
|
||||
self.console.setup()
|
||||
|
||||
def stop(self):
|
||||
"""set flag to stop worker threads"""
|
||||
self.alive = False
|
||||
|
||||
def join(self, transmit_only=False):
|
||||
"""wait for worker threads to terminate"""
|
||||
self.transmitter_thread.join()
|
||||
if not transmit_only:
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def close(self):
|
||||
self.serial.close()
|
||||
|
||||
def update_transformations(self):
|
||||
"""take list of transformation classes and instantiate them for rx and tx"""
|
||||
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
|
||||
for f in self.filters]
|
||||
self.tx_transformations = [t() for t in transformations]
|
||||
self.rx_transformations = list(reversed(self.tx_transformations))
|
||||
|
||||
def set_rx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for received data"""
|
||||
self.input_encoding = encoding
|
||||
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
|
||||
|
||||
def set_tx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for transmitted data"""
|
||||
self.output_encoding = encoding
|
||||
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
|
||||
|
||||
def dump_port_settings(self):
|
||||
"""Write current settings to sys.stderr"""
|
||||
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
|
||||
p=self.serial))
|
||||
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
|
||||
('active' if self.serial.rts else 'inactive'),
|
||||
('active' if self.serial.dtr else 'inactive'),
|
||||
('active' if self.serial.break_condition else 'inactive')))
|
||||
try:
|
||||
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
|
||||
('active' if self.serial.cts else 'inactive'),
|
||||
('active' if self.serial.dsr else 'inactive'),
|
||||
('active' if self.serial.ri else 'inactive'),
|
||||
('active' if self.serial.cd else 'inactive')))
|
||||
except serial.SerialException:
|
||||
# on RFC 2217 ports, it can happen if no modem state notification was
|
||||
# yet received. ignore this error.
|
||||
pass
|
||||
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
|
||||
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def reader(self):
|
||||
"""loop and copy serial->console"""
|
||||
try:
|
||||
while self.alive and self._reader_alive:
|
||||
# read all that is there or wait for one byte
|
||||
data = self.serial.read(self.serial.in_waiting or 1)
|
||||
if data:
|
||||
if self.raw:
|
||||
self.console.write_bytes(data)
|
||||
else:
|
||||
text = self.rx_decoder.decode(data)
|
||||
for transformation in self.rx_transformations:
|
||||
text = transformation.rx(text)
|
||||
self.console.write(text)
|
||||
except serial.SerialException:
|
||||
self.alive = False
|
||||
self.console.cancel()
|
||||
raise # XXX handle instead of re-raise?
|
||||
|
||||
def writer(self):
|
||||
"""\
|
||||
Loop and copy console->serial until self.exit_character character is
|
||||
found. When self.menu_character is found, interpret the next key
|
||||
locally.
|
||||
"""
|
||||
menu_active = False
|
||||
try:
|
||||
while self.alive:
|
||||
try:
|
||||
c = self.console.getkey()
|
||||
except KeyboardInterrupt:
|
||||
c = '\x03'
|
||||
if not self.alive:
|
||||
break
|
||||
if menu_active:
|
||||
self.handle_menu_key(c)
|
||||
menu_active = False
|
||||
elif c == self.menu_character:
|
||||
menu_active = True # next char will be for menu
|
||||
elif c == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
else:
|
||||
#~ if self.raw:
|
||||
text = c
|
||||
for transformation in self.tx_transformations:
|
||||
text = transformation.tx(text)
|
||||
self.serial.write(self.tx_encoder.encode(text))
|
||||
if self.echo:
|
||||
echo_text = c
|
||||
for transformation in self.tx_transformations:
|
||||
echo_text = transformation.echo(echo_text)
|
||||
self.console.write(echo_text)
|
||||
except:
|
||||
self.alive = False
|
||||
raise
|
||||
|
||||
def handle_menu_key(self, c):
|
||||
"""Implement a simple menu / settings"""
|
||||
if c == self.menu_character or c == self.exit_character:
|
||||
# Menu/exit character again -> send itself
|
||||
self.serial.write(self.tx_encoder.encode(c))
|
||||
if self.echo:
|
||||
self.console.write(c)
|
||||
elif c == '\x15': # CTRL+U -> upload file
|
||||
self.upload_file()
|
||||
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
|
||||
sys.stderr.write(self.get_help_text())
|
||||
elif c == '\x12': # CTRL+R -> Toggle RTS
|
||||
self.serial.rts = not self.serial.rts
|
||||
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
|
||||
elif c == '\x04': # CTRL+D -> Toggle DTR
|
||||
self.serial.dtr = not self.serial.dtr
|
||||
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
|
||||
elif c == '\x02': # CTRL+B -> toggle BREAK condition
|
||||
self.serial.break_condition = not self.serial.break_condition
|
||||
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
|
||||
elif c == '\x05': # CTRL+E -> toggle local echo
|
||||
self.echo = not self.echo
|
||||
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
|
||||
elif c == '\x06': # CTRL+F -> edit filters
|
||||
self.change_filter()
|
||||
elif c == '\x0c': # CTRL+L -> EOL mode
|
||||
modes = list(EOL_TRANSFORMATIONS) # keys
|
||||
eol = modes.index(self.eol) + 1
|
||||
if eol >= len(modes):
|
||||
eol = 0
|
||||
self.eol = modes[eol]
|
||||
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
|
||||
self.update_transformations()
|
||||
elif c == '\x01': # CTRL+A -> set encoding
|
||||
self.change_encoding()
|
||||
elif c == '\x09': # CTRL+I -> info
|
||||
self.dump_port_settings()
|
||||
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
|
||||
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
|
||||
elif c in 'pP': # P -> change port
|
||||
self.change_port()
|
||||
elif c in 'sS': # S -> suspend / open port temporarily
|
||||
self.suspend_port()
|
||||
elif c in 'bB': # B -> change baudrate
|
||||
self.change_baudrate()
|
||||
elif c == '8': # 8 -> change to 8 bits
|
||||
self.serial.bytesize = serial.EIGHTBITS
|
||||
self.dump_port_settings()
|
||||
elif c == '7': # 7 -> change to 8 bits
|
||||
self.serial.bytesize = serial.SEVENBITS
|
||||
self.dump_port_settings()
|
||||
elif c in 'eE': # E -> change to even parity
|
||||
self.serial.parity = serial.PARITY_EVEN
|
||||
self.dump_port_settings()
|
||||
elif c in 'oO': # O -> change to odd parity
|
||||
self.serial.parity = serial.PARITY_ODD
|
||||
self.dump_port_settings()
|
||||
elif c in 'mM': # M -> change to mark parity
|
||||
self.serial.parity = serial.PARITY_MARK
|
||||
self.dump_port_settings()
|
||||
elif c in 'sS': # S -> change to space parity
|
||||
self.serial.parity = serial.PARITY_SPACE
|
||||
self.dump_port_settings()
|
||||
elif c in 'nN': # N -> change to no parity
|
||||
self.serial.parity = serial.PARITY_NONE
|
||||
self.dump_port_settings()
|
||||
elif c == '1': # 1 -> change to 1 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE
|
||||
self.dump_port_settings()
|
||||
elif c == '2': # 2 -> change to 2 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_TWO
|
||||
self.dump_port_settings()
|
||||
elif c == '3': # 3 -> change to 1.5 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
|
||||
self.dump_port_settings()
|
||||
elif c in 'xX': # X -> change software flow control
|
||||
self.serial.xonxoff = (c == 'X')
|
||||
self.dump_port_settings()
|
||||
elif c in 'rR': # R -> change hardware flow control
|
||||
self.serial.rtscts = (c == 'R')
|
||||
self.dump_port_settings()
|
||||
else:
|
||||
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
|
||||
|
||||
def upload_file(self):
|
||||
"""Ask user for filenname and send its contents"""
|
||||
sys.stderr.write('\n--- File to upload: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
filename = sys.stdin.readline().rstrip('\r\n')
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
|
||||
while True:
|
||||
block = f.read(1024)
|
||||
if not block:
|
||||
break
|
||||
self.serial.write(block)
|
||||
# Wait for output buffer to drain.
|
||||
self.serial.flush()
|
||||
sys.stderr.write('.') # Progress indicator.
|
||||
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
|
||||
except IOError as e:
|
||||
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
|
||||
|
||||
def change_filter(self):
|
||||
"""change the i/o transformations"""
|
||||
sys.stderr.write('\n--- Available Filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'--- {:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
|
||||
with self.console:
|
||||
new_filters = sys.stdin.readline().lower().split()
|
||||
if new_filters:
|
||||
for f in new_filters:
|
||||
if f not in TRANSFORMATIONS:
|
||||
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
|
||||
break
|
||||
else:
|
||||
self.filters = new_filters
|
||||
self.update_transformations()
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def change_encoding(self):
|
||||
"""change encoding on the serial port"""
|
||||
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
|
||||
with self.console:
|
||||
new_encoding = sys.stdin.readline().strip()
|
||||
if new_encoding:
|
||||
try:
|
||||
codecs.lookup(new_encoding)
|
||||
except LookupError:
|
||||
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
|
||||
else:
|
||||
self.set_rx_encoding(new_encoding)
|
||||
self.set_tx_encoding(new_encoding)
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
|
||||
def change_baudrate(self):
|
||||
"""change the baudrate"""
|
||||
sys.stderr.write('\n--- Baudrate: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
backup = self.serial.baudrate
|
||||
try:
|
||||
self.serial.baudrate = int(sys.stdin.readline().strip())
|
||||
except ValueError as e:
|
||||
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
|
||||
self.serial.baudrate = backup
|
||||
else:
|
||||
self.dump_port_settings()
|
||||
|
||||
def change_port(self):
|
||||
"""Have a conversation with the user to change the serial port"""
|
||||
with self.console:
|
||||
try:
|
||||
port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
port = None
|
||||
if port and port != self.serial.port:
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
# save settings
|
||||
settings = self.serial.getSettingsDict()
|
||||
try:
|
||||
new_serial = serial.serial_for_url(port, do_not_open=True)
|
||||
# restore settings and open
|
||||
new_serial.applySettingsDict(settings)
|
||||
new_serial.rts = self.serial.rts
|
||||
new_serial.dtr = self.serial.dtr
|
||||
new_serial.open()
|
||||
new_serial.break_condition = self.serial.break_condition
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
|
||||
new_serial.close()
|
||||
else:
|
||||
self.serial.close()
|
||||
self.serial = new_serial
|
||||
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
|
||||
def suspend_port(self):
|
||||
"""\
|
||||
open port temporarily, allow reconnect, exit and port change to get
|
||||
out of the loop
|
||||
"""
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
self.serial.close()
|
||||
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
|
||||
do_change_port = False
|
||||
while not self.serial.is_open:
|
||||
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
|
||||
exit=key_description(self.exit_character)))
|
||||
k = self.console.getkey()
|
||||
if k == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
elif k in 'pP':
|
||||
do_change_port = True
|
||||
break
|
||||
try:
|
||||
self.serial.open()
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
|
||||
if do_change_port:
|
||||
self.change_port()
|
||||
else:
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
|
||||
|
||||
def get_help_text(self):
|
||||
"""return the help text"""
|
||||
# help text, starts with blank line!
|
||||
return """
|
||||
--- pySerial ({version}) - miniterm - help
|
||||
---
|
||||
--- {exit:8} Exit program
|
||||
--- {menu:8} Menu escape key, followed by:
|
||||
--- Menu keys:
|
||||
--- {menu:7} Send the menu character itself to remote
|
||||
--- {exit:7} Send the exit character itself to remote
|
||||
--- {info:7} Show info
|
||||
--- {upload:7} Upload file (prompt will be shown)
|
||||
--- {repr:7} encoding
|
||||
--- {filter:7} edit filters
|
||||
--- Toggles:
|
||||
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
|
||||
--- {echo:7} echo {eol:7} EOL
|
||||
---
|
||||
--- Port settings ({menu} followed by the following):
|
||||
--- p change port
|
||||
--- 7 8 set data bits
|
||||
--- N E O S M change parity (None, Even, Odd, Space, Mark)
|
||||
--- 1 2 3 set stop bits (1, 2, 1.5)
|
||||
--- b change baud rate
|
||||
--- x X disable/enable software flow control
|
||||
--- r R disable/enable hardware flow control
|
||||
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
|
||||
exit=key_description(self.exit_character),
|
||||
menu=key_description(self.menu_character),
|
||||
rts=key_description('\x12'),
|
||||
dtr=key_description('\x04'),
|
||||
brk=key_description('\x02'),
|
||||
echo=key_description('\x05'),
|
||||
info=key_description('\x09'),
|
||||
upload=key_description('\x15'),
|
||||
repr=key_description('\x01'),
|
||||
filter=key_description('\x06'),
|
||||
eol=key_description('\x0c'))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# default args can be used to override when calling main() from an other script
|
||||
# e.g to create a miniterm-my-device.py
|
||||
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
|
||||
"""Command line tool, entry point"""
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Miniterm - A simple terminal program for the serial port.")
|
||||
|
||||
parser.add_argument(
|
||||
"port",
|
||||
nargs='?',
|
||||
help="serial port name ('-' to show port list)",
|
||||
default=default_port)
|
||||
|
||||
parser.add_argument(
|
||||
"baudrate",
|
||||
nargs='?',
|
||||
type=int,
|
||||
help="set baud rate, default: %(default)s",
|
||||
default=default_baudrate)
|
||||
|
||||
group = parser.add_argument_group("port settings")
|
||||
|
||||
group.add_argument(
|
||||
"--parity",
|
||||
choices=['N', 'E', 'O', 'S', 'M'],
|
||||
type=lambda c: c.upper(),
|
||||
help="set parity, one of {N E O S M}, default: N",
|
||||
default='N')
|
||||
|
||||
group.add_argument(
|
||||
"--rtscts",
|
||||
action="store_true",
|
||||
help="enable RTS/CTS flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--xonxoff",
|
||||
action="store_true",
|
||||
help="enable software flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--rts",
|
||||
type=int,
|
||||
help="set initial RTS line state (possible values: 0, 1)",
|
||||
default=default_rts)
|
||||
|
||||
group.add_argument(
|
||||
"--dtr",
|
||||
type=int,
|
||||
help="set initial DTR line state (possible values: 0, 1)",
|
||||
default=default_dtr)
|
||||
|
||||
group.add_argument(
|
||||
"--ask",
|
||||
action="store_true",
|
||||
help="ask again for port when open fails",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("data handling")
|
||||
|
||||
group.add_argument(
|
||||
"-e", "--echo",
|
||||
action="store_true",
|
||||
help="enable local echo (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--encoding",
|
||||
dest="serial_port_encoding",
|
||||
metavar="CODEC",
|
||||
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
|
||||
default='UTF-8')
|
||||
|
||||
group.add_argument(
|
||||
"-f", "--filter",
|
||||
action="append",
|
||||
metavar="NAME",
|
||||
help="add text transformation",
|
||||
default=[])
|
||||
|
||||
group.add_argument(
|
||||
"--eol",
|
||||
choices=['CR', 'LF', 'CRLF'],
|
||||
type=lambda c: c.upper(),
|
||||
help="end of line mode",
|
||||
default='CRLF')
|
||||
|
||||
group.add_argument(
|
||||
"--raw",
|
||||
action="store_true",
|
||||
help="Do no apply any encodings/transformations",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("hotkeys")
|
||||
|
||||
group.add_argument(
|
||||
"--exit-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode of special character that is used to exit the application, default: %(default)s",
|
||||
default=0x1d) # GS/CTRL+]
|
||||
|
||||
group.add_argument(
|
||||
"--menu-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
|
||||
default=0x14) # Menu: CTRL+T
|
||||
|
||||
group = parser.add_argument_group("diagnostics")
|
||||
|
||||
group.add_argument(
|
||||
"-q", "--quiet",
|
||||
action="store_true",
|
||||
help="suppress non-error messages",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--develop",
|
||||
action="store_true",
|
||||
help="show Python traceback on error",
|
||||
default=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.menu_char == args.exit_char:
|
||||
parser.error('--exit-char can not be the same as --menu-char')
|
||||
|
||||
if args.filter:
|
||||
if 'help' in args.filter:
|
||||
sys.stderr.write('Available filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'{:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n')
|
||||
sys.exit(1)
|
||||
filters = args.filter
|
||||
else:
|
||||
filters = ['default']
|
||||
|
||||
while True:
|
||||
# no port given on command line -> ask user now
|
||||
if args.port is None or args.port == '-':
|
||||
try:
|
||||
args.port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write('\n')
|
||||
parser.error('user aborted and port is not given')
|
||||
else:
|
||||
if not args.port:
|
||||
parser.error('port is not given')
|
||||
try:
|
||||
serial_instance = serial.serial_for_url(
|
||||
args.port,
|
||||
args.baudrate,
|
||||
parity=args.parity,
|
||||
rtscts=args.rtscts,
|
||||
xonxoff=args.xonxoff,
|
||||
do_not_open=True)
|
||||
|
||||
if not hasattr(serial_instance, 'cancel_read'):
|
||||
# enable timeout for alive flag polling if cancel_read is not available
|
||||
serial_instance.timeout = 1
|
||||
|
||||
if args.dtr is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
|
||||
serial_instance.dtr = args.dtr
|
||||
if args.rts is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
|
||||
serial_instance.rts = args.rts
|
||||
|
||||
serial_instance.open()
|
||||
except serial.SerialException as e:
|
||||
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
|
||||
if args.develop:
|
||||
raise
|
||||
if not args.ask:
|
||||
sys.exit(1)
|
||||
else:
|
||||
args.port = '-'
|
||||
else:
|
||||
break
|
||||
|
||||
miniterm = Miniterm(
|
||||
serial_instance,
|
||||
echo=args.echo,
|
||||
eol=args.eol.lower(),
|
||||
filters=filters)
|
||||
miniterm.exit_character = unichr(args.exit_char)
|
||||
miniterm.menu_character = unichr(args.menu_char)
|
||||
miniterm.raw = args.raw
|
||||
miniterm.set_rx_encoding(args.serial_port_encoding)
|
||||
miniterm.set_tx_encoding(args.serial_port_encoding)
|
||||
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
|
||||
p=miniterm.serial))
|
||||
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
|
||||
key_description(miniterm.exit_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description('\x08')))
|
||||
|
||||
miniterm.start()
|
||||
try:
|
||||
miniterm.join(True)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
if not args.quiet:
|
||||
sys.stderr.write("\n--- exit ---\n")
|
||||
miniterm.join()
|
||||
miniterm.close()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user