Skip to content

Commit d94feb9

Browse files
More complete narrative and reference documentation. (#63)
Co-authored-by: Robert Smallshire <[email protected]>
1 parent 839d17d commit d94feb9

File tree

5 files changed

+143
-55
lines changed

5 files changed

+143
-55
lines changed

documentation/api.rst

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,88 @@
22
pySerial-asyncio API
33
======================
44

5-
asyncio
6-
=======
7-
85
.. module:: serial_asyncio
96

10-
.. warning:: This implementation is currently in an experimental state. Use
11-
at your own risk.
127

13-
Experimental asyncio support is available for Python 3.4 and newer. The module
14-
:mod:`serial_asyncio` provides a :class:`asyncio.Transport`:
15-
``SerialTransport``.
8+
The following high-level functions are provided for initiating a serial connection:
9+
10+
.. function:: create_serial_connection(loop, protocol_factory, *args, **kwargs)
11+
:async:
12+
13+
Open a streaming connection to the specified serial port.
14+
15+
:param loop: The *asyncio* event loop
16+
:param protocol_factory: A callable which when invoked without arguments and which should
17+
return a :class:`asyncio.Protocol` subclass. Most commonly the protocol *class*
18+
(*e.g.* ``MyProtocol``) can be passed as this argument. If it is required to use an
19+
existing protocol *instance*, pass a zero-argument lambda which evaluates to the instance,
20+
such as ``lambda: my_protocol``
21+
:param args: Forwarded to the :class:`serial.Serial` constructor
22+
:param kwargs: Forwarded to the :class:`serial.Serial` constructor
23+
:returns: A coroutine for managing a serial port connection, which when
24+
awaited returns transport and protocol instances.
25+
:platform: Posix
26+
27+
Use this function to associate an asynchronous call-back based protocol with an
28+
new :class:`serial.Serial` instance that will be created on your behalf.
29+
30+
The chronological order of the operation is:
31+
32+
1. ``protocol_factory`` is called without arguments and must return
33+
a :class:`asyncio.Protocol` subclass instance.
34+
35+
2. The protocol instance is tied to a :class:`~serial_asyncio.SerialTransport`.
1636

37+
3. This coroutine returns successfully with a ``(transport, protocol)`` pair.
1738

18-
A factory function (`asyncio.coroutine`) is provided:
39+
4. The :meth:`~serial_asyncio.SerialTransport.connection_made()` method of the protocol
40+
will be called at some point by the event loop.
1941

20-
.. function:: create_serial_connection(loop, protocol_factory, \*args, \*\*kwargs)
2142

22-
:param loop: The event handler
23-
:param protocol_factory: Factory function for a :class:`asyncio.Protocol`
24-
:param args: Passed to the :class:`serial.Serial` init function
25-
:param kwargs: Passed to the :class:`serial.Serial` init function
43+
44+
.. function:: connection_for_serial(loop, protocol_factory, serial_instance)
45+
:async:
46+
47+
Open a streaming connection to an existing serial port instance.
48+
49+
:param loop: The *asyncio* event loop
50+
:param protocol_factory: A callable which when invoked without arguments and which should
51+
return a :class:`asyncio.Protocol` subclass. Most commonly the protocol *class*
52+
(*e.g.* ``MyProtocol``) can be passed as this argument. If it is required to use an
53+
existing protocol *instance*, pass a zero-argument lambda which evaluates to the instance,
54+
such as ``lambda: my_protocol``
55+
:param serial_instance: A :class:`serial.Serial` instance.
56+
:returns: A coroutine for managing a serial port connection, which when
57+
awaited returns transport and protocol instances.
2658
:platform: Posix
2759

28-
Get a connection making coroutine.
60+
Use this function to associate an asynchronous call-back based protocol with an
61+
existing :class:`serial.Serial` instance.
62+
63+
The chronological order of the operation is:
2964

30-
Example::
65+
1. ``protocol_factory`` is called without arguments and must return
66+
a :class:`asyncio.Protocol` subclass instance.
3167

32-
import asyncio
33-
import serial_asyncio
34-
35-
36-
class Output(asyncio.Protocol):
37-
def connection_made(self, transport):
38-
self.transport = transport
39-
print('port opened', transport)
40-
transport.serial.rts = False
41-
transport.write(b'hello world\n')
68+
2. The protocol instance is tied to a :class:`~serial_asyncio.SerialTransport`.
4269

43-
def data_received(self, data):
44-
print('data received', repr(data))
45-
self.transport.close()
70+
3. This coroutine returns successfully with a ``(transport, protocol)`` pair.
4671

47-
def connection_lost(self, exc):
48-
print('port closed')
49-
asyncio.get_event_loop().stop()
72+
4. The :meth:`~serial_asyncio.SerialTransport.connection_made()` method of the protocol
73+
will be called at some point by the event loop.
5074

51-
loop = asyncio.get_event_loop()
52-
coro = serial_asyncio.create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
53-
loop.run_until_complete(coro)
54-
loop.run_forever()
55-
loop.close()
5675

76+
.. function:: open_serial_connection(*, loop=None, limit=None, **kwargs)
77+
:async:
78+
79+
Open a streaming connection to an existing serial port instance.
80+
81+
:param loop: The *asyncio* event loop
82+
:param limit: A optional buffer limit in bytes for the :class:`asyncio.StreamReader`
83+
:param kwargs: Forwarded to the :class:`serial.Serial` constructor
84+
:returns: A coroutine for managing a serial port connection, which when
85+
awaited returns an :class:`asyncio.StreamReader` and a :class:`asyncio.StreamWriter`.
86+
:platform: Posix
87+
88+
Use this function to open connections where serial traffic is handled by
89+
an asynchronous coroutine interacting with :class:`asyncio.StreamReader` and a :class:`asyncio.StreamWriter` objects.

documentation/conf.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222

2323
# Add any Sphinx extension module names here, as strings. They can be extensions
2424
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
25-
extensions = ['sphinx.ext.intersphinx']
25+
extensions = [
26+
'sphinx.ext.intersphinx',
27+
'sphinx_rtd_theme',
28+
]
2629

2730
# Add any paths that contain templates here, relative to this directory.
2831
templates_path = ['_templates']
@@ -38,7 +41,7 @@
3841

3942
# General information about the project.
4043
project = u'pySerial-asyncio'
41-
copyright = u'2015-2017, pySerial-team'
44+
copyright = u'2015-2020, pySerial-team'
4245

4346
# The version info for the project you're documenting, acts as replacement for
4447
# |version| and |release|, also used in various other places throughout the
@@ -81,7 +84,7 @@
8184
#show_authors = False
8285

8386
# The name of the Pygments (syntax highlighting) style to use.
84-
pygments_style = 'sphinx'
87+
#pygments_style = 'sphinx'
8588

8689
# A list of ignored prefixes for module index sorting.
8790
#modindex_common_prefix = []
@@ -91,7 +94,7 @@
9194

9295
# The theme to use for HTML and HTML Help pages. Major themes that come with
9396
# Sphinx are currently 'default' and 'sphinxdoc'.
94-
#html_theme = 'default'
97+
html_theme = "sphinx_rtd_theme"
9598

9699
# Theme options are theme-specific and customize the look and feel of a theme
97100
# further. For a list of options available for each theme, see the
@@ -195,6 +198,6 @@
195198

196199
# for external links to standard library
197200
intersphinx_mapping = {
198-
#~ 'python': ('http://docs.python.org', None),
199-
'py': ('http://docs.python.org', None),
200-
}
201+
'python': ('https://docs.python.org/3', None),
202+
'pyserial': ('https://pyserial.readthedocs.io/en/latest', None),
203+
}

