Of course! A "Python serial tool" can mean a few different things, from a simple script to send/receive data to a full-fledged GUI application. I'll guide you through the most common and useful approaches.
The Core Library: pyserial
The go-to library for serial communication in Python is pyserial. It's a powerful, cross-platform library that wraps the operating system's serial port functionality.
Installation
First, you need to install it. Open your terminal or command prompt and run:
pip install pyserial
Basic Script: Sending and Receiving Data
This is the simplest form of a "tool." It's a script that you can run from the command line to interact with a serial device.
Let's create a script named serial_test.py.
# serial_test.py
import serial
import serial.tools.list_ports
def find_serial_port():
"""Lists available serial ports and returns the first one."""
ports = serial.tools.list_ports.comports()
if not ports:
print("No serial ports found.")
return None
print("Available serial ports:")
for i, port in enumerate(ports):
print(f"{i}: {port.device} - {port.description}")
# For simplicity, we'll just return the first port.
# In a real tool, you might let the user choose.
return ports[0].device
def main():
# --- Configuration ---
# Find a port
port_name = find_serial_port()
if not port_name:
return
# Baud rate must match the device's setting
BAUD_RATE = 9600
# Timeout in seconds: 0 means non-blocking (read returns immediately),
# a positive value means wait for that many seconds for data.
TIMEOUT = 1
print(f"\nConnecting to {port_name} at {BAUD_RATE} baud...")
try:
# The 'with' statement ensures the port is closed automatically
with serial.Serial(port_name, BAUD_RATE, timeout=TIMEOUT) as ser:
print(f"Connection successful. Port is open: {ser.is_open}")
# --- Sending Data ---
print("\nSending: 'Hello from Python!'")
# .encode() converts the string to bytes, which is required for serial
ser.write(b'Hello from Python!\n')
print("Data sent.")
# --- Receiving Data ---
print("\nWaiting for data (with a 1-second timeout)...")
# ser.in_waiting checks how many bytes are in the buffer
if ser.in_waiting > 0:
# ser.read() reads the specified number of bytes
# ser.readline() reads until a newline character is found
received_data = ser.readline().decode('utf-8').strip()
print(f"Received: '{received_data}'")
else:
print("No data received within the timeout period.")
except serial.SerialException as e:
print(f"Error: Could not open or use serial port {port_name}.")
print(f"Details: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
main()
How to Use It:
- Save the code as
serial_test.py. - Connect your serial device (e.g., an Arduino, a USB-to-TTL converter, a sensor).
- Run the script from your terminal:
python serial_test.py - The script will list available ports and attempt to connect to the first one. Make sure your device is sending data back, or modify the script to send a command that triggers a response.
Creating a Simple Command-Line Interface (CLI) Tool
A more advanced script can take arguments from the command line, making it a much more flexible tool. We'll use Python's built-in argparse module.
Let's create a script named serial_cli.py.
# serial_cli.py
import serial
import serial.tools.list_ports
import argparse
import time
def main():
parser = argparse.ArgumentParser(description="A simple Python serial communication tool.")
# Add arguments for port, baud rate, and data to send
parser.add_argument('--port', '-p', type=str, help="Serial port (e.g., COM3 or /dev/ttyUSB0)")
parser.add_argument('--baud', '-b', type=int, default=9600, help="Baud rate (default: 9600)")
parser.add_argument('--send', '-s', type=str, help="Data string to send")
parser.add_argument('--receive', '-r', action='store_true', help="Read data from the serial port")
parser.add_argument('--timeout', '-t', type=float, default=1.0, help="Read timeout in seconds (default: 1.0)")
args = parser.parse_args()
# If port is not specified, list and ask for one
if not args.port:
ports = serial.tools.list_ports.comports()
if not ports:
print("Error: No serial ports found.")
return
print("Available serial ports:")
for i, port in enumerate(ports):
print(f"{i}: {port.device}")
port_index = int(input("Enter the number of the port to use: "))
port_name = ports[port_index].device
else:
port_name = args.port
try:
with serial.Serial(port_name, args.baud, timeout=args.timeout) as ser:
print(f"Connected to {ser.name} at {ser.baudrate} baud.")
if args.send:
print(f"Sending: '{args.send}'")
ser.write(args.send.encode('utf-8') + b'\n') # Add newline for many devices
if args.receive:
print("Waiting for data... (Press Ctrl+C to stop)")
while True:
if ser.in_waiting > 0:
data = ser.readline().decode('utf-8').strip()
if data:
print(f"Received: {data}")
time.sleep(0.1) # Small delay to prevent busy-waiting
except serial.SerialException as e:
print(f"Error: {e}")
except KeyboardInterrupt:
print("\nExiting.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
main()
How to Use It:
This tool is much more versatile. You can combine its arguments in many ways.
-
List ports and connect to the 3rd one:
python serial_cli.py --port /dev/ttyUSB0
(Or use the interactive prompt if you omit
--port). -
Send a command and exit:
python serial_cli.py -p COM3 -b 115200 -s "GET_STATUS"
-
Open a port and continuously monitor for incoming data (like a terminal):
python serial_cli.py -p COM4 -r
Press
Ctrl+Cto stop.
Creating a GUI Tool (Advanced)
For a full graphical "tool," you'll need a GUI library. Tkinter is built into Python, making it a great choice for simple tools. PyQt or PySide are more powerful for complex applications.
Here's a basic example using Tkinter. It will have a text area to see received data and an entry box to send data.
Prerequisites: Tkinter usually comes pre-installed with Python. If not, you may need to install it depending on your system.
# serial_gui_tool.py
import tkinter as tk
from tkinter import ttk, scrolledtext
import serial
import serial.tools.list_ports
import threading
class SerialGUI:
def __init__(self, root):
self.root = root
self.root.title("Python Serial Tool")
self.root.geometry("600x400")
self.serial_port = None
self.running = False
self.create_widgets()
self.populate_ports()
def create_widgets(self):
# --- Port Selection Frame ---
port_frame = ttk.Frame(self.root, padding="10")
port_frame.pack(fill=tk.X)
ttk.Label(port_frame, text="Port:").grid(row=0, column=0, sticky=tk.W)
self.port_var = tk.StringVar()
self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, state="readonly")
self.port_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
ttk.Label(port_frame, text="Baud Rate:").grid(row=0, column=2, sticky=tk.W, padx=(20, 0))
self.baud_var = tk.StringVar(value="9600")
self.baud_combo = ttk.Combobox(port_frame, textvariable=self.baud_var, state="readonly", width=10)
self.baud_combo['values'] = ('9600', '19200', '38400', '57600', '115200')
self.baud_combo.grid(row=0, column=3, sticky=tk.W)
self.connect_btn = ttk.Button(port_frame, text="Connect", command=self.toggle_connection)
self.connect_btn.grid(row=0, column=4, padx=10)
port_frame.columnconfigure(1, weight=1)
# --- Communication Frame ---
comm_frame = ttk.Frame(self.root, padding="10")
comm_frame.pack(fill=tk.BOTH, expand=True)
# --- Receive Area ---
ttk.Label(comm_frame, text="Received Data:").pack(anchor=tk.W)
self.receive_area = scrolledtext.ScrolledText(comm_frame, wrap=tk.WORD, state='disabled')
self.receive_area.pack(fill=tk.BOTH, expand=True)
# --- Send Frame ---
send_frame = ttk.Frame(comm_frame)
send_frame.pack(fill=tk.X, pady=(10, 0))
self.send_entry = ttk.Entry(send_frame)
self.send_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.send_entry.bind('<Return>', self.send_data) # Send on Enter key
self.send_btn = ttk.Button(send_frame, text="Send", command=self.send_data)
self.send_btn.pack(side=tk.RIGHT, padx=(5, 0))
def populate_ports(self):
ports = [port.device for port in serial.tools.list_ports.comports()]
if ports:
self.port_combo['values'] = ports
self.port_combo.current(0)
else:
self.port_combo['values'] = ['No ports found']
self.port_combo.current(0)
def toggle_connection(self):
if self.serial_port and self.serial_port.is_open:
self.disconnect()
else:
self.connect()
def connect(self):
try:
port = self.port_var.get()
baud = int(self.baud_var.get())
self.serial_port = serial.Serial(port, baud, timeout=1)
self.connect_btn.config(text="Disconnect")
self.running = True
# Start a separate thread for reading data
self.read_thread = threading.Thread(target=self.read_serial, daemon=True)
self.read_thread.start()
self.log_message(f"Connected to {port} at {baud} baud.")
except Exception as e:
self.log_message(f"Connection Error: {e}")
def disconnect(self):
if self.serial_port and self.serial_port.is_open:
self.running = False
self.serial_port.close()
self.connect_btn.config(text="Connect")
self.log_message("Disconnected.")
def read_serial(self):
"""Continuously reads from the serial port in a separate thread."""
while self.running and self.serial_port and self.serial_port.is_open:
if self.serial_port.in_waiting > 0:
data = self.serial_port.readline().decode('utf-8', errors='replace').strip()
if data:
# Use .after to safely update the GUI from another thread
self.root.after(0, lambda d=data: self.log_message(d))
time.sleep(0.1) # Prevent busy-waiting
def send_data(self, event=None):
data_to_send = self.send_entry.get()
if data_to_send and self.serial_port and self.serial_port.is_open:
try:
self.serial_port.write(data_to_send.encode('utf-8') + b'\n')
self.log_message(f"Sent: {data_to_send}")
self.send_entry.delete(0, tk.END)
except Exception as e:
self.log_message(f"Send Error: {e}")
def log_message(self, message):
"""Appends a message to the receive area."""
self.receive_area.config(state='normal')
self.receive_area.insert(tk.END, message + '\n')
self.receive_area.see(tk.END) # Auto-scroll to the end
self.receive_area.config(state='disabled')
def on_closing(self):
self.disconnect()
self.root.destroy()
# You need to import time for the read_thread sleep
import time
if __name__ == "__main__":
root = tk.Tk()
app = SerialGUI(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()
How to Use It:
- Save the code as
serial_gui_tool.py. - Run it:
python serial_gui_tool.py. - A window will appear. Select your COM port and baud rate from the dropdowns.
- Click "Connect".
- You can now type in the "Send" box and press Enter to send data. All received data will appear in the "Received Data" area automatically.
Summary of Tools
| Tool Type | Best For | Pros | Cons |
|---|---|---|---|
| Basic Script | Quick tests, simple automation. | Simple, no dependencies beyond pyserial. |
Not flexible, hard-coded parameters. |
| CLI Tool | Powerful scripting, automation, server tasks. | Flexible (command-line args), scriptable, lightweight. | No visual feedback, command-line only. |
| GUI Tool | Interactive use, debugging, user-friendly applications. | Visual, easy to use for non-technical users. | More complex code, requires a GUI library. |
Choose the one that best fits your needs! For most general-purpose "tinkering," the CLI tool is the most powerful and versatile.
