Python Serial Read with pySerial: Bytes, Lines, and Timeouts

Quick answer: Read serial data in Python with pySerial by opening a configured Serial port, choosing a timeout, reading bytes with read() or lines with readline(), decoding only when appropriate, and closing the port reliably.

Python serial read infographic showing port configuration, byte stream, framing, readline timeout, decoding, buffering, and cleanup
Reliable serial reads define the frame boundary before turning bytes into text or application data.

PySerial lets Python read and write data through serial ports such as USB-to-serial adapters, microcontrollers, sensors, barcode scanners, and lab devices. The key is to configure the correct port, baud rate, timeout, and byte decoding.

The primary references are the PySerial API documentation and the PySerial short introduction. Hardware references include the Arduino hardware docs and the Arduino getting started guide.

Serial data arrives as bytes. Decode it only after you know the device’s encoding and message boundaries. Many devices send newline-terminated records, which makes readline() a good first choice.

Before writing code, confirm three details from the device documentation: the port name, the baud rate, and the message format. A correct Python loop cannot fix a mismatched baud rate or a device that sends binary packets while the code expects text lines.

Also close other serial monitors before running your script. Many operating systems allow only one program to open the same serial port at a time.

Open A Serial Port

Use serial.Serial() with an explicit timeout so reads do not block forever.

import serial

port = "/dev/ttyUSB0"
baud_rate = 9600

with serial.Serial(port, baud_rate, timeout=1) as ser:
    print(ser.name)
    print(ser.baudrate)
    print(ser.timeout)

On Windows, the port may look like COM3. On macOS and Linux, it often looks like /dev/tty.usbserial-... or /dev/ttyUSB0. PySerial handles UART and COM-port communication; for SPI peripherals on Windows, SPIdev for Windows: Python Alternatives and Setup explains USB bridges, microcontroller relays, and vendor SDKs.

The baud rate must match the device configuration. Common values include 9600, 19200, 57600, and 115200.

If the script cannot open the port, check cable quality, driver installation, user permissions, and whether the device path changed after reconnecting the hardware.

Read One Line

If the device sends newline-terminated records, readline() reads until a newline or timeout.

import serial

with serial.Serial("/dev/ttyUSB0", 9600, timeout=1) as ser:
    raw_line = ser.readline()
    text = raw_line.decode("utf-8", errors="replace").strip()

    if text:
        print(text)
    else:
        print("no data before timeout")

The result from readline() is bytes. Decode it explicitly and decide how to handle invalid bytes.

Use strip() when you want to remove trailing newline characters from display output.

Use errors="replace" when the output is for diagnostics. For protocols where every byte matters, keep the bytes and parse the protocol directly instead of decoding too early.

Read Available Bytes

in_waiting reports how many bytes are waiting in the input buffer. It replaces older examples that use inWaiting().

import serial

with serial.Serial("/dev/ttyUSB0", 115200, timeout=0.5) as ser:
    waiting = ser.in_waiting
    if waiting:
        data = ser.read(waiting)
        print(data)
    else:
        print("buffer is empty")

This pattern is useful for polling loops and binary protocols where messages are not line-based.

Do not spin in a tight loop without sleeping or using a timeout. That can waste CPU while waiting for a device.

For GUI programs or long-running services, read serial data in a worker thread or event loop so the user interface does not freeze.

Write A Command Then Read

Many devices respond after receiving a command. Send bytes with write(), then read a response.

import serial

command = "MEASURE\n".encode("ascii")

with serial.Serial("/dev/ttyUSB0", 9600, timeout=2) as ser:
    ser.write(command)
    ser.flush()

    response = ser.readline().decode("ascii", errors="replace").strip()
    print(response)

Check the device manual for command format, line endings, baud rate, and expected response timing.

Some devices need a short delay after opening the port before they are ready to receive commands.

Many Arduino-style boards reset when the serial port opens. If the first response is missing, wait briefly after opening the connection and discard startup text before sending a command.

List Available Ports

PySerial can list connected serial ports. This helps users choose the right device path.

from serial.tools import list_ports

ports = list(list_ports.comports())

for port in ports:
    print(port.device)
    print(port.description)

Descriptions are helpful, but not guaranteed to be unique. For production tools, let the user choose the port in a config file or command-line option.

If no port appears, check drivers, cables, permissions, and whether another program already opened the device.

When several devices are connected, print the hardware ID and description during setup. That makes support logs more useful than a bare list of port names.

Handle Serial Exceptions

Wrap serial setup in exception handling so missing ports and permission errors produce clear messages.

import serial

try:
    with serial.Serial("/dev/ttyUSB0", 9600, timeout=1) as ser:
        line = ser.readline()
        print(line.decode("utf-8", errors="replace"))
except serial.SerialException as exc:
    print(f"Serial port error: {exc}")

This catches common connection problems without hiding parsing errors that happen later in your code.

The practical workflow is to list ports, open the selected port with a timeout, read bytes, decode them deliberately, and log device errors clearly.

For reliable tools, make the port and baud rate configurable. Hardcoded examples are fine for tutorials, but deployed scripts should not assume every machine uses the same serial device name.

If reads return empty bytes repeatedly, verify that the device is actually transmitting, that line endings match your read method, and that hardware flow control settings match the device requirements.

Configure A Port And Read Bytes

Serial data arrives as bytes. Open the port with the correct device name and baud rate, set a timeout so a read cannot wait forever, and decide whether the protocol is framed by a fixed length, delimiter, or message header. Do not decode arbitrary chunks as text unless the protocol guarantees an encoding.

import serial

with serial.Serial("/dev/ttyUSB0", 9600, timeout=1) as port:
    packet = port.read(8)
    if packet:
        print(packet.hex())
    else:
        print("No bytes received before timeout")

Read Lines Only When The Protocol Has Newlines

readline() waits for a newline or until the timeout expires. It is convenient for line-oriented devices, but a missing terminator can produce partial data. Use packet.decode("utf-8", errors="replace") only after you have a complete text frame and have chosen an error policy.

Handle Buffers, Timeouts, And Shutdown

Check in_waiting when polling is part of the design, but do not assume it is a complete message boundary. Clear buffers only when the protocol permits it, log timeout and framing failures separately, and close the port with a context manager or explicit cleanup. Hardware tests should include disconnects, partial packets, and unexpected bytes.

Frequently Asked Questions

How do I read serial data in Python?

Use pySerial to open a configured Serial port, choose a timeout, then read bytes with read() or line-oriented data with readline().

Why does pySerial readline() hang?

readline() waits for a newline or the configured timeout, so a device that sends no terminator can make it wait until the timeout expires.

Should I decode serial data immediately?

Read a complete protocol frame as bytes first, then decode only when the device protocol guarantees the encoding and error policy.

How do I close a serial port safely?

Use a Serial context manager or explicit close() cleanup, and test disconnects, partial packets, timeouts, and unexpected bytes.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted