1#! python
2#
3# Base class and support functions used by various backends.
4#
5# This file is part of pySerial. https://github.com/pyserial/pyserial
6# (C) 2001-2020 Chris Liechti <[email protected]>
7#
8# SPDX-License-Identifier:    BSD-3-Clause
9
10from __future__ import absolute_import
11
12import io
13import time
14
15# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)``
16# isn't returning the contents (very unfortunate). Therefore we need special
17# cases and test for it. Ensure that there is a ``memoryview`` object for older
18# Python versions. This is easier than making every test dependent on its
19# existence.
20try:
21    memoryview
22except (NameError, AttributeError):
23    # implementation does not matter as we do not really use it.
24    # it just must not inherit from something else we might care for.
25    class memoryview(object):   # pylint: disable=redefined-builtin,invalid-name
26        pass
27
28try:
29    unicode
30except (NameError, AttributeError):
31    unicode = str       # for Python 3, pylint: disable=redefined-builtin,invalid-name
32
33try:
34    basestring
35except (NameError, AttributeError):
36    basestring = (str,)    # for Python 3, pylint: disable=redefined-builtin,invalid-name
37
38
39# "for byte in data" fails for python3 as it returns ints instead of bytes
40def iterbytes(b):
41    """Iterate over bytes, returning bytes instead of ints (python3)"""
42    if isinstance(b, memoryview):
43        b = b.tobytes()
44    i = 0
45    while True:
46        a = b[i:i + 1]
47        i += 1
48        if a:
49            yield a
50        else:
51            break
52
53
54# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11'
55# so a simple ``bytes(sequence)`` doesn't work for all versions
56def to_bytes(seq):
57    """convert a sequence to a bytes type"""
58    if isinstance(seq, bytes):
59        return seq
60    elif isinstance(seq, bytearray):
61        return bytes(seq)
62    elif isinstance(seq, memoryview):
63        return seq.tobytes()
64    elif isinstance(seq, unicode):
65        raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq))
66    else:
67        # handle list of integers and bytes (one or more items) for Python 2 and 3
68        return bytes(bytearray(seq))
69
70
71# create control bytes
72XON = to_bytes([17])
73XOFF = to_bytes([19])
74
75CR = to_bytes([13])
76LF = to_bytes([10])
77
78
79PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
80STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
81FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8)
82
83PARITY_NAMES = {
84    PARITY_NONE: 'None',
85    PARITY_EVEN: 'Even',
86    PARITY_ODD: 'Odd',
87    PARITY_MARK: 'Mark',
88    PARITY_SPACE: 'Space',
89}
90
91
92class SerialException(IOError):
93    """Base class for serial port related exceptions."""
94
95
96class SerialTimeoutException(SerialException):
97    """Write timeouts give an exception"""
98
99
100class PortNotOpenError(SerialException):
101    """Port is not open"""
102    def __init__(self):
103        super(PortNotOpenError, self).__init__('Attempting to use a port that is not open')
104
105
106class Timeout(object):
107    """\
108    Abstraction for timeout operations. Using time.monotonic() if available
109    or time.time() in all other cases.
110
111    The class can also be initialized with 0 or None, in order to support
112    non-blocking and fully blocking I/O operations. The attributes
113    is_non_blocking and is_infinite are set accordingly.
114    """
115    if hasattr(time, 'monotonic'):
116        # Timeout implementation with time.monotonic(). This function is only
117        # supported by Python 3.3 and above. It returns a time in seconds
118        # (float) just as time.time(), but is not affected by system clock
119        # adjustments.
120        TIME = time.monotonic
121    else:
122        # Timeout implementation with time.time(). This is compatible with all
123        # Python versions but has issues if the clock is adjusted while the
124        # timeout is running.
125        TIME = time.time
126
127    def __init__(self, duration):
128        """Initialize a timeout with given duration"""
129        self.is_infinite = (duration is None)
130        self.is_non_blocking = (duration == 0)
131        self.duration = duration
132        if duration is not None:
133            self.target_time = self.TIME() + duration
134        else:
135            self.target_time = None
136
137    def expired(self):
138        """Return a boolean, telling if the timeout has expired"""
139        return self.target_time is not None and self.time_left() <= 0
140
141    def time_left(self):
142        """Return how many seconds are left until the timeout expires"""
143        if self.is_non_blocking:
144            return 0
145        elif self.is_infinite:
146            return None
147        else:
148            delta = self.target_time - self.TIME()
149            if delta > self.duration:
150                # clock jumped, recalculate
151                self.target_time = self.TIME() + self.duration
152                return self.duration
153            else:
154                return max(0, delta)
155
156    def restart(self, duration):
157        """\
158        Restart a timeout, only supported if a timeout was already set up
159        before.
160        """
161        self.duration = duration
162        self.target_time = self.TIME() + duration
163
164
165class SerialBase(io.RawIOBase):
166    """\
167    Serial port base class. Provides __init__ function and properties to
168    get/set port settings.
169    """
170
171    # default values, may be overridden in subclasses that do not support all values
172    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
173                 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000,
174                 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000,
175                 3000000, 3500000, 4000000)
176    BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
177    PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE)
178    STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
179
180    def __init__(self,
181                 port=None,
182                 baudrate=9600,
183                 bytesize=EIGHTBITS,
184                 parity=PARITY_NONE,
185                 stopbits=STOPBITS_ONE,
186                 timeout=None,
187                 xonxoff=False,
188                 rtscts=False,
189                 write_timeout=None,
190                 dsrdtr=False,
191                 inter_byte_timeout=None,
192                 exclusive=None,
193                 **kwargs):
194        """\
195        Initialize comm port object. If a "port" is given, then the port will be
196        opened immediately. Otherwise a Serial port object in closed state
197        is returned.
198        """
199
200        self.is_open = False
201        self.portstr = None
202        self.name = None
203        # correct values are assigned below through properties
204        self._port = None
205        self._baudrate = None
206        self._bytesize = None
207        self._parity = None
208        self._stopbits = None
209        self._timeout = None
210        self._write_timeout = None
211        self._xonxoff = None
212        self._rtscts = None
213        self._dsrdtr = None
214        self._inter_byte_timeout = None
215        self._rs485_mode = None  # disabled by default
216        self._rts_state = True
217        self._dtr_state = True
218        self._break_state = False
219        self._exclusive = None
220
221        # assign values using get/set methods using the properties feature
222        self.port = port
223        self.baudrate = baudrate
224        self.bytesize = bytesize
225        self.parity = parity
226        self.stopbits = stopbits
227        self.timeout = timeout
228        self.write_timeout = write_timeout
229        self.xonxoff = xonxoff
230        self.rtscts = rtscts
231        self.dsrdtr = dsrdtr
232        self.inter_byte_timeout = inter_byte_timeout
233        self.exclusive = exclusive
234
235        # watch for backward compatible kwargs
236        if 'writeTimeout' in kwargs:
237            self.write_timeout = kwargs.pop('writeTimeout')
238        if 'interCharTimeout' in kwargs:
239            self.inter_byte_timeout = kwargs.pop('interCharTimeout')
240        if kwargs:
241            raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs))
242
243        if port is not None:
244            self.open()
245
246    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
247
248    # to be implemented by subclasses:
249    # def open(self):
250    # def close(self):
251
252    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
253
254    @property
255    def port(self):
256        """\
257        Get the current port setting. The value that was passed on init or using
258        setPort() is passed back.
259        """
260        return self._port
261
262    @port.setter
263    def port(self, port):
264        """\
265        Change the port.
266        """
267        if port is not None and not isinstance(port, basestring):
268            raise ValueError('"port" must be None or a string, not {}'.format(type(port)))
269        was_open = self.is_open
270        if was_open:
271            self.close()
272        self.portstr = port
273        self._port = port
274        self.name = self.portstr
275        if was_open:
276            self.open()
277
278    @property
279    def baudrate(self):
280        """Get the current baud rate setting."""
281        return self._baudrate
282
283    @baudrate.setter
284    def baudrate(self, baudrate):
285        """\
286        Change baud rate. It raises a ValueError if the port is open and the
287        baud rate is not possible. If the port is closed, then the value is
288        accepted and the exception is raised when the port is opened.
289        """
290        try:
291            b = int(baudrate)
292        except TypeError:
293            raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
294        else:
295            if b < 0:
296                raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
297            self._baudrate = b
298            if self.is_open:
299                self._reconfigure_port()
300
301    @property
302    def bytesize(self):
303        """Get the current byte size setting."""
304        return self._bytesize
305
306    @bytesize.setter
307    def bytesize(self, bytesize):
308        """Change byte size."""
309        if bytesize not in self.BYTESIZES:
310            raise ValueError("Not a valid byte size: {!r}".format(bytesize))
311        self._bytesize = bytesize
312        if self.is_open:
313            self._reconfigure_port()
314
315    @property
316    def exclusive(self):
317        """Get the current exclusive access setting."""
318        return self._exclusive
319
320    @exclusive.setter
321    def exclusive(self, exclusive):
322        """Change the exclusive access setting."""
323        self._exclusive = exclusive
324        if self.is_open:
325            self._reconfigure_port()
326
327    @property
328    def parity(self):
329        """Get the current parity setting."""
330        return self._parity
331
332    @parity.setter
333    def parity(self, parity):
334        """Change parity setting."""
335        if parity not in self.PARITIES:
336            raise ValueError("Not a valid parity: {!r}".format(parity))
337        self._parity = parity
338        if self.is_open:
339            self._reconfigure_port()
340
341    @property
342    def stopbits(self):
343        """Get the current stop bits setting."""
344        return self._stopbits
345
346    @stopbits.setter
347    def stopbits(self, stopbits):
348        """Change stop bits size."""
349        if stopbits not in self.STOPBITS:
350            raise ValueError("Not a valid stop bit size: {!r}".format(stopbits))
351        self._stopbits = stopbits
352        if self.is_open:
353            self._reconfigure_port()
354
355    @property
356    def timeout(self):
357        """Get the current timeout setting."""
358        return self._timeout
359
360    @timeout.setter
361    def timeout(self, timeout):
362        """Change timeout setting."""
363        if timeout is not None:
364            try:
365                timeout + 1     # test if it's a number, will throw a TypeError if not...
366            except TypeError:
367                raise ValueError("Not a valid timeout: {!r}".format(timeout))
368            if timeout < 0:
369                raise ValueError("Not a valid timeout: {!r}".format(timeout))
370        self._timeout = timeout
371        if self.is_open:
372            self._reconfigure_port()
373
374    @property
375    def write_timeout(self):
376        """Get the current timeout setting."""
377        return self._write_timeout
378
379    @write_timeout.setter
380    def write_timeout(self, timeout):
381        """Change timeout setting."""
382        if timeout is not None:
383            if timeout < 0:
384                raise ValueError("Not a valid timeout: {!r}".format(timeout))
385            try:
386                timeout + 1     # test if it's a number, will throw a TypeError if not...
387            except TypeError:
388                raise ValueError("Not a valid timeout: {!r}".format(timeout))
389
390        self._write_timeout = timeout
391        if self.is_open:
392            self._reconfigure_port()
393
394    @property
395    def inter_byte_timeout(self):
396        """Get the current inter-character timeout setting."""
397        return self._inter_byte_timeout
398
399    @inter_byte_timeout.setter
400    def inter_byte_timeout(self, ic_timeout):
401        """Change inter-byte timeout setting."""
402        if ic_timeout is not None:
403            if ic_timeout < 0:
404                raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
405            try:
406                ic_timeout + 1     # test if it's a number, will throw a TypeError if not...
407            except TypeError:
408                raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
409
410        self._inter_byte_timeout = ic_timeout
411        if self.is_open:
412            self._reconfigure_port()
413
414    @property
415    def xonxoff(self):
416        """Get the current XON/XOFF setting."""
417        return self._xonxoff
418
419    @xonxoff.setter
420    def xonxoff(self, xonxoff):
421        """Change XON/XOFF setting."""
422        self._xonxoff = xonxoff
423        if self.is_open:
424            self._reconfigure_port()
425
426    @property
427    def rtscts(self):
428        """Get the current RTS/CTS flow control setting."""
429        return self._rtscts
430
431    @rtscts.setter
432    def rtscts(self, rtscts):
433        """Change RTS/CTS flow control setting."""
434        self._rtscts = rtscts
435        if self.is_open:
436            self._reconfigure_port()
437
438    @property
439    def dsrdtr(self):
440        """Get the current DSR/DTR flow control setting."""
441        return self._dsrdtr
442
443    @dsrdtr.setter
444    def dsrdtr(self, dsrdtr=None):
445        """Change DsrDtr flow control setting."""
446        if dsrdtr is None:
447            # if not set, keep backwards compatibility and follow rtscts setting
448            self._dsrdtr = self._rtscts
449        else:
450            # if defined independently, follow its value
451            self._dsrdtr = dsrdtr
452        if self.is_open:
453            self._reconfigure_port()
454
455    @property
456    def rts(self):
457        return self._rts_state
458
459    @rts.setter
460    def rts(self, value):
461        self._rts_state = value
462        if self.is_open:
463            self._update_rts_state()
464
465    @property
466    def dtr(self):
467        return self._dtr_state
468
469    @dtr.setter
470    def dtr(self, value):
471        self._dtr_state = value
472        if self.is_open:
473            self._update_dtr_state()
474
475    @property
476    def break_condition(self):
477        return self._break_state
478
479    @break_condition.setter
480    def break_condition(self, value):
481        self._break_state = value
482        if self.is_open:
483            self._update_break_state()
484
485    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
486    # functions useful for RS-485 adapters
487
488    @property
489    def rs485_mode(self):
490        """\
491        Enable RS485 mode and apply new settings, set to None to disable.
492        See serial.rs485.RS485Settings for more info about the value.
493        """
494        return self._rs485_mode
495
496    @rs485_mode.setter
497    def rs485_mode(self, rs485_settings):
498        self._rs485_mode = rs485_settings
499        if self.is_open:
500            self._reconfigure_port()
501
502    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
503
504    _SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
505                       'dsrdtr', 'rtscts', 'timeout', 'write_timeout',
506                       'inter_byte_timeout')
507
508    def get_settings(self):
509        """\
510        Get current port settings as a dictionary. For use with
511        apply_settings().
512        """
513        return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS])
514
515    def apply_settings(self, d):
516        """\
517        Apply stored settings from a dictionary returned from
518        get_settings(). It's allowed to delete keys from the dictionary. These
519        values will simply left unchanged.
520        """
521        for key in self._SAVED_SETTINGS:
522            if key in d and d[key] != getattr(self, '_' + key):   # check against internal "_" value
523                setattr(self, key, d[key])          # set non "_" value to use properties write function
524
525    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
526
527    def __repr__(self):
528        """String representation of the current port settings and its state."""
529        return '{name}<id=0x{id:x}, open={p.is_open}>(port={p.portstr!r}, ' \
530               'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \
531               'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \
532               'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format(
533                   name=self.__class__.__name__, id=id(self), p=self)
534
535    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
536    # compatibility with io library
537    # pylint: disable=invalid-name,missing-docstring
538
539    def readable(self):
540        return True
541
542    def writable(self):
543        return True
544
545    def seekable(self):
546        return False
547
548    def readinto(self, b):
549        data = self.read(len(b))
550        n = len(data)
551        try:
552            b[:n] = data
553        except TypeError as err:
554            import array
555            if not isinstance(b, array.array):
556                raise err
557            b[:n] = array.array('b', data)
558        return n
559
560    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
561    # context manager
562
563    def __enter__(self):
564        if self._port is not None and not self.is_open:
565            self.open()
566        return self
567
568    def __exit__(self, *args, **kwargs):
569        self.close()
570
571    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
572
573    def send_break(self, duration=0.25):
574        """\
575        Send break condition. Timed, returns to idle state after given
576        duration.
577        """
578        if not self.is_open:
579            raise PortNotOpenError()
580        self.break_condition = True
581        time.sleep(duration)
582        self.break_condition = False
583
584    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
585    # backwards compatibility / deprecated functions
586
587    def flushInput(self):
588        self.reset_input_buffer()
589
590    def flushOutput(self):
591        self.reset_output_buffer()
592
593    def inWaiting(self):
594        return self.in_waiting
595
596    def sendBreak(self, duration=0.25):
597        self.send_break(duration)
598
599    def setRTS(self, value=1):
600        self.rts = value
601
602    def setDTR(self, value=1):
603        self.dtr = value
604
605    def getCTS(self):
606        return self.cts
607
608    def getDSR(self):
609        return self.dsr
610
611    def getRI(self):
612        return self.ri
613
614    def getCD(self):
615        return self.cd
616
617    def setPort(self, port):
618        self.port = port
619
620    @property
621    def writeTimeout(self):
622        return self.write_timeout
623
624    @writeTimeout.setter
625    def writeTimeout(self, timeout):
626        self.write_timeout = timeout
627
628    @property
629    def interCharTimeout(self):
630        return self.inter_byte_timeout
631
632    @interCharTimeout.setter
633    def interCharTimeout(self, interCharTimeout):
634        self.inter_byte_timeout = interCharTimeout
635
636    def getSettingsDict(self):
637        return self.get_settings()
638
639    def applySettingsDict(self, d):
640        self.apply_settings(d)
641
642    def isOpen(self):
643        return self.is_open
644
645    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
646    # additional functionality
647
648    def read_all(self):
649        """\
650        Read all bytes currently available in the buffer of the OS.
651        """
652        return self.read(self.in_waiting)
653
654    def read_until(self, expected=LF, size=None):
655        """\
656        Read until an expected sequence is found (line feed by default), the size
657        is exceeded or until timeout occurs.
658        """
659        lenterm = len(expected)
660        line = bytearray()
661        timeout = Timeout(self._timeout)
662        while True:
663            c = self.read(1)
664            if c:
665                line += c
666                if line[-lenterm:] == expected:
667                    break
668                if size is not None and len(line) >= size:
669                    break
670            else:
671                break
672            if timeout.expired():
673                break
674        return bytes(line)
675
676    def iread_until(self, *args, **kwargs):
677        """\
678        Read lines, implemented as generator. It will raise StopIteration on
679        timeout (empty read).
680        """
681        while True:
682            line = self.read_until(*args, **kwargs)
683            if not line:
684                break
685            yield line
686
687
688#  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
689if __name__ == '__main__':
690    import sys
691    s = SerialBase()
692    sys.stdout.write('port name:  {}\n'.format(s.name))
693    sys.stdout.write('baud rates: {}\n'.format(s.BAUDRATES))
694    sys.stdout.write('byte sizes: {}\n'.format(s.BYTESIZES))
695    sys.stdout.write('parities:   {}\n'.format(s.PARITIES))
696    sys.stdout.write('stop bits:  {}\n'.format(s.STOPBITS))
697    sys.stdout.write('{}\n'.format(s))
698