baldaquin.serial_ — Serial interface#

The module provides basically an abstraction layer over the pyserial package.

The DeviceId class is a simple class grouping the vendor and product IDs of a device into a single data structure. The Port class represents a serial port, and groups the most useful things out of the ListPortInfo class from pyserial.

The most useful bit in the module is probably the list_com_ports(), listing all the COM ports, along wit the device that are attached to them.

>>> ports = serial_.list_com_ports()
>>> [INFO] Scanning serial devices...
>>> [DEBUG] PortInfo(name='/dev/ttyS0', device_id=(vid=None, pid=None), manufacturer=None)
>>> [DEBUG] PortInfo(name='/dev/ttyACM0', device_id=(vid=0x2341, pid=0x43), manufacturer='Arduino (www.arduino.cc)')
>>> [INFO] Done, 2 device(s) found.

The method allows to filter over device IDs (or, equivalently, over vid, pid tuples) which comes handy when one is interested in a particular device or set of devices.

>>> ports = serial_.list_com_ports((0x2341, 0x0043))
>>> [INFO] Scanning serial devices...
>>> [DEBUG] PortInfo(name='/dev/ttyS0', device_id=(vid=None, pid=None), manufacturer=None)
>>> [DEBUG] PortInfo(name='/dev/ttyACM0', device_id=(vid=0x2341, pid=0x43), manufacturer='Arduino (www.arduino.cc)')
>>> [INFO] Done, 2 device(s) found.
>>> [INFO] Filtering port list for specific devices: [(vid=0x2341, pid=0x43)]...
>>> [INFO] Done, 1 device(s) remaining.
>>> [DEBUG] PortInfo(name='/dev/ttyACM0', device_id=(vid=0x2341, pid=0x43), manufacturer='Arduino (www.arduino.cc)')

In addition, the SerialInterface acts like a base class that can be subclassed to implement any specific communication protocol over the serial port. It is a simple wrapper around the pyserial Serial class and provides a few convenience methods to read and write data over the serial port. The class is designed to be instantiated with no arguments, with the actual connection to the serial port being done via the SerialInterface.connect() method, e.g.

>>> interface = SerialInterface()
>>> port_info = serial_.list_com_ports()[0]
>>> interface.connect(port_info, baudrate=115200, timeout=1)

Although we generally prefer to exchange fixed-width binary data over the serial port, we recognize that situations exist where it is more convenient to talk text, and we provide the TextLine class to facilitate text-based exchange of numerical, string, or mixed data. The basic idea is that devices connected over the serial port can pass along line-feed terminated text strings with a proper header, and where the different fields are separated by a given character, and the SerialInterface.read_text_line() is equipped to do the magic, and return a TextLine object that can be esily unpacked into the underlying fields. Note that TextLine objects support the post-facto insertion of new fields via the TextLine.prepend() and TextLine.append() methods.

Module documentation#

Serial port interface.

class baldaquin.serial_.DeviceId(vid: int, pid: int)[source]#

Data class to hold the device id information.

A device id is basically a tuple of two integers, the vendor id (vid) and the product id (pid), and the class has exactly these two members.

Parameters:
  • vid (int) – The vendor id.

  • pid (int) – The product id.

vid: int#
pid: int#
static _hex(value: int) str[source]#

Convenience function to format an integer as a hexadecimal string, gracefully handling the case when the input is not an integer.

class baldaquin.serial_.PortInfo(name: str, device_id: DeviceId, manufacturer: str = None)[source]#

Small data class holding the information about a COM port.

This is a simple wrapper around the serial.tools.list_ports_common.ListPortInfo isolating the basic useful functionalities, for the sake of simplicity. (And, in addition, this convenience class is meant to integrate the functionality embedded in the DeviceId class, which is handy.)

See https://pyserial.readthedocs.io/en/latest/tools.html#serial.tools.list_ports.ListPortInfo for more information.

Parameters:
  • name (str) – The name of the port (e.g., /dev/ttyACM0).

  • device_id (DeviceId) – The device id.

  • manufacturer (str, optional) – The manufacturer of the device attached to the port.

name: str#
device_id: DeviceId#
manufacturer: str = None#
classmethod from_serial(port_info: ListPortInfo) PortInfo[source]#

Create a Port object from a ListPortInfo object.

baldaquin.serial_.list_com_ports(*device_ids: DeviceId) list[PortInfo][source]#

List all the com ports with devices attached, possibly with a filter on the device ids we are interested into.

Parameters:

device_ids (DeviceId or vid, pid tuples, optional) – An arbitrary number of device ids to filter the list of ports returned by pyserial. This is useful when we are searching for a specific device attached to a port; an arduino uno, e.g., might look something like (0x2341, 0x43).

Returns:

The list of COM ports.

Return type:

list of PortInfo objects

class baldaquin.serial_.TextLine(data: bytes)[source]#

Small class defining a simple protocol for passing messages in string form over the serial port.

A message is basically a small piece of text, in the form of a binary stream encoded in UTF-8, starting with a # header character and terminated by a line feed, and that can contain an arbitrary number of fields separated by a ;. Note the delimiters are properly checked at creation time. The unpack() class method returns a tuple with the field values, converted in the proper types.

