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.

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.