Skip to main content

Serial port library (PySerial wrapper) with more features

Project description

OK serial I/O for Python   🔌〡〇〡〇〡🐍

A Python serial port library (based on PySerial) with improved port discovery and I/O semantics. (API reference)

Think twice before using this library! Consider something more established:

  • good old PySerial - the implementation under ok-serial, well established and widely used
  • pyserial-asyncio - official and "proper" asyncio support for PySerial
  • pyserial-asyncio-fast - pyserial-asyncio fork designed for faster writes
  • aioserial - alternative asyncio wrapper designed for ease of use
  • bonus recommendation: tio - not a library, not Python, but a great serial terminal utility

Purpose

Since 2001, PySerial has been the workhorse serial port / UART library for Python. It runs most places Python does and abstracts lots of gnarly system details. However, some issues keep coming up:

  • Most modern serial ports are USB, and get temporary names like /dev/ttyACM3 or COM4. PySerial's serial.tools.list_ports.grep(...) or Linux's udev rules require extra clumsy steps to use.

  • Nonblocking or concurrent PySerial I/O is tricky and often broken entirely.

  • PySerial has small buffers; overruns lose data and/or block unexpectedly.

  • PySerial doesn't lock ports by default; even when enabled, PySerial only uses one advisory locking method. Bad things happen when multiple programs try to use the same port.

The ok-serial library uses PySerial internally but has a revised interface:

  • Ports are referenced by port match expressions with wildcard support, eg. *RP2040* or 2e43:0226 or manufacturer="Arduino".

  • I/O operations are thread safe and can be blocking, non-blocking, timeout-based, or async. Blocking operations can be interrupted. The semantics of concurrent access, partial reads/writes, interruption, I/O errors, and other edge cases are well defined.

  • I/O buffers are limited only by system memory; writes never block. (A blocking drain is available.)

  • Several port locking modes are supported, with exclusive locking by default. All of /var/lock/LCK..* files, flock(...) (like PySerial), and TIOCEXCL (as available) are used avoid contention.

  • SerialPortTracker is an automatic reconnection helper for graceful handling of pluggable devices.

Installation

pip install ok-serial

(or uv add ok-serial, etc.)

Usage

Here is a minimal example:

import ok_serial

conn = ok_serial.SerialConnection(match="MyDevice", baud=115200)
conn.write("Hello Device!")
while (data := conn.read_sync(timeout=5)):
    print("Received data:", data)
print("...5 seconds elapsed with no data")

(Note that "MyDevice" is a port match expression.)

API elements worth knowing include:

Methods come in different flavors of blocking behavior:

  • *_sync methods (eg. read_sync) block, accept timeout=..., and can raise exceptions
  • *_async methods (eg. read_async) return a Future the caller can await (see asyncio)
    • Use asyncio.timeout to add a timeout
    • Errors are reported via the Future (await will raise)
  • Other methods (neither *_sync nor *_async) are non-blocking.

Methods and functions of any flavor are thread-safe and thread-sane, and any error or closure on a connection interrupts all pending operations on that connection.

See the full API reference docs for interface details.

Serial port attributes

Serial ports can have attributes such as description text, USB vendor/product ID, serial number and the like. These are captured as key/value pairs in SerialPort.attr as returned by scan_serial_ports.

