sensors module now in early working stage

This commit is contained in:
d3m1g0d
2019-01-23 20:59:51 +01:00
parent 56ce1e9a32
commit 9c8418a03d
123 changed files with 9170 additions and 5954 deletions

Binary file not shown.

View File

@@ -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()

Binary file not shown.

976
backend/venv/bin/miniterm.py Executable file
View 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

Binary file not shown.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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_

View File

@@ -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
)

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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()

View 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()

View 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))

View 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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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,
)

View File

@@ -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()

View File

@@ -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'))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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()

Some files were not shown because too many files have changed in this diff Show More