documentation/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ Welcome to pySerial-asyncio's documentation
55

66
`Async I/O`_ extension for the `Python Serial Port`_ package for OSX, Linux, BSD
77

8-
It depends on pySerial and is compatible with Python 3.4 and later.
8+
It depends on pySerial and is compatible with Python 3.5 and later.
99

10-
.. _`Async I/O`: https://www.python.org/dev/peps/pep-3156/
10+
.. _`Async I/O`: https://docs.python.org/3/library/asyncio.html
1111
.. _`Python Serial Port`: https://pypi.python.org/pypi/pyserial
1212

1313

documentation/shortintro.rst

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,63 @@
1-
==================
2-
Short introduction
3-
==================
1+
========
2+
Overview
3+
========
4+
5+
Serial transports, protocols and streams
6+
----------------------------------------
7+
8+
This module layers `asyncio <https://docs.python.org/3/library/asyncio.html>`_ support onto
9+
`pySerial <http://pyserial.readthedocs.io/>`_. It provides support for working with serial
10+
ports through *asyncio*
11+
`Transports <https://docs.python.org/3/library/asyncio-protocol.html#transports>`_,
12+
`Protocols <https://docs.python.org/3/library/asyncio-protocol.html#protocols>`_, and
13+
`Streams <https://docs.python.org/3/library/asyncio-stream.html>`_.
14+
15+
Transports are a low-level abstraction, provided by this package in the form of an
16+
:class:`asyncio.Transport` implementation called :class:`SerialTransport`, which manages the
17+
asynchronous transmission of data through an underlying *pySerial* :class:`~serial.Serial`
18+
instance. Transports are concerned with *how* bytes are transmitted through the serial port.
19+
20+
Protocols are a callback-based abstraction which determine *which* bytes are transmitted
21+
through an underlying transport. You can implement a subclass of :class:`asyncio.Protocol` which
22+
reads from, and/or writes to, a :class:`~serial_asyncio.SerialTransport`. When a serial connection
23+
is established your protocol will be handed a transport, to which your protocol
24+
implementation can write data as necessary. Incoming data and other serial connection lifecycle
25+
events cause callbacks on your protocol to be invoked, so it can take action as necessary.
26+
27+
Usually, you will not create a :class:`~serial_asyncio.SerialTransport` directly. Rather, you will
28+
define a ``Protocol`` class and pass that protocol to a function such as
29+
:func:`~serial_asyncio.create_serial_connection()` which will instantiate your ``Protocol`` and
30+
connect it to a :class:`~serial_asyncio.SerialTransport`.
31+
32+
Streams are a coroutine-based alternative to callback-based protocols. This package provides a
33+
function :func:`~serial_asyncio.open_serial_connection` which returns :class:`asyncio.StreamReader`
34+
and :class:`asyncio.StreamWriter` objects for interacting with underlying protocol and transport
35+
objects, which this library will create for you.
36+
37+
38+
Protocol Example
39+
----------------
40+
41+
This example defines a very simple Protocol which sends a greeting message through the serial port
42+
and displays to the console any data received through the serial port, until a newline byte is
43+
received.
44+
45+
A call is made to :func:`create_serial_connection()`, to which the protocol *class* (not an
46+
instance) is passed, together with arguments destined for the :class:`~serial.Serial` constructor.
47+
This call returns a coroutine object. When passed to :func:`~asyncio.run_until_complete` the
48+
coroutine is scheduled to run as an :class:`asyncio.Task` by the *asyncio* library, and the result
49+
of the coroutine, which is a tuple containing the transport and protocol instances, return to the
50+
caller.
51+
52+
While the event loop is running (:meth:`~asyncio.AbstractEventLoop.loop.run_forever`), or until
53+
the protocol closes the transport itself, the protocol will process data received through the serial
54+
port asynchronously::
455

