title: pty multiplexer
body: <p>I'm trying to multiplex access to a serial port on linux. I'm working with an embedded system that have only one serial port and it would be nice to have more than one process talking to it.</p>
  
  <p>The common use case is to have:</p>
  
  <ul>
  <li>One main program running tests (sending commands and receiving output);</li>
  <li>Another logging all serial port activity;</li>
  <li>An user terminal open to send additional commands and/or perform post morten analysis after some error during the tests.</li>
  </ul>
  
  <p>First, I made a simple python script to open n pseudo terminal pairs (plus the serial port) and used a poll statement to direct input/output to the right places:</p>
  
  <pre><code># Removed boiler plate and error checking for clarity
  
  ##### Serial port setup
  ttyS = serial.Serial(device, baudrate, width, parity, stopbits, 1, xon, rtc)
  ttyS.setTimeout(0) # Non-blocking
  
  ##### PTYs setup
  pts = []
  for n in range(number_of_slave_terminals):
      master, slave = os.openpty()
      # Print slave names so others know where to connect
      print &gt;&gt;sys.stderr, 'MUX &gt; fd: %d pty: %s' % (slave, os.ttyname(slave))
      pts.append(master)
  
  ##### Poller setup
  poller = select.poll()
  poller.register(ttyS.fd, select.POLLIN | select.POLLPRI)
  for pt in pts:
      poller.register(pt, select.POLLIN | select.POLLPRI)
  
  ##### MAIN
  while True:
  
  events = poller.poll(500)
  
  for fd, flag in events:
  
      # fd has input
      if flag &amp; (select.POLLIN | select.POLLPRI):
          # Data on serial
          if fd == ttyS.fd:
              data = ttyS.read(80)
              for pt in pts:
                  os.write(pt, data)
  
          # Data on other pty
          else:
              ttyS.write(os.read(fd, 80))
  </code></pre>
  
  <p>This approach works very well if every pty is connected. If there is some unconnected pty, eventually its buffer fills up and it blocks on write. Seems like I need to either know which slaves are connected or some kind of on demand pty opening.</p>
  
  <p>I found a neat trick on <a href="https://stackoverflow.com/questions/3486491/how-can-i-detect-when-someone-opens-the-slave-side-of-a-pty-pseudo-terminal-in">this question</a>, on which the guy needs to do just the reading from the serial port part, so I adapted my script:</p>
  
  <pre><code>##### Serial port setup
  ttyS = serial.Serial(device, baudrate, width, parity, stopbits, 1, xon, rtc)
  ttyS.setTimeout(0) # Non-blocking
  
  ##### PTYs setup
  pts = []
  for n in range(number_of_slave_terminals):
      master, slave = os.openpty()
  
      # slaves
      print &gt;&gt;sys.stderr, 'MUX &gt; fd: %d pty: %s' % (slave, os.ttyname(slave))
      os.close(slave) # POLLHUP trick
  
      # masters
      pts.append(master)
  
  ##### Poller setup
  reader = select.poll()
  writer = select.poll()
  
  reader.register(ttyS, select.POLLIN | select.POLLPRI)
  for pt in pts:
      reader.register(pt, select.POLLIN | select.POLLPRI)
      writer.register(pt, select.POLLIN | select.POLLPRI | select.POLLOUT)
  
  def write_to_ptys(data):
      events = writer.poll(500)
  
      for fd, flag in events:
  
          # There is someone on the other side...
          if not (flag &amp; select.POLLHUP):
              os.write(fd, data)
  
  ##### MAIN
  while True:
      events = reader.poll(500)
  
      for fd, flag in events:
  
      if flag &amp; (select.POLLIN | select.POLLPRI):
          # Data on serial
          if fd == ttyS.fd:
              write_to_tty(ttyS.read(80))
          # Data on other pty
          else:
              ttyS.write(os.read(fd, 80))
  </code></pre>
  
  <p>Which works, but uses 100% of the CPU because the reader poll is flooded with POLLHUP events.</p>
  
  <p>I imagine I could get what I want if I used TCP sockets instead of pseudo terminals. The downside is that I would have to modify all other scripts that already work with terminals to use sockets (I know I can use socat, I just want something simpler). Besides, there's all that networking overhead...</p>
  
  <p>So, any ideas?</p>
  
  <p>I don't mind using other tools, as long as it's simple to setup. I also don't mind using other languages, I just like Python the most.</p>