Skip to main content

Modern Python serial port library — pyserial replacement

Project description

wireio

PyPI Python License: MIT

Modern Python serial port library — a drop-in replacement for pyserial.

  • Python 3.10+ with full type annotations (PEP 561)
  • Zero dependencies — stdlib only
  • Cross-platform — Linux, macOS, Windows
  • Built-in asyncAsyncSerial with native asyncio support
  • Port discoverylist_ports() enumerates available serial devices
  • CLI toolswireio-miniterm and wireio-list-ports

Why wireio?

pyserial is used by 100,000+ projects but has had no release since November 2020. The single maintainer is unreachable, 275+ issues are open, and the library has no type hints, no async support, and still targets Python 2.7.

wireio is a modern replacement:

pyserial wireio
Last release Nov 2020 Active
Dependencies None None
Python support 2.7, 3.4–3.8 3.10+ (including 3.13+)
Type hints No Full PEP 561
Async support Separate package Built-in AsyncSerial
API style Old-style class Dataclass config + enums

Installation

pip install wireio

Quick Start

Synchronous

from wireio import Serial

with Serial("/dev/ttyUSB0", baudrate=115200) as port:
    port.write(b"AT\r\n")
    response = port.read_until(b"\r\n")
    print(response)

Asynchronous

import asyncio
from wireio import AsyncSerial

async def main():
    async with AsyncSerial("/dev/ttyUSB0", baudrate=9600) as port:
        await port.write(b"hello")
        data = await port.read(100)
        print(data)

asyncio.run(main())

Port Discovery

from wireio import list_ports

for port in list_ports():
    print(f"{port.device}{port.description}")

Dataclass Config

from wireio import Serial, SerialConfig, Parity, StopBits

config = SerialConfig(
    baudrate=115200,
    parity=Parity.EVEN,
    stopbits=StopBits.TWO,
    timeout=1.0,
)
with Serial("/dev/ttyS0", config=config) as port:
    port.write(b"data")

API Reference

Serial(port, baudrate=9600, **kwargs)

Main serial port class. Platform-specific backend is selected automatically (POSIX on Linux/macOS, Win32 on Windows).

Constructor parameters:

  • port — device path (/dev/ttyUSB0, COM3)
  • baudrate — baud rate (default: 9600)
  • bytesizeByteSize.FIVE through ByteSize.EIGHT (default: EIGHT)
  • parityParity.NONE, EVEN, ODD, MARK, SPACE (default: NONE)
  • stopbitsStopBits.ONE, ONE_POINT_FIVE, TWO (default: ONE)
  • timeout — read timeout in seconds (None = blocking, 0 = non-blocking, default: None)
  • write_timeout — write timeout in seconds (None = blocking, default: None)
  • flow_controlFlowControl.NONE, HARDWARE, SOFTWARE (default: NONE)
  • xonxoff — enable XON/XOFF software flow control (default: False)
  • rtscts — enable RTS/CTS hardware flow control (default: False)
  • dsrdtr — enable DSR/DTR hardware flow control (default: False)
  • inter_byte_timeout — timeout between consecutive bytes in seconds (None = disabled, default: None)
  • configSerialConfig object (overrides individual params when provided)

Methods:

  • open() / close() — open or close the port
  • read(size=1) -> bytes — read up to size bytes; returns fewer if timeout expires
  • write(data) -> int — write bytes, returns count written
  • flush() — wait until all data transmitted
  • read_until(delimiter=b"\n", size=0) -> bytes — read until delimiter found; size=0 means no limit
  • read_line() -> bytes — read until \n
  • read_exactly(size) -> bytes — read exactly size bytes, blocking until all received
  • configure(config) — apply new SerialConfig to an open port

Properties:

  • is_open — whether the port is open
  • in_waiting — bytes available in input buffer
  • port, baudrate, bytesize, parity, stopbits, timeout, config

Supports the context manager protocol (with Serial(...) as port:).

AsyncSerial(port, baudrate=9600, **kwargs)

Async wrapper around Serial. Runs blocking I/O in a thread executor via asyncio.run_in_executor. Same constructor parameters as Serial, plus:

  • loop (asyncio.AbstractEventLoop | None) — event loop to use (default: current running loop)

All I/O methods are async and mirror the Serial interface:

async with AsyncSerial("/dev/ttyUSB0", baudrate=9600) as port:
    await port.write(b"hello")
    data = await port.read(100)
    line = await port.read_line()
    exact = await port.read_exactly(4)
    until = await port.read_until(b"\r\n")
    await port.flush()
    await port.configure(SerialConfig(baudrate=115200))

Async methods: open, close, read, write, flush, read_until, read_line, read_exactly, configure

Properties (sync): port, is_open, baudrate, config

Supports the async context manager protocol (async with AsyncSerial(...) as port:).

SerialConfig

Dataclass holding all serial port settings. Pass as the config= argument to Serial or AsyncSerial, or use port.configure(config) to reconfigure an open port.

from wireio import SerialConfig, Parity, StopBits, ByteSize, FlowControl

config = SerialConfig(
    baudrate=115200,
    bytesize=ByteSize.EIGHT,
    parity=Parity.NONE,
    stopbits=StopBits.ONE,
    timeout=1.0,
    write_timeout=None,
    flow_control=FlowControl.NONE,
    inter_byte_timeout=None,
)

Fields (all optional, defaults match Serial constructor):

  • baudrate: int — default 9600
  • bytesize: ByteSize — default ByteSize.EIGHT
  • parity: Parity — default Parity.NONE
  • stopbits: StopBits — default StopBits.ONE
  • timeout: float | None — default None
  • write_timeout: float | None — default None
  • flow_control: FlowControl — default FlowControl.NONE
  • xonxoff: bool — default False
  • rtscts: bool — default False
  • dsrdtr: bool — default False
  • inter_byte_timeout: float | None — default None

SerialConfig.validate() raises ConfigError for invalid values (negative baudrate, negative timeouts, etc.).

list_ports() -> list[PortInfo]

Enumerate available serial ports. Returns PortInfo objects with:

  • device — device path (e.g. /dev/ttyUSB0, COM3)
  • name — short port name
  • description — human-readable description
  • hwid — hardware ID string
  • vid, pid — USB vendor/product IDs (or None)
  • serial_number, manufacturer, product — USB metadata (or None)

Enums

  • ParityNONE, EVEN, ODD, MARK, SPACE
  • StopBitsONE, ONE_POINT_FIVE, TWO
  • ByteSizeFIVE, SIX, SEVEN, EIGHT
  • FlowControlNONE, HARDWARE, SOFTWARE

Exceptions

All exceptions inherit from SerialError:

  • SerialError — base exception for all serial port errors
  • PortNotFoundError — port not found or permission denied
  • ConfigError — invalid configuration value
  • SerialTimeoutError — operation timed out

CLI Tools

wireio-list-ports

Print a table of available serial ports:

wireio-list-ports
# or
python -m wireio.tools.list_ports

Output example:

DEVICE        DESCRIPTION          HWID
-------------------------------------------------
/dev/ttyUSB0  USB Serial Device    USB VID:PID=0403:6001
/dev/ttyS0    ttyS0                n/a

wireio-miniterm

Interactive serial terminal:

wireio-miniterm /dev/ttyUSB0 115200
# or
python -m wireio.tools.miniterm /dev/ttyUSB0 115200 --echo --eol crlf

Options:

  • port — serial port device path (required)
  • baudrate — baud rate (default: 9600)
  • --encoding — character encoding for display and input (default: utf-8)
  • --echo — enable local echo of typed input
  • --eol {cr,lf,crlf} — line ending appended to transmitted lines (default: crlf)

Press Ctrl+C to exit.

Migration from pyserial

wireio is designed as a drop-in replacement for pyserial. Key differences:

pyserial wireio
import serial from wireio import Serial
serial.Serial(...) Serial(...)
serial.tools.list_ports.comports() wireio.list_ports()
serial.SerialException wireio.SerialError
serial.serialutil.SerialTimeoutException wireio.SerialTimeoutError
Separate pyserial-asyncio package Built-in AsyncSerial
No type hints Full PEP 561 type hints
Python 2.7+ Python 3.10+

Common patterns

# pyserial
import serial
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
ser.write(b'hello')
data = ser.read(100)
ser.close()

# wireio (same pattern works)
from wireio import Serial
ser = Serial('/dev/ttyUSB0', 9600, timeout=1)
ser.open()
ser.write(b'hello')
data = ser.read(100)
ser.close()

# wireio (preferred — context manager)
with Serial('/dev/ttyUSB0', baudrate=9600, timeout=1) as ser:
    ser.write(b'hello')
    data = ser.read(100)

License

MIT

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

wireio-0.1.0.tar.gz (20.5 kB view details)

Uploaded Source

Built Distribution

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

wireio-0.1.0-py3-none-any.whl (19.7 kB view details)

Uploaded Python 3

File details

Details for the file wireio-0.1.0.tar.gz.

File metadata

  • Download URL: wireio-0.1.0.tar.gz
  • Upload date:
  • Size: 20.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for wireio-0.1.0.tar.gz
Algorithm Hash digest
SHA256 97d6d8b0f34beac24c5b0d9f7d07bcfe5f4d9208a648e62f15a2dbf9558bb402
MD5 b4877053bd9877350a8db5989766a547
BLAKE2b-256 c77b5f2d14825d98f2eccda755fd2dbe6eecd335694d53a84729da2ee09cea65

See more details on using hashes here.

File details

Details for the file wireio-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: wireio-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for wireio-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f345507080d7a7f5ee48ffb62aa8b50909f0a392aa07e4b28dfc3621424730eb
MD5 a90fc68a60f7487e326b0385102594be
BLAKE2b-256 21fda6cbbc6100f541c7c7a0840b40ac0f7f8f39f45945507476034f32b2813d

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