Serialx is a no-compromise serial communication library for Python targeting common platforms such as Linux (POSIX), macOS, and Windows. It provides both synchronous and native asynchronous APIs for all platforms.
For more information, visit serialx's documentation: https://puddly.github.io/serialx/
pip install serialxFor drop-in import compatibility (serial, serial_asyncio, serial_asyncio_fast) in
environments where existing code cannot be migrated:
pip install serialx-compatSerialx features a familiar synchronous API:
importserialxwithserialx.serial_for_url("/dev/serial/by-id/port", baudrate=115200) asserial:
data=serial.readexactly(5)
serial.write(b"test")
serial.set_modem_pins(rts=True, dtr=True)
pins=serial.get_modem_pins()
assertpins.rtsisserialx.PinState.HIGHassertpins.dtrisserialx.PinState.HIGHAn async equivalent of the synchronous API:
importasyncioimportserialxasyncdefmain():
asyncwithserialx.async_serial_for_url(
"/dev/serial/by-id/port", baudrate=115200,
) asserial:
data=awaitserial.readexactly(5)
awaitserial.write(b"test")
awaitserial.set_modem_pins(rts=True, dtr=True)
pins=awaitserial.get_modem_pins()
assertpins.rtsisserialx.PinState.HIGHA (StreamReader, StreamWriter) pair is also available for code already wired up to
the asyncio streams API:
importasyncioimportserialxasyncdefmain():
reader, writer=awaitserialx.open_serial_connection(
"/dev/serial/by-id/port", baudrate=115200,
)
try:
data=awaitreader.readexactly(5)
writer.write(b"test")
awaitwriter.drain()
finally:
writer.close()
awaitwriter.wait_closed()And a low-level asynchronous serial transport for protocol-style consumers:
importasyncioimportserialxasyncdefmain():
loop=asyncio.get_running_loop()
protocol=YourProtocol()
transport, protocol=awaitserialx.create_serial_connection(
loop,
lambda: protocol,
url="/dev/serial/by-id/port",
baudrate=115200,
)
awaittransport.set_modem_pins(rts=True, dtr=True)Serialx can communicate with serial devices exposed by ESPHome.
It can either create the API instance directly, for simplicity:
fromserialximportopen_serial_connectionreader, writer=awaitopen_serial_connection(
url="esphome://192.168.1.42:6053/?port_name=Zigbee&key=...",
baudrate=115200,
)Or reuse an existing API instance, for efficiency:
fromaioesphomeapiimportAPIClientfromserialximportopen_serial_connectionfromserialx.platforms.serial_esphomeimportESPHomeSerialTransport# An external API instanceapi=APIClient(address="192.168.1.42", port=6053, key="...", password=None)
awaitapi.connect(login=True)
reader, writer=awaitopen_serial_connection(
url=None,
transport_cls=ESPHomeSerialTransport,
api=api,
port_name="Zigbee",
baudrate=115200,
)All development dependencies are listed in pyproject.toml. To install them, use:
uv pip install '.[dev]'On macOS and Windows, a Rust toolchain is required to build the native serial port enumeration extension. Install Rust via rustup.
Set up pre-commit hooks with pre-commit install. Your code will then be type checked
and auto-formatted when you run git commit. You can do this on-demand with
pre-commit run.
Serialx relies on automated testing. CI runs tests using both socat virtual PTYs
(Linux/macOS) and socket-based serial pairs. To also test with physical adapter pairs,
pass CLI flags to pytest:
pytest --adapter-pair=/dev/serial/by-id/left1:/dev/serial/by-id/right1 \
--adapter-pair=/dev/serial/by-id/left2:/dev/serial/by-id/right2By default, tests run in parallel. You can disable this by passing -n 0 to pytest.