5-
Example::
656

757
import asyncio
858
import serial_asyncio
959

10-
class Output(asyncio.Protocol):
60+
class OutputProtocol(asyncio.Protocol):
1161
def connection_made(self, transport):
1262
self.transport = transport
1363
print('port opened', transport)
@@ -32,8 +82,8 @@ Example::
3282
print('resume writing')
3383

3484
loop = asyncio.get_event_loop()
35-
coro = serial_asyncio.create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
36-
loop.run_until_complete(coro)
85+
coro = serial_asyncio.create_serial_connection(loop, OutputProtocol, '/dev/ttyUSB0', baudrate=115200)
86+
transport, protocol = loop.run_until_complete(coro)
3787
loop.run_forever()
3888
loop.close()
3989

serial_asyncio/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ async def connection_for_serial(loop, protocol_factory, serial_instance):
465465

466466
async def open_serial_connection(*,
467467
loop=None,
468-
limit=asyncio.streams._DEFAULT_LIMIT,
468+
limit=None,
469469
**kwargs):
470470
"""A wrapper for create_serial_connection() returning a (reader,
471471
writer) pair.
@@ -482,6 +482,8 @@ async def open_serial_connection(*,
482482
"""
483483
if loop is None:
484484
loop = asyncio.get_event_loop()
485+
if limit is None:
486+
limit = asyncio.streams._DEFAULT_LIMIT
485487
reader = asyncio.StreamReader(limit=limit, loop=loop)
486488
protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
487489
transport, _ = await create_serial_connection(
@@ -526,6 +528,6 @@ def resume_writing(self):
526528

527529
loop = asyncio.get_event_loop()
528530
coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
529-
loop.run_until_complete(coro)
531+
transport, protocol = loop.run_until_complete(coro)
530532
loop.run_forever()
531533
loop.close()

0 commit comments

Comments
 (0)