1

I am creating a fake serial device like this:

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

This creates devices with new numbers each time. Let's call them /dev/pty/6 and /dev/pty/7

To simulate a hardware device that I don't have available on my test machine, I am writing into /dev/pty/6 at twenty lines per second.

similar to this (in my real project I am writing with python, but this shows the same issue)

while true
do
    date > /dev/pts/6
    sleep 0.05
done

I have noticed that if I let cat /dev/pty/7 and after letting it run for a few seconds, it starts where I last left off, instead of starting with the next line I write.

so if I go timeout 5 cat -n /dev/pty/7 over and over again, without taking a break in between, I will always get about 100 lines. However, if I let the "producer" script run for five minutes, and then run timeout 5 cat -n /dev/pty/7, I get thousands of lines - all the lines that were written since my last read I guess.

The actual hardware I am trying to simulate doesn't behave like that.

timeout 5 cat -n /dev/ttyUSB0 always gives me about a hundred lines. It just throws away the lines that nobody is reading.

How can I do a better job of simulating this aspect of the hardware device? Is it the producer script that needs to check if anybody is reading and only write? Or is there an option I have to give to the socat command?

I think when I do this with python, the buffer it is writing to gets full, and the producer program gets stuck waiting for space to write to, but I can't demonstrate that in this example.

1
  • additionally, when I cat my fake serial port twice, each cat only gets half the output, whereas with the real usb device, I can cat in two terminals, and see the same output
    – Alex028502
    Feb 9, 2023 at 18:58

1 Answer 1

0

The problem with using socat for this is that the pty's have large buffers. Since you are using Python you can try to emulate something yourself. For example,

#!/usr/bin/python3
# https://unix.stackexchange.com/a/735081/119298
# some termios code from:
#  https://github.com/pyserial/pyserial/blob/master/serial/serialposix.py
import os, time, struct, fcntl, termios

class pseudotty:
    TIOCM_zero_str = struct.pack('I', 0)

    def __init__(self, name, mode):
        master, slave = os.openpty()
        sname = os.readlink(f"/proc/self/fd/{slave}")   # "/dev/pts/6"
        os.unlink(name)
        os.symlink(sname, name)
        self.mfd = master
        self.sfd = slave
        self.file = os.fdopen(master, mode)

    def waiting(self):
        result = fcntl.ioctl(self.sfd, termios.FIONREAD, self.TIOCM_zero_str)
        return struct.unpack('I', result)[0]

datain = pseudotty("input", "r")
dataout = pseudotty("output", "w")

while True:
    d = datain.file.readline()
    wait = dataout.waiting()
    if wait<200:
        dataout.file.write(d)

This creates and opens two pty's, with a symbolic link in the current working directory to each of the slaves, input and output. You can therefore direct your loop to use date >input, and the cat to cat output.

The program loops forever: it reads a line from the input pty, tests the current number of characters waiting in the output pty, and if it is under 200 characters, writes the line to the output. If there are over 200 characters, the input is discarded.

This can give you some control over how much data is buffered between the two pty's. Of course, you can replace the readline() by read(1), and replace the 200 limit by, say, 15; this could emulate many serial devices that have a limited fifo.

2
  • 1
    Please note: There is no socketpair() involved in the data streams of the given Socat command. Feb 11, 2023 at 9:11
  • You are right, thanks. It is only for exec: or system:, which I often use.
    – meuh
    Feb 11, 2023 at 9:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .