Bash – send data to serial (rs232) port and wait for response

Sending data to a serial port is quite easy in Bash, for example:

echo "my packet data" > /dev/ttyS0

And you can read from a serial port using cat:

cat /dev/ttyS0

However cat must typically be run from a different shell instance as it blocks waiting for data. So is it possible to write and then read the response from a single shell instance?

Well, it is, but it requires a bit of sleight-of-hand. For example, if we start cat in the background and then send the command, cat will report the response as follows:

# Run cat in the background
cat /dev/ttyS0&

# Send the command, cat should print the response
echo "my packet data" > /dev/ttyS0

Which works but it a bit of a mouth-full! cat continues to run in the background, and will print more responses as they arrive.

But what if you want to just send one packet and then wait for a single response?

This is a bit harder, but if your response ends with an end of line character, or another known character then we can use read to help with this…

First we setup a read command in the background, unlike cat, this command will end when a response is received or when the timeout time arrives, then we can send our command:

(read -n60 -t20 RESP < /dev/ttyS0; echo $RESP)&
echo "my packet data" > /dev/ttyS0

This gets read to wait for up to 20 seconds (-t20) for a line of data (max size, 60 characters -n60) from /dev/ttyS0, which it reads into RESP, it then echos $RESP – all of this happens in the background. echo then sends the packet which will result in a response.

If your response packed ends with a character other than an EOL character then you can specify a delimiter to read using the -d command-line option.

Again it’s all a bit long winded, so we can wrap it all up in a bash script (send_tty.sh) as follows:

#!/bin/sh
#
# Send a packet to the specified serial port
# and wait for, and output the response, it is assumed
# that the response will end with an EOL character.
#
# usage: send_tty.sh  
#
# [backgound]  Wait for, and read a line from the serial port into RESP,
# max 128 characters, timeout=10s, then output $RESP
#
(read -n128 -t10 RESP < $2; echo $RESP)&

# Hack - use read to pause for 200ms to give previous
# command a chance to get started..
read -p "" -t 0.2

# Send command
printf "$1\r" > $2

# Wait for background read to complete
wait

An example of using the script:

./send_tty.sh "my-command" /dev/ttyPS0

Github repo is here