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/ttyACM3orCOM4. PySerial'sserial.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*or2e43:0226ormanufacturer="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), andTIOCEXCL(as available) are used avoid contention. -
SerialPortTrackeris 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:
SerialConnection- establish a connection to a specific port and perform I/Oscan_serial_ports- get all ports on the system, with descriptive attributesSerialPortMatcher- use port match expression strings to identify ports of interestSerialPortTracker- scan and connect to a port with automatic error retry
Methods come in different flavors of blocking behavior:
*_syncmethods (eg.read_sync) block, accepttimeout=..., and can raise exceptions*_asyncmethods (eg.read_async) return aFuturethe caller canawait(see asyncio)- Use
asyncio.timeoutto add a timeout - Errors are reported via the
Future(awaitwill raise)
- Use
- Other methods (neither
*_syncnor*_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/ttyUSB1orCOM3description- human readable text, eg.Arduino Unomanufacturer- USB device manufacturer name, eg. `vid_pid- USB vendor and product ID, eg.0403:6001serial_number- USB device serial, eg.DF62585783553434location- 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 valuewild*word?-*and?are wildcards (any text, single character)1234,0xabcd- hex or decimal values match hex or decimal equivalentsspaces\ and\ st\*rs\?- special characters can be escaped with backslash..."spaces and st*rs?"- ...or with C/JS/Python-style quoted stringsattr="specific value"- scoped to attribute prefix, must match whole value~/regexp/- case SENSITIVE regex match (Pythonre)attr~/regexp/- attribute-scoped partial regex matchattr~/^regexp$/- attribute-scoped whole-value regex matchnewest- the shortest-uptime port among those matching other termsoldest- the longest-uptime port among those matching other terms
Some examples:
Pico Serial- the wordspicoANDserialmust each appear somewhere (any case, as a whole word)RP2040 DF625*- the wordrp2040AND a word starting withdf625subsys="usb"-subsystemmust equalusb(any case but whole value)Adafruit serial~/^DF625/-adafruitmust appear somewhere (any case), andserial_numbermust begin withDF625(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 otherpoliteusers. 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-politeuser. 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ok_serial-0.3.tar.gz.
File metadata
- Download URL: ok_serial-0.3.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a6637922c0f6e2728aa20a3c779c1f0c945f28c138d6ba376b9a4865153e025
|
|
| MD5 |
83cfe4de7e6742f29af6ab65c84b4e9d
|
|
| BLAKE2b-256 |
7f8fdf0e2cc0595c18ce93bdaa3d40abbfb7e729cc43cff9ba7995d17a596ea4
|
File details
Details for the file ok_serial-0.3-py3-none-any.whl.
File metadata
- Download URL: ok_serial-0.3-py3-none-any.whl
- Upload date:
- Size: 22.9 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
307d68b21a62d01831dd77a83e915974beddacb88a311d5d5e3c941461d99835
|
|
| MD5 |
fec5c5122e70d1c28c26c8870d7d095c
|
|
| BLAKE2b-256 |
de718c4cefb5e9bc540bccc9a462c70cd2ee70a939aec908dbe7025dee562e0c
|