diff options
author | zsquareplusc <cliechti@gmx.net> | 2016-06-14 22:09:56 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-14 22:09:56 +0200 |
commit | 6ea336f4714299506b7582edea0e335e56f5dc3b (patch) | |
tree | c675ba327245cf6a83cb62074e7046553c6f68d1 /serial | |
parent | 68239376ace131f5028734f70116050c96f5bbed (diff) | |
parent | 494cb49837e794ec525e26b198da0e9467c4678b (diff) | |
download | pyserial-git-6ea336f4714299506b7582edea0e335e56f5dc3b.tar.gz |
Merge pull request #134 from rob-smallshire/namespace-package
Namespace package and extract aio into a separate distribution
Diffstat (limited to 'serial')
-rw-r--r-- | serial/__init__.py | 5 | ||||
-rw-r--r-- | serial/aio.py | 428 |
2 files changed, 5 insertions, 428 deletions
diff --git a/serial/__init__.py b/serial/__init__.py index c3cc154..3874193 100644 --- a/serial/__init__.py +++ b/serial/__init__.py @@ -9,6 +9,8 @@ import importlib import sys +from pkgutil import extend_path + from serial.serialutil import * #~ SerialBase, SerialException, to_bytes, iterbytes @@ -17,6 +19,9 @@ __version__ = '3.1.1' VERSION = __version__ +# serial is a namespace package +__path__ = extend_path(__path__, __name__) + # pylint: disable=wrong-import-position if sys.platform == 'cli': from serial.serialcli import Serial diff --git a/serial/aio.py b/serial/aio.py deleted file mode 100644 index 8173897..0000000 --- a/serial/aio.py +++ /dev/null @@ -1,428 +0,0 @@ -#!/usr/bin/env python3 -# -# Experimental implementation of asyncio 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 -"""\ -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 - - -class SerialTransport(asyncio.Transport): - """An asyncio transport model of a serial communication channel. - - A transport class is an abstraction of a communication channel. - This allows protocol implementations to be developed against the - transport abstraction without needing to know the details of the - underlying channel, such as whether it is a pipe, a socket, or - indeed a serial port. - - - You generally won’t instantiate a transport yourself; instead, you - will call `create_serial_connection` which will create the - transport and try to initiate the underlying communication channel, - calling you back when it succeeds. - """ - - def __init__(self, loop, protocol, serial_instance): - super().__init__() - self._loop = loop - self._protocol = protocol - self._serial = serial_instance - self._closing = False - self._protocol_paused = False - self._max_read_size = 1024 - self._write_buffer = [] - self._set_write_buffer_limits() - self._has_reader = False - self._has_writer = False - - # XXX how to support url handlers too - - # Asynchronous I/O requires non-blocking devices - self._serial.timeout = 0 - self._serial.write_timeout = 0 - - # These two callbacks will be enqueued in a FIFO queue by asyncio - loop.call_soon(protocol.connection_made, self) - loop.call_soon(self._ensure_reader) - - @property - def serial(self): - """The underlying Serial instance.""" - return self._serial - - def __repr__(self): - return '{self.__class__.__name__}({self._loop}, {self._protocol}, {self.serial})'.format(self=self) - - def is_closing(self): - """Return True if the transport is closing or closed.""" - return self._closing - - def close(self): - """Close the transport gracefully. - - Any buffered data will be written asynchronously. No more data - will be received and further writes will be silently ignored. - After all buffered data is flushed, the protocol's - connection_lost() method will be called with None as its - argument. - """ - if not self._closing: - self._close(None) - - def _read_ready(self): - try: - data = self._serial.read(self._max_read_size) - except serial.SerialException as e: - self._close(exc=e) - else: - if data: - self._protocol.data_received(data) - - def write(self, data): - """Write some data to the transport. - - This method does not block; it buffers the data and arranges - for it to be sent out asynchronously. Writes made after the - transport has been closed will be ignored.""" - if self._closing: - return - - if self.get_write_buffer_size() == 0: - # Attempt to send it right away first - try: - n = self._serial.write(data) - except serial.SerialException as exc: - self._fatal_error(exc, 'Fatal write error on serial transport') - return - if n == len(data): - return # Whole request satisfied - assert n > 0 - data = data[n:] - self._ensure_writer() - - self._write_buffer.append(data) - self._maybe_pause_protocol() - - def can_write_eof(self): - """Serial ports do not support the concept of end-of-file. - - Always returns False. - """ - return False - - def pause_reading(self): - """Pause the receiving end of the transport. - - No data will be passed to the protocol’s data_received() method - until resume_reading() is called. - """ - self._remove_reader() - - def resume_reading(self): - """Resume the receiving end of the transport. - - Incoming data will be passed to the protocol's data_received() - method until pause_reading() is called. - """ - self._ensure_reader() - - def set_write_buffer_limits(self, high=None, low=None): - """Set the high- and low-water limits for write flow control. - - These two values control when call the protocol’s - pause_writing()and resume_writing() methods are called. If - specified, the low-water limit must be less than or equal to - the high-water limit. Neither high nor low can be negative. - """ - self._set_write_buffer_limits(high=high, low=low) - self._maybe_pause_protocol() - - def get_write_buffer_size(self): - """The number of bytes in the write buffer. - - This buffer is unbounded, so the result may be larger than the - the high water mark. - """ - return sum(map(len, self._write_buffer)) - - def write_eof(self): - raise NotImplementedError("Serial connections do not support end-of-file") - - def abort(self): - """Close the transport immediately. - - Pending operations will not be given opportunity to complete, - and buffered data will be lost. No more data will be received - and further writes will be ignored. The protocol's - connection_lost() method will eventually be called. - """ - self._abort(None) - - def _maybe_pause_protocol(self): - """To be called whenever the write-buffer size increases. - - Tests the current write-buffer size against the high water - mark configured for this transport. If the high water mark is - exceeded, the protocol is instructed to pause_writing(). - """ - if self.get_write_buffer_size() <= self._high_water: - return - if not self._protocol_paused: - self._protocol_paused = True - try: - self._protocol.pause_writing() - except Exception as exc: - self._loop.call_exception_handler({ - 'message': 'protocol.pause_writing() failed', - 'exception': exc, - 'transport': self, - 'protocol': self._protocol, - }) - - def _maybe_resume_protocol(self): - """To be called whenever the write-buffer size decreases. - - Tests the current write-buffer size against the low water - mark configured for this transport. If the write-buffer - size is below the low water mark, the protocol is - instructed that is can resume_writing(). - """ - if (self._protocol_paused and - self.get_write_buffer_size() <= self._low_water): - self._protocol_paused = False - try: - self._protocol.resume_writing() - except Exception as exc: - self._loop.call_exception_handler({ - 'message': 'protocol.resume_writing() failed', - 'exception': exc, - 'transport': self, - 'protocol': self._protocol, - }) - - def _write_ready(self): - """Asynchronously write buffered data. - - This method is called back asynchronously as a writer - registered with the asyncio event-loop against the - underlying file descriptor for the serial port. - - Should the write-buffer become empty if this method - is invoked while the transport is closing, the protocol's - connection_lost() method will be called with None as its - argument. - """ - data = b''.join(self._write_buffer) - num_bytes = len(data) - assert data, 'Write buffer should not be empty' - - self._write_buffer.clear() - - try: - n = self._serial.write(data) - except (BlockingIOError, InterruptedError): - self._write_buffer.append(data) - except serial.SerialException as exc: - self._fatal_error(exc, 'Fatal write error on serial transport') - else: - if n == len(data): - assert self._flushed() - self._remove_writer() - self._maybe_resume_protocol() # May cause further writes - # _write_ready may have been invoked by the event loop - # after the transport was closed, as part of the ongoing - # process of flushing buffered data. If the buffer - # is now empty, we can close the connection - if self._closing and self._flushed(): - self._close() - return - - assert n > 0 - data = data[n:] - self._write_buffer.append(data) # Try again later - self._maybe_resume_protocol() - assert self._has_writer - - def _ensure_reader(self): - if (not self._has_reader) and (not self._closing): - self._loop.add_reader(self._serial.fd, self._read_ready) - self._has_reader = True - - def _remove_reader(self): - if self._has_reader: - self._loop.remove_reader(self._serial.fd) - self._has_reader = False - - def _ensure_writer(self): - if (not self._has_writer) and (not self._closing): - self._loop.add_writer(self._serial.fd, self._write_ready) - self._has_writer = True - - def _remove_writer(self): - if self._has_writer: - self._loop.remove_writer(self._serial.fd) - self._has_writer = False - - def _set_write_buffer_limits(self, high=None, low=None): - """Ensure consistent write-buffer limits.""" - if high is None: - high = 64 * 1024 if low is None else 4 * low - if low is None: - low = high // 4 - if not high >= low >= 0: - raise ValueError('high (%r) must be >= low (%r) must be >= 0' % - (high, low)) - self._high_water = high - self._low_water = low - - def _fatal_error(self, exc, message='Fatal error on serial transport'): - """Report a fatal error to the event-loop and abort the transport.""" - self._loop.call_exception_handler({ - 'message': message, - 'exception': exc, - 'transport': self, - 'protocol': self._protocol, - }) - self._abort(exc) - - def _flushed(self): - """True if the write buffer is empty, otherwise False.""" - return self.get_write_buffer_size() == 0 - - def _close(self, exc=None): - """Close the transport gracefully. - - If the write buffer is already empty, writing will be - stopped immediately and a call to the protocol's - connection_lost() method scheduled. - - If the write buffer is not already empty, the - asynchronous writing will continue, and the _write_ready - method will call this _close method again when the - buffer has been flushed completely. - """ - self._closing = True - self._remove_reader() - if self._flushed(): - self._remove_writer() - self._loop.call_soon(self._call_connection_lost, exc) - - def _abort(self, exc): - """Close the transport immediately. - - Pending operations will not be given opportunity to complete, - and buffered data will be lost. No more data will be received - and further writes will be ignored. The protocol's - connection_lost() method will eventually be called with the - passed exception. - """ - self._closing = True - self._remove_reader() - self._remove_writer() # Pending buffered data will not be written - self._loop.call_soon(self._call_connection_lost, exc) - - def _call_connection_lost(self, exc): - """Close the connection. - - Informs the protocol through connection_lost() and clears - pending buffers and closes the serial connection. - """ - assert self._closing - assert not self._has_writer - assert not self._has_reader - self._serial.flush() - try: - self._protocol.connection_lost(exc) - finally: - self._write_buffer.clear() - self._serial.close() - self._serial = None - self._protocol = None - self._loop = None - - -@asyncio.coroutine -def create_serial_connection(loop, protocol_factory, *args, **kwargs): - ser = serial.serial_for_url(*args, **kwargs) - protocol = protocol_factory() - transport = SerialTransport(loop, protocol, ser) - return (transport, protocol) - - -@asyncio.coroutine -def open_serial_connection(**kwargs): - """A wrapper for create_serial_connection() returning a (reader, - writer) pair. - - The reader returned is a StreamReader instance; the writer is a - StreamWriter instance. - - The arguments are all the usual arguments to Serial(). Additional - optional keyword arguments are loop (to set the event loop instance - to use) and limit (to set the buffer limit passed to the - StreamReader. - - This function is a coroutine. - """ - # in order to avoid errors when pySerial is installed under Python 2, - # avoid Pyhthon 3 syntax here. So do not use this function as a good - # example! - loop = kwargs.get('loop', asyncio.get_event_loop()) - limit = kwargs.get('limit', asyncio.streams._DEFAULT_LIMIT) - reader = asyncio.StreamReader(limit=limit, loop=loop) - protocol = asyncio.StreamReaderProtocol(reader, loop=loop) - # in Python 3 we would write "yield transport, _ from c()" - for transport, _ in create_serial_connection( - loop=loop, - protocol_factory=lambda: protocol, - **kwargs): - yield transport, _ - writer = asyncio.StreamWriter(transport, protocol, reader, loop) - # in Python 3 we would write "return reader, writer" - raise StopIteration(reader, writer) - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# 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)) - if b'\n' in data: - self.transport.close() - - def connection_lost(self, exc): - print('port closed') - asyncio.get_event_loop().stop() - - def pause_writing(self): - print('pause writing') - print(self.transport.get_write_buffer_size()) - - def resume_writing(self): - print(self.transport.get_write_buffer_size()) - print('resume writing') - - 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() |