123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697 |
- #! python
- #
- # Base class and support functions used by various backends.
- #
- # This file is part of pySerial. https://github.com/pyserial/pyserial
- # (C) 2001-2020 Chris Liechti <cliechti@gmx.net>
- #
- # SPDX-License-Identifier: BSD-3-Clause
- from __future__ import absolute_import
- 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"""
- class PortNotOpenError(SerialException):
- """Port is not open"""
- def __init__(self):
- super(PortNotOpenError, self).__init__('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 self._port is not None and 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, expected=LF, size=None):
- """\
- Read until an expected sequence is found ('\n' by default), the size
- is exceeded or until timeout occurs.
- """
- lenterm = len(expected)
- line = bytearray()
- timeout = Timeout(self._timeout)
- while True:
- c = self.read(1)
- if c:
- line += c
- if line[-lenterm:] == expected:
- 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))
|