Note this is a subclass of the bytearray class, as opposed to the bytes class, because we provide a minimal support for prepending and appending fields to the text line. (Insertion at a generic index does not make much sense, as the field widths are variable.)

The choice of the header character and the line feed is largely arbitrary, but the requirement that the line is terminated by a line feed provides the most straightforward way to read variable-length messages from the serial port in a reliable way, leveraging the serial readline() method.

Users should by no means feel compelled to use this, but we provide it in order to facilitate passing strings over the serial port, saving boilerplate code for runtime checking.

Example

>>> message = TextLine.from_text('#Hello world;1\n')
>>> name, version = message.unpack(str, int)
>>> print(name, type(name))
Hello world, <class 'str'>
>>> print(version, type(version))
1 <class 'int'>
_ENCODING = 'utf-8'#
_HEADER = '#'#
_HEADER_ORD = 35#
_LINE_FEED = '\n'#
_LINE_FEED_ORD = 10#
_SEPARATOR = ';'#
classmethod from_text(text: str) bytes[source]#

Create a message from a text string (mainly for debug purposes).

prepend(value: str) None[source]#

Prepend a string field to the text line.

Parameters:

value (str) – The string to be prepended.

append(value: str) None[source]#

Append a string field to the text line.

Parameters:

value (str) – The string to be prepended.

unpack(*converters) tuple[source]#

Unpack the message in its (properly formatted) fields.

Parameters:

arguments (callable) – The converter functions to be used to unpack the fields. Note the number of converters passed to the function must be either zero (in which case all the fields are treated as strings) or equal to the number of fields in the message. A RuntimeError is raised if that is not the case.

class baldaquin.serial_.SerialInterface[source]#

Small wrapper around the serial.Serial class.

Note the class is designed to be instantiated without arguments, and all the setup is done through the connect() method, where the port is configured and the actual connection is opened.

connect(port_info: PortInfo, baudrate: int = 115200, timeout: float = None) None[source]#

Setup the serial connection.

Note that the first argument is a fully-fledged PortInfo object, which allows to keep track of the basic information about the device connected to the port, in addition to configuration of the port itself. This is handy, e.g., for the auto-upload of arduino sketched, where we need to know the designation of the board we are uploading to.

Parameters:
  • port_info (PortInfo) – The PortInfo object describing the port.

  • baudrate (int) –

    The baud rate.

    Verbatim from the pyserial documentation: the parameter baudrate can be one of the standard values: 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200. These are well supported on all platforms.

    Standard values above 115200, such as: 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000 also work on many platforms and devices.

    Non-standard values are also supported on some platforms (GNU/Linux, MAC OSX >= Tiger, Windows). Though, even on these platforms some serial ports may reject non-standard values.

  • timeout (float) –

    The timeout in seconds.

    Verbatim from the pyserial documentation: possible values for the parameter timeout which controls the behavior of read():

    • timeout = None: wait forever / until requested number of bytes are received

    • timeout = 0: non-blocking mode, return immediately in any case, returning zero or more, up to the requested number of bytes

    • timeout = x: set timeout to x seconds (float allowed) returns immediately when the requested number of bytes are available, otherwise wait until the timeout expires and return all bytes that were received until then.

set_timeout(timeout: float) None[source]#

Set the timeout for the serial port.

Parameters:

timeout (float) – The timeout in seconds.

disconnect()[source]#

Disconnect from the serial port.

pulse_dtr(pulse_length: float = 0.5) None[source]#

Pulse the DTR line for a given amount of time.

This asserts the DTR line, waits for a specific amount of time, and then de-asserts the line.

Parameters:

pulse_length (float) – The duration (in seconds) for the DTR line signal to be asserted.

read_available_data() bytes[source]#

Read all the available data on the serial interface.

Warning

Be cautious when using this method, as the amount of data available on the serial port at any given time may depend on the exact timing of the function call relative to what the device attached to the port is doing, and in the long run it might be difficult to read a stream of variable-length data reliably.

read_text_line() TextLine[source]#

Read a line-feed terminated string data from the serial interface and return a TextLine object.

read_and_unpack(fmt: str) Any[source]#

Read a given number of bytes from the serial port and unpack them.

Note that the number of bytes to be read from the serial port is automatically calculated from the format string. See https://docs.python.org/3/library/struct.html for all the details about format strings and byte ordering.

Parameters:

fmt (str) – The format string for the packet to be read from the seria port.

Returns:

Returns the proper Python object for the format string at hand.

Return type:

any

Example

>>> s = SerialInterface(port)
>>> val = s.read_and_unpack('B') # Single byte (val is int)
>>> val = s.read_and_unpack('>L') # Big-endian unsigned long (val is also int)
pack_and_write(value: Any, fmt: str) int[source]#

Pack a given value into a proper bytes object and write the latter to the serial port.

Parameters:
  • value (any) – The value to be written to the serial port.

  • fmt (str) – The format string to pack the value with.

Returns:

The number of bytes written to the serial port.

Return type:

int

_abc_impl = <_abc._abc_data object>#