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