Specific attribute keys come from PySerial and are platform/device dependent but usually include:

  • device - system device name, eg. /dev/ttyUSB1 or COM3
  • description - human readable text, eg. Arduino Uno
  • manufacturer - USB device manufacturer name, eg. `
  • vid_pid - USB vendor and product ID, eg. 0403:6001
  • serial_number - USB device serial, eg. DF62585783553434
  • location - system bus attachment path, eg. 3-2.1:1.0

To see all the attributes, install ok-serial, connect some device(s) and run okserial --verbose:

Serial port: /dev/ttyACM3
   device='/dev/ttyACM3'
   name='ttyACM3'
   description='Feather RP2040 RFM - Pico Serial'
   hwid='USB VID:PID=239A:812D SER=DF62585783553434 LOCATION=3-2.1:1.0'
   vid='9114'
   pid='33069'
   serial_number='DF62585783553434'
   location='3-2.1:1.0'
   manufacturer='Adafruit'
   product='Feather RP2040 RFM'
   interface='Pico Serial'
   usb_device_path='/sys/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2.1'
   device_path='/sys/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2.1/3-2.1:1.0'
   subsystem='usb'
   usb_interface_path='/sys/devices/pci0000:00/0000:00:14.0/usb3/3-2/3-2.1/3-2.1:1.0'
   vid_pid='239A:812D'
...

Serial port match expressions

Instead of plain device names, ok-serial can use port match expressions to find ports. Match expressions are made of space-separated search terms:

  • word - case INsensitive whole-word match in any attribute value
  • wild*word? - * and ? are wildcards (any text, single character)
  • 1234, 0xabcd - hex or decimal values match hex or decimal equivalents
  • spaces\ and\ st\*rs\? - special characters can be escaped with backslash...
  • "spaces and st*rs?" - ...or with C/JS/Python-style quoted strings
  • attr="specific value" - scoped to attribute prefix, must match whole value
  • ~/regexp/ - case SENSITIVE regex match (Python re)
  • attr~/regexp/ - attribute-scoped partial regex match
  • attr~/^regexp$/ - attribute-scoped whole-value regex match
  • newest - the shortest-uptime port among those matching other terms
  • oldest - the longest-uptime port among those matching other terms

Some examples:

  • Pico Serial - the words pico AND serial must each appear somewhere (any case, as a whole word)
  • RP2040 DF625* - the word rp2040 AND a word starting with df625
  • subsys="usb" - subsystem must equal usb (any case but whole value)
  • Adafruit serial~/^DF625/ - adafruit must appear somewhere (any case), and serial_number must begin with DF625 (uppercase as written)

You can use okserial list with $OK_LOGGING_LEVEL=debug to see parsing results:

% OK_LOGGING_LEVEL=debug okserial list -v 'Adafruit serial~/^DF625.*/'
🕸  ok_serial.scanning: Parsed 'Adafruit serial~/^DF625.*/':
  *~/(?<!\w)(Adafruit)(?!\w)/
  serial~/^DF625.*/
🕸  ok_serial.scanning: Found 36 ports
🎯 36 serial ports found, 1 matches 'Adafruit serial~/^DF625.*/'
Serial port: /dev/ttyACM3
   device='/dev/ttyACM3'
   ...

Sharing modes

When opening a port, SerialConnection offers a choice of four sharing modes:

  • oblivious (not recommended) - no locking is done and locks are ignored. Multiple users may end up sending and receiving data on the same port.
  • polite - open fails if the port is locked. Once opened, no locks are held except for a shared lock to ward off other polite users. If a less polite program opens the port later there will be conflict. (In the future, this mode will attempt to notice such conflicts and close out the port, deferring to the less-polite program.)
  • exclusive (the default) - open fails if the port is locked by a non-polite user. Once opened, locking protocols are used to prevent or discourage others from opening the port.
  • stomp (use with care!) - any other program using the port is killed, if possible; regardless, locks are acquired, if possible; regardless, the port is opened, if possible.

Sharing mode implementation is limited by OS capabilities, process permissions, and historical conventions of port usage coordination. Best efforts are taken but your mileage may vary.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ok_serial-0.3.1.tar.gz (18.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ok_serial-0.3.1-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

File details

Details for the file ok_serial-0.3.1.tar.gz.

File metadata

  • Download URL: ok_serial-0.3.1.tar.gz
  • Upload date:
  • Size: 18.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ok_serial-0.3.1.tar.gz
Algorithm Hash digest
SHA256 c4610e9292c653ccf6850c2cd864339b84be6c4bb5ca96e561c69dfc633e3c93
MD5 31aa1ef5d873f0369aee47733d1f1880
BLAKE2b-256 1a443c6a07517e547feb2bd30cb2ab5b5cfb9c46691ec67ca31f0aa492be9b1c

See more details on using hashes here.

File details

Details for the file ok_serial-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: ok_serial-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 23.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ok_serial-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6ce90d54e4c442f2d036354ff57ec2335567b5f9bc01e02b23a87aa57ca15d52
MD5 c23164265c00a3b4817a4eb59fd21b47
BLAKE2b-256 5c3cf7b82508c513ff146afc049615cd7ee02ca589de55fd732d41f0c770a233

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page