Search code examples
pythonserial-portptysocat

How to determine which pair of pseudo tty ports are connected to each other


I have two python programs that are to communicate with each other over a point to point tty serial connection. To simulate the connection, I create a bi-directional byte stream using socat as here:

socat -d -d pty,raw,echo=0 pty,raw,echo=0

The console returns the following, indicating the two pty ports that are now connected together:

2018/04/15 22:33:03 socat[18197] N PTY is /dev/pts/2
2018/04/15 22:33:03 socat[18197] N PTY is /dev/pts/3
2018/04/15 22:33:03 socat[18197] N starting data transfer loop with FDs [5,5] and [7,7]

Then I have to modify each of the two programs manually to specify the serial connection for each, such as:

In program A.py:

import serial
...
ser = serial.Serial('/dev/pts/2')

In program B.py:

import serial
...
ser = serial.Serial('/dev/pts/3')

What I'd like is a script that would set up the socat connection, then execute A.py with one port number passed to it as an argument, and execute B.py with the other port number passed to it as an argument.

I've tried a bash script, but the socat command blocks and I can't capture its stderr output to put it in a variable. If I could, I could then parse the variable and be on my merry way.

Any ideas? Or maybe a muy better way to do this?

Thank you!


Solution

  • You can run socat without blocking using the low level Popen class in python. You need to redirect stderr so that you can scan it for the pty name, but then you can hand it off to a background thread after you get the information you want.

    import sys
    import subprocess as subp
    import threading
    import shutil
    import re
    
    socat_proc = subp.Popen(['socat', '-d', '-d', 'pty,raw,echo=0', 'pty,raw,echo=0'],
        stderr=subp.PIPE)
    
    try:
        # scan output for 2 ptys
        ptylist = []
        for line in socat_proc.stderr:
            line = line.decode().strip()
            print(line)
            pty = re.search(r"N PTY is (.+)", line)
            if pty:
                ptylist.append(pty.group(1))
                if len(ptylist) == 2:
                    break
    
        if socat_proc.poll() is not None:
            print("badness. socat dead.")
            exit(2)
    
        # background thread consumes proc pipe and sends to stdout
        socat_thread = threading.Thread(target=shutil.copyfileobj, 
            args=(socat_proc.stdout, sys.stdout))
        socat_thread.daemon = True
        socat_thread.start()
    
        print("pty", ptylist)
    
        # now you can start your programs...
    
    finally:
        socat_proc.terminate()