Use Python to Interact with Cisco Device Using Pyserial

For my latest project I wanted to communicate with a Cisco device over serial via the console port with my python program. It isn’t overly complicated, but it took me a bit to hack together something that worked properly and I thought I’d share it with everyone. ¬†Below is the code:

import serial
from time import sleep

# Open the serial port. The settings are set to Cisco default.
serial_port = serial.Serial(2, baudrate=9600, timeout=None, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, xonxoff=False)

# Make sure there is not any lingering input - input in this case being data waiting to be received
serial_port.flushInput()

print(serial_port.name)

serial_port.write("\n".encode())

serial_port.write("?".encode())

bytes_to_read = serial_port.inWaiting()

# Give the line a small amount of time to receive data
sleep(.5)

# 9600 baud is not very fast so if you call serial_port.inWaiting() without sleeping at all it will likely just say
# 0 bytes. This loop sleeps 1 second each iteration and updates bytes_to_read. If serial_port.inWaiting() returns a
# higher value than what is in bytes_to_read we know that more data is still coming in. I noticed just by doing a ?
# command it had to iterate through the loop twice before all the data arrived.
while bytes_to_read < serial_port.inWaiting():
bytes_to_read = serial_port.inWaiting()
sleep(1)

# This line reads the amount of data specified by bytes_to_read in. The .decode converts it from a type of "bytes" to a
# string type, which we can then properly print.
data = serial_port.read(bytes_to_read).decode()
print(data)

# This is an alternate way to read data. However it presents a problem in that it will block even after there is no more
# IO. I solved it using the loop above.
# for line in serial_port:
# print(line)

serial_port.close()

The first line opens a serial port object for our use. The only thing you will need to change here is the port number, which is the first argument. In my case, my console cable was connected to COM3, which is the third port. As per the usual, they start counting at 0 so mine was port # 2. The rest you can leave as I’ve configured it for Cisco’s default. Keep in mind if you change the baud rate on the Cisco device you’d have to change the baud rate here just like a normal terminal.

# Open the serial port. The settings are set to Cisco default.
serial_port = serial.Serial(2, baudrate=9600, timeout=None, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, xonxoff=False)

The next two lines clear any lingering input into the receive queue that we aren’t interested in and then print the name of the port we’re using (just in case you need that).

# Make sure there isn't any lingering input - input in this case being data waiting to be received
serial_port.flushInput()

print(serial_port.name)

Now we interact with the Cisco device. When you first boot up IOS you have to hit enter to get the command prompt. That’s exactly what the line:

serial_port.write("\n".encode())

is doing.¬†The .encode() is necessary because the .write() command is expecting a type of bytes. The encode() method converts our string to bytes for us using whatever the default character encoding is. On my computer that’s UTF-8. The next line is a proof of concept. To test my program I just had it read in the contents of a help command.

serial_port.write("?".encode())

These next lines are the core of the program. The comment above the loop does a pretty good job of explaining what’s going on. The inWaiting() command returns the number of bytes waiting in the receive queue.

# Give the line a small amount of time to receive data
sleep(.5)

# 9600 baud is not very fast so if you call serial_port.inWaiting() without sleeping at all it will likely just say
# 0 bytes. This loop sleeps 1 second each iteration and updates bytes_to_read. If serial_port.inWaiting() returns a
# higher value than what is in bytes_to_read we know that more data is still coming in. I noticed just by doing a ?
# command it had to iterate through the loop twice before all the data arrived.
while bytes_to_read < serial_port.inWaiting():
    bytes_to_read = serial_port.inWaiting()
    sleep(1)

# This line reads the amount of data specified by bytes_to_read in. The .decode converts it from a type of "bytes" to a
# string type, which we can then properly print.
data = serial_port.read(bytes_to_read).decode()
print(data)

The output should look something like this:

Cisco-1700>?
Exec commands:
access-enable Create a temporary Access-List entry
access-profile Apply user-profile to interface
clear Reset functions
connect Open a terminal connection
crypto Encryption related commands.
disable Turn off privileged commands
disconnect Disconnect an existing network connection
enable Turn on privileged commands
exit Exit from the EXEC
help Description of the interactive help system
lock Lock the terminal
login Log in as a particular user
logout Exit from the EXEC
modemui Start a modem-like user interface
mrinfo Request neighbor and version information from a multicast
router
mstat Show statistics after multiple multicast traceroutes
mtrace Trace reverse multicast path from destination to source
name-connection Name an existing network connection
pad Open a X.29 PAD connection
ping Send echo messages
--More--

The final line closes are serial port.

serial_port.close()

Hope this helps and saves you some time.

Leave a Reply