Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jakelevi1996/79f42f47f3885765b80bd6af62649786 to your computer and use it in GitHub Desktop.
Save jakelevi1996/79f42f47f3885765b80bd6af62649786 to your computer and use it in GitHub Desktop.
Automatically opening serial connections to COM ports

Automatically opening serial connections to COM ports

Below is a small Python script (putty_com.py) which can automatically open serial connections to COM ports using PuTTY, unless they have been specified as being occupied. The docstring contains instructions for how to further automate running this script, EG by adding a shortcut to the start menu. The script also has an argparse command-line interface, as explained in the docstring. This script has been tested using Python version 3.7.6, Pyserial version 3.4, and PuTTY version 0.73.

Note that an alternative to opening a PuTTY window in a subprocess is to read from the serial connection directly in Python using pySerial. The snippet below demonstrates a very basic yet functional example of how to read from a serial device (a more complete script containing some error handling, command line arguments, logging to file, and timestamps, is included in the serial_log.py script below):

serial_device = serial.Serial("COM28", 115200, timeout=1)
while True:
    print((serial_device.read_until(b"\n")).decode("utf-8"), end="\n***\n")

pySerial can also be used to write bytes through the serial connection; for example, the snippet below demonstrates how to write the integer 90 to COM3:

serial_device = serial.Serial("COM3")
serial_device.write(bytes([90]))
"""
Open Putty sessions with all valid COM ports, except those in the
occupied_com_ports set, which are presumed to be used by the PC for other
purposes.
Running this script can be automated further by creating a shortcut in Windows
explorer, under "Type the location of the item" entering `C:/Python37/python.exe
"C:/Users/Jake/Documents/Python/putty_com.py"` or equivalent, and under "Type a
name for this shortcut" entering a descriptive name, EG "Putty open COM ports".
This shortcut can then be pinned to the start menu for easy access from
anywhere.
This script has a command-line interface, which allows min_com_port,
max_com_port, occupied_com_ports, and baud_rate to be specified from the
command-line. For example, the following command will only open serial
connections with COM ports 3, 4, 5, 6, 7, and 8:
python ./putty_com.py --min_com_port 3 --max_com_port 8
The following command will not open serial connections to COM ports 4 or 6, and
all COM ports will be opened with a baud rate of 9600 (instead of the default
which is 115200):
python ./putty_com.py --occupied_com_ports 4,6 --baud_rate 9600
For more, use the --help flag, EG:
python ./putty_com.py --help
"""
import os
import subprocess
from argparse import ArgumentParser
import serial
def main(min_com_port, max_com_port, occupied_com_ports, baud_rate):
# Iterate through COM ports
for i in range(min_com_port, max_com_port + 1):
# Check if this port number is occupied
if i in occupied_com_ports:
# This COM port is occupied, so skip
continue
# Create name of COM port
com_name = "COM{}".format(i)
# Try to open and close a serial connection
try:
s = serial.Serial(com_name)
s.close()
# The serial connection was opened successfully, so start Putty
subprocess.Popen("putty -serial {} -sercfg {}".format(
com_name,
baud_rate
))
except serial.serialutil.SerialException:
# This COM port is unavailable, so skip
pass
if __name__ == "__main__":
# Define CLI using ArgumentParser
help_str = (
"Script for automatically opening serial connections to COM ports."
)
parser = ArgumentParser(description=help_str)
parser.add_argument(
"--min_com_port",
help="Minimum COM port to open. Default is 0",
default=0,
type=int
)
parser.add_argument(
"--max_com_port",
help="Maximum COM port to open. Default is 20",
default=20,
type=int
)
parser.add_argument(
"--occupied_com_ports",
help="Comma-separated list of occupied COM ports, EG 1,2,3",
default="1",
type=str
)
parser.add_argument(
"--baud_rate",
help="Baud rate for serial connection. Default is 115200",
default=115200,
type=int
)
args = parser.parse_args()
# Convert comma-separated string to list of ints
occupied_com_ports_list = [
int(i) for i in args.occupied_com_ports.split(",")
]
# Call main function using command-line arguments
main(
args.min_com_port,
args.max_com_port,
occupied_com_ports_list,
args.baud_rate
)
import serial
from argparse import ArgumentParser
from datetime import datetime
from time import sleep
def initialise_connection(com_name, baud_rate, timeout):
""" Initialise connection to the serial device """
serial_device = serial.Serial(com_name, baud_rate, timeout=timeout)
print_with_timestamp("Initialised connection")
return serial_device
def print_with_timestamp(s, file=None):
""" Print a string s, prepended with a timestamp. Default is to print to
stdout, but can optionally print to a file """
print("%s: %s" % (datetime.now(), s), file=file)
def main(com_port_number, baud_rate, timeout, output_fname):
""" Main function for the script. Open the logging file, and start an
infinite loop, repeatedly trying to read bytes from the serial device,
decode the bytes, and printing timestamped decoded bytes to the log file. If
the serial device is busy, wait and try again. """
com_name = "COM%i" % com_port_number
with open(output_fname, "a") as f:
# Initialise connection
serial_device = initialise_connection(com_name, baud_rate, timeout)
# Start loop
while True:
try:
# Read bytes from serial device
line_bytes = serial_device.read_until(b"\n")
# Decode bytes into string
try:
line_str = line_bytes.decode("utf-8")
except UnicodeDecodeError:
line_str = repr(line_bytes)
# Remove newlines from string and print, if it is non-empty
line_str = line_str.replace("\n", "").replace("\r", "")
if len(line_str) > 0:
print_with_timestamp(line_str)
print_with_timestamp(line_str, file=f)
except serial.serialutil.SerialException:
try:
# Wait and then reinitialise connection
sleep(1.5)
serial_device = initialise_connection(
com_name,
baud_rate,
timeout
)
except serial.serialutil.SerialException:
pass
if __name__ == "__main__":
# Define CLI using argparse
parser = ArgumentParser(description="Script for logging serial connection")
parser.add_argument(
"-p",
"--com_port_number",
help="COM port that the serial device is connected to. Default is 10",
default=10,
type=int
)
parser.add_argument(
"-b",
"--baud_rate",
help="Baud rate for serial connection. Default is 115200",
default=115200,
type=int
)
parser.add_argument(
"-t",
"--timeout",
help="Timeout for reading from serial device in the loop",
default=1,
type=int
)
parser.add_argument(
"-f",
"--filename",
help="Filename for logging output ",
default="serial_log.txt",
type=str
)
# Parse arguments
args = parser.parse_args()
# Call main function using command-line arguments
main(
args.com_port_number,
args.baud_rate,
args.timeout,
args.filename
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment