3

I need to create a null modem simulation to test various pieces of software. I've been using socat to do this:

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

But I also need to simulate the basic baud rates of the serial port and the effects of UART buffers of various lengths. socat's baud rate (b####), ispeed, and ospeed parameters don't seem to have any effect when doing this type of pty loopback. For example:

socat -d -d pty,raw,echo=0,b50,ispeed=50,ospeed=50 pty,raw,echo=0,b50,ispeed=50,ospeed=50 &

Should result in a very slow data rate. Yet when passing data through this loopback, the data is transferred at the maximum machine speed. This doesn't surprise me since there's no real hardware in the loop.

So, is there a way to simulate UART timing short of writing custom code?

My ultimate aim is to simulate a large Ethernet-to-Serial device like a Moxa 5600 (16 ports) and how the effects of serial throughput affects the TCP socket streams etc... etc...

Thanks,

xl600
  • 71
  • 5
  • There do exist network simulators (and a serial port can be viewed as a network containing a single point-to-point connection, like a TCP stream) which introduce traffic shaping, artificial delay, error, etc. You could use one of these with a serial port by using socat to copy data between one pty to a network socket, and a second socat instance copying between the other end of the same connection to the other pty. – Ben Voigt Feb 25 '21 at 15:53
  • Note that buffering behavior is quite different between different models of real serial port (at the very least different drivers and bus connections, like UART 16550A vs USB FTDI vs USB CDC) so it would be difficult to have a pty simulator "just do the right thng", quite a bit of custom configuration will probably be needed even if not custom code. – Ben Voigt Feb 25 '21 at 15:55
  • I'm sure devices like the Moxa 5600 are unique in their own right, but I need to start somewhere and just getting the basic data rate simulated may be 'good enough' to continue. – xl600 Feb 25 '21 at 16:39
  • For a date rate (bandwidth) limit, I would suggest adding a tun/tap virtual network interface, routing your socat traffic through it instead of directly between ptys, and then configure traffic shaping on that tun/tap interface. – Ben Voigt Feb 25 '21 at 16:51
  • Also see `man throttle`. You could use socat to launch that, connecting each end to a different pty (but I don't think that would get you bidirectional traffic) – Ben Voigt Feb 25 '21 at 16:52
  • I tried the tuntap approach using a simple TCP echo server, but could not get any sort of actual (visible) rate limiting. For example: tc qdisc replace dev mytap root tbf rate 300bit peakrate 310bit limit 100 maxburst 10 minburst 10 – xl600 Feb 25 '21 at 18:57
  • My guess is that tc is just delaying entire packets, not limiting the amount in each packet. socat ought to be capable of that. Try its `-b` option. – Ben Voigt Feb 25 '21 at 20:02
  • I also don't see `bit` on the list of permitted suffixes for `tc` – Ben Voigt Feb 25 '21 at 20:06

2 Answers2

2

I don't regard this as a practical solution, but it is a fun one. Why simulate a null modem, when you can simulate a real modem. minimodem is available as a package in some distributions and converts data into modem audio tones, and vice versa. To try it out, just listen to this audio at baud rate 10 and 0.1 volume:

$ echo 'the quick brown fox' | minimodem  -v 0.1 --tx 10

This takes about 20 seconds to run. If you direct the output to a file, it is instantaneous, but you can then "play" the file with sox and have the tones interpreted back to data:

$ echo 'the quick brown fox' | minimodem  -v 0.1 --tx 10 -f out.wav 
$ file out.wav
  out.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 48000 Hz
$ sox out.wav -t wav - | minimodem -f - --rx 10
  ### CARRIER 10.00 @ 1590.0 Hz ###
  the quick brown fox
  ### NOCARRIER ndata=20 confidence=33.775 ampl=0.064 bps=10.00 (rate perfect) ###

This is still instantaneous, but shows that it works.

To play the tones in the wav file at the right speed, it needs to be played through an audio device. But we can use software loopback devices to do that:

$ sudo modprobe snd-aloop
$ aplay -l
 **** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC662 rev1 Analog [ALC662 rev1 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 3: HDMI 0 [HDMI 0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: Loopback [Loopback], device 0: Loopback PCM [Loopback PCM]
  Subdevices: 7/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 1: Loopback [Loopback], device 1: Loopback PCM [Loopback PCM]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7

We now have 8 loopback devices (the module has a parameter to ask for more). For example, we can now play sound on card 1, device 1, subdevice 2, and listen to it on card 1, device 0, subdevice 2. To play, taking 20 seconds:

$ sox out.wav -t alsa hw:1,1,2 
out.wav:
 File Size: 1.96M     Bit Rate: 768k
  Encoding: Signed PCM    
  Channels: 1 @ 16-bit   
Samplerate: 48000Hz      
Replaygain: off         
  Duration: 00:00:20.40  
In:47.7% 00:00:09.73 [00:00:10.67] Out:467k  [    -=|=-    ]        Clip:0    

To listen:

$ sox -q -t alsa hw:1,0,2 -t wav -c 1 - | minimodem  --rx 10 -f -
### CARRIER 10.00 @ 1590.0 Hz ###
the quick brown fox
### NOCARRIER ndata=21 confidence=10.265 ampl=0.060 bps=10.00 (rate perfect) ###

The characters take 20 seconds to appear, one by one.

meuh
  • 11,500
  • 2
  • 29
  • 45
  • 1
    Very interesting:) I would never have thought about doing it that way. I am trying to simulate serial rates from 1200 through 115200baud but I'm going to try this just to see it work. – xl600 Feb 25 '21 at 18:54
0

I remembered expect has an option to simulate human typing, and also to slowly output characters one by one for some hardware devices that have no input fifo and are really picky. From the man page (edited):

send_slow takes a two element list. The first element is an integer that describes the number of bytes to send atomically. The second element is a real number that describes the number of seconds by which the atomic sends must be separated. For example, "set send_slow {10 .001}" sends strings with 1 millisecond in between each 10 characters sent.

Here's an expect script, slowly, using that feature that copies stdin to stdout, slowly.

#!/usr/bin/expect
# 8890 bd
set send_slow {1 .001}
# 17740
set send_slow {2 .001}
# 95970
set send_slow {10 .001}
# 143660
set send_slow {15 .001}
# 123210
set send_slow {14 .001}
# 107770
set send_slow {12 .001}

# time the run. get eg: 1866426 microseconds per iteration
set total 0
set runtime [time {
    while {1} {
     if {[eof stdin]} { break }
     set data [read stdin 99]
     send_user -s $data
     set total [expr $total + [string bytelength $data]]
    }
}]
set runtime [lindex $runtime 0]
set bps [expr $total * 1000 * 1000 / $runtime]
set baud [expr $bps * 10]
puts stderr "$runtime usecs $total bytes $bps bytes/sec $baud baud"

To try it out, make the file executable and give it some data, eg:

yes abcdefghijklmnopqrstuvwxyz | dd bs=27 count=1000 status=none >input
./slowly <input >output

With the above I got an approximate baud rate of 107520:

2510973 usecs 27000 bytes 10752 bytes/sec 107520 baud

By changing the send_slow parameters to 1 .001 (and reducing the data file size!), it can go down to about 9000 baud. To get even slower, 1 .008 will get to about 1230 baud.

The send_human type of output might also be useful to simulate a certain type of "jittery" human-like data stream and flush out some bugs.

meuh
  • 11,500
  • 2
  • 29
  • 45