Skip to main content

Serial port library (PySerial wrapper) with more features

Project description

ok-serial for Python   🔌〡〇〡〇〡🐍

Python serial I/O library based on PySerial with improved discovery and interface semantics.

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

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 problems keep coming up:

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

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

  • Buffers in PySerial are small and unspecified; overruns cause lost data and/or unexpected blocking.

  • 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 an improved interface:

  • Ports are referenced by port attribute match expressions with wildcard support, eg. *RP2040* or 2e43:0226 or manufacturer="Arduino". (You can also use device path if desired.)

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

  • I/O buffers are limited only by system memory; writes never block. (Blocking drain operations are available to wait for output completion.)

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

  • Offers a SerialTracker helper to wait for a device of interest to appear and rescan as needed after disconnection, to gracefully handle pluggable devices.

Installation

pip install ok-serial

(or uv add ok-serial, etc.)

Identifying serial ports

Device names like /dev/ttyUSB3 or COM4 aren't very useful for USB serial ports, so ok-serial identifies ports by attributes like device manufacturer (eg. Adafruit), product name (eg. CP2102 USB to UART Bridge Controller), USB vendor/product ID (eg. 239a:812d), serial number, etc..

To see the attributes that can be used, install ok-serial and run okserial --verbose to list available ports in this format:

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'

Attribute definitions are inherited from platform and can vary, but device, name, description, hwid, and (for USB) vid, pid, serial_number, location, manufacturer, product and interface are semi-standardized.

To identify ports to use, ok-serial uses port match expression strings, which contain space-separated search terms:

  • word - regular words: case INsensitive match in any attribute value, respecting word boundaries
  • wild*word? - words may contain * and ? wildcards
  • spaces\ and\ st\*rs - words may contain special characters if backslash-escaped
  • "spaces and st*rs" - C/JS/Python-style quoted strings: case SENSITIVE exact phrase match in any attribute value, respecting word boundaries
  • attr:"quoted text" - quoted phrases can be scoped to specific attributes (attr name can be truncated)
  • attr="exact match" - with =, quoted strings must match the entire value
  • ~/regexp/ - regex match (Python re) across all attributes
  • attr~/regexp/ - regex match can be scoped to specific attributes

Some examples:

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

You can pass a match expression to okserial and set $OK_LOGGING_LEVEL=debug to see parsing results:

% OK_LOGGING_LEVEL=debug okserial -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'
   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'

Sharing modes

When opening a port, ok-serial offers a choice of four sharing modes:

  • oblivious - no locking is done and advisory locks are ignored. If multiple programs open the port, they will all send and receive data to the same device. This mode is not recommended.
  • polite - locking is checked at open, and if the port is in use the open fails. Once opened, no locks are held except for a shared lock to discourage other polite users from opening the port. 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 mode) - locking is checked at open, and if the port is in use the open fails. Once opened, several means of locking are employed to prevent or discourage others from opening the port.
  • stomp (use with care!) - locking is checked at open, and if the port is in use, the program using the port is killed if possible. The port is opened regardless and all available locks are taken.

The implementation of these modes is limited by OS capabilities, process permissions, and the 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.2.tar.gz (13.2 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.2-py3-none-any.whl (16.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ok_serial-0.2.tar.gz
  • Upload date:
  • Size: 13.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.10

File hashes

Hashes for ok_serial-0.2.tar.gz
Algorithm Hash digest
SHA256 f197d172e3edd1a187053946bc70dc1c832e27e1474ca0249c385a250b9f43cb
MD5 3a0d3a4cfef6682834e544096b9577c6
BLAKE2b-256 426f1d9522892a2e4b55e2f017c2f374582b03df8f242585c72cd3a2f5aa69f8

See more details on using hashes here.

File details

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

File metadata

  • Download URL: ok_serial-0.2-py3-none-any.whl
  • Upload date:
  • Size: 16.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.10

File hashes

Hashes for ok_serial-0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 961b40686a3a3644b441d6c87839f80286f3837759d7d058317449a34a5a71dc
MD5 ec1f3806b510fcf70d8847989ee0665b
BLAKE2b-256 b666057594d1eda397cc8261968977ad1992fb88180cc56a276b07f5e85cb9ca

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