202

I need to test a serial port application on Linux. However, my test machine only has one serial port.

Is there a way to add a virtual serial port to Linux and test my application by emulating a device through a shell or script?

Note: I cannot remap the port; it hard coded on ttys2, and I need to test the application as it is written.

0

10 Answers 10

251

Complementing the @slonik's answer.

You can test socat to create Virtual Serial Port doing the following procedure (tested on Ubuntu 12.04):

Open a terminal (let's call it Terminal 0) and execute it:

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

The code above returns:

2013/11/01 13:47:27 socat[2506] N PTY is /dev/pts/2 2013/11/01 13:47:27 socat[2506] N PTY is /dev/pts/3 2013/11/01 13:47:27 socat[2506] N starting data transfer loop with FDs [3,3] and [5,5] 

Open another terminal and write (Terminal 1):

cat < /dev/pts/2 

this command's port name can be changed according to the pc. it's depends on the previous output.

2013/11/01 13:47:27 socat[2506] N PTY is /dev/pts/**2** 2013/11/01 13:47:27 socat[2506] N PTY is /dev/pts/**3** 2013/11/01 13:47:27 socat[2506] N starting data transfer loop with FDs 

you should use the number available on highlighted area.

Open another terminal and write (Terminal 2):

echo "Test" > /dev/pts/3 

Now back to Terminal 1 and you'll see the string "Test".

Sign up to request clarification or add additional context in comments.

5 Comments

This worked better for me than slonik's answer, because it auto-assigns to virtual COM port files, and doesn't echo.
If you want reproducible filename use link=/path/to/link after each device-declaration (after echo=0). It can thus be used in automated tests. (as slonik does in their answer)
To create a pty that links to a real serial port: socat -d -d pty,raw,echo=0 /dev/ttyUSB5,raw,echo=0.
can i create a serial port with names like /dev/ttyS0 instead of /dev/pts/1 ?
I tried this but I can only open it once, (using c# SerialPort) eventhough a I dispose before trying to connect with a new instance it always crashes on the open
89

You can use a pty ("pseudo-teletype", where a serial port is a "real teletype") for this. From one end, open /dev/ptyp5, and then attach your program to /dev/ttyp5; ttyp5 will act just like a serial port, but it will send/receive everything it does via /dev/ptyp5.

If you really need it to talk to a file called /dev/ttys2, then simply move your old /dev/ttys2 out of the way and make a symbolic link from ptyp5 to ttys2.

Of course, you can use some number other than ptyp5. Perhaps pick one with a high number to avoid duplicates, since all your login terminals will also be using ptys.

Wikipedia has more about ptys: Pseudoterminal

13 Comments

On linux you can use the openpty / forkpty system calls. See man page
how to create a virtual serial port pair by using command line tool?
note that many serial port parameters, e.g. baudrate, parity, hw flow control, character size (?) are not implemented in pty, it is thus impossible to test your application in presence of serial transmission errors.
This is helpful, but it describes the "old style" BSD pseudo-terminals. The "new style" UNIX 98 pseudo-terminals operate a bit differently—see pts man page for details.
@LaszloPapp I do apologise, I was lying the whole time
|
67

Use socat for this:

For example:

socat PTY,link=/dev/ttyS10 PTY,link=/dev/ttyS11 

3 Comments

This worked well for me, tested with minicom! Seems as though the input to one terminal gets echoed to both (so it will reappear on the input terminal also).
I don't have the same echo behavior ... minicom has a "local echo" feature... but when it is disabled, it works exactly as a real serial port would. thanks for the tip.
This doesn't appear to work without special permissions: ``` 2021/12/10 16:08:47 socat[90013] E unlink("/dev/ttyS10"): Permission denied ```
21

There is also tty0tty, which is a real null modem emulator for Linux.

It is a simple kernel module and is a small source file. I don't know why it only got thumbs down on SourceForge, but it works well for me. The best thing about it is that is also emulates the hardware pins (RTC/CTS DSR/DTR). It even implements the TIOCMGET/TIOCMSET and TIOCMIWAIT iotcl commands!

On a recent kernel, you may get compilation errors. This is easy to fix. Just insert a few lines at the top of the module/tty0tty.c source (after the includes):

#ifndef init_MUTEX #define init_MUTEX(x) sema_init((x),1) #endif 

When the module is loaded, it creates four pairs of serial ports. The devices are /dev/tnt0 to /dev/tnt7, where tnt0 is connected to tnt1, tnt2 is connected to tnt3, etc. You may need to fix the file permissions to be able to use the devices.

I guess I was a little quick with my enthusiasm. While the driver looks promising, it seems unstable. I don't know for sure, but I think it crashed a machine in the office I was working on from home. I can't check until I'm back in the office on Monday.

The second thing is that TIOCMIWAIT does not work. The code seems to be copied from some "tiny tty" example code. The handling of TIOCMIWAIT seems in place, but it never wakes up because the corresponding call to wake_up_interruptible() is missing.

The crash in the office really was the driver's fault. There was an initialization missing, and the completely untested TIOCMIWAIT code caused a crash of the machine.

I spent yesterday and today rewriting the driver. There were a lot of issues, but now it works well for me. There's still code missing for hardware flow control managed by the driver, but I don't need it, because I'll be managing the pins myself using TIOCMGET/TIOCMSET/TIOCMIWAIT from user mode code.

I uploaded the driver to github if anyone wants to download it.

Comments

10

You may want to look at Tibbo VSPDL for creating a Linux virtual serial port using a kernel driver. It seems pretty new and is available for download right now (beta version). I am not sure about the license at this point, or whether they want to make it available commercially only in the future.

There are other commercial alternatives, such as http://www.ttyredirector.com/.

In open source, Remserial (GPL) may also do what you want, using Unix PTY's. It transmits the serial data in "raw form" to a network socket; STTY-like setup of terminal parameters must be done when creating the port, changing them later like described in RFC 2217 does not seem to be supported. You should be able to run two remserial instances to create a virtual nullmodem like com0com, except that you'll need to set up port speed, etc. in advance.

Socat (also GPL) is like an extended variant of Remserial with many many more options, including a "PTY" method for redirecting the PTY to something else, which can be another instance of Socat. For unit testing, socat is likely nicer than remserial, because you can directly cat files into the PTY. See the PTY example on the man page. A patch exists under "contrib" to provide RFC 2217 support for negotiating serial line settings.

1 Comment

The www.ttyredirector.com link is broken: "Hmm. We’re having trouble finding that site. We can’t connect to the server at www.ttyredirector.com."
7
$ socat -d -d pty,link=/tmp/vserial1,raw,echo=0 pty,link=/tmp/vserial2,raw,echo=0 

Will generate symlinks of /tmp/vserial1 and /tmp/vserial2 for generated virtual serial ports in /dev/pts/*

Resource

Comments

7

Using the links posted in the previous answers, I coded a little example in C++ using a Virtual Serial Port. I pushed the code into GitHub.

The code is pretty self-explanatory. First, you create the master process by running ./main master, and it will print to standard error the device is using. After that, you invoke ./main slave device, where device is the device printed in the first command.

And that's it. You have a bidirectional link between the two processes.

Using this example, you can test you the application by sending all kind of data, and see if it works correctly.

Also, you can always symbolic link the device, so you don't need to recompile the application you are testing.

2 Comments

while (read(fd, &inputbyte, 1) == 1) { ... } read is undefined in your code. write is undefined. close is undefined.
@MattisAsp has a point, you should at least include <unistd.h>. Plus there's a bunch of should-not-ignore-warnings fixable stuffs but overall simple code as I like.
5

You might be able to use a USB-to-RS-232 adapter. I have a few, and they just use the FTDI driver. Then, you should be able to rename /dev/ttyUSB0 (or whatever gets created) as /dev/ttyS2.

Comments

5

I can think of three options:

Implement RFC 2217

RFC 2217 covers a COM port to TCP/IP standard that allows a client on one system to emulate a serial port to the local programs, while transparently sending and receiving data and control signals to a server on another system which actually has the serial port. Here's a high-level overview.

What you would do is find or implement a client COM port driver that would implement the client side of the system on your PC - appearing to be a real serial port, but in reality shuttling everything to a server. You might be able to get this driver for free from Digi, Lantronix, etc. in support of their real stand-alone serial port servers.

You would then implement the server-side of the connection locally in another program, allowing the client to connect and issuing the data and control commands as needed.

It's probably non trivial, but the RFC is out there, and you might be able to find an open source project that implements one or both sides of the connection.

Modify the Linux serial port driver

Alternately, the serial port driver source for Linux is readily available. Take that, gut the hardware control pieces, and have that one driver run two /dev/ttySx ports, as a simple loopback. Then connect your real program to the ttyS2 and your simulator to the other ttySx.

Use two USB-to-serial cables in a loopback

But the easiest thing to do right now? Spend US$40 on two serial port USB devices, wire them together (null modem) and actually have two real serial ports, one for the program you're testing, and one for your simulator.

2 Comments

Actually the null modem USB UART cables seem like quite an elegant solution to me as it supports both local testing (get an USB hub if you are short on ports) and remote debugging.
I haven't reviewed it's quality, but ttynvt implements RFC 2217 via Linux FUSE
3

Combining all other amazingly useful answers, I found the below command to be VERY useful for testing on different types of Linux distros where there's no guarantee you're going to get the same /dev/pts/#'s every time and/or you need to test multiple psuedo serial devices and connections at once.

parallel 'i="{1}"; socat -d -d pty,raw,echo=0,link=$HOME/pty{1} pty,raw,echo=0,link=$HOME/pty$(($i+1))' ::: $(seq 0 2 3;) 

Breaking this down:

parallel runs the same command for each argument supplied to it. So for example if we run it with the --dryrun flag it gives us:

i="0"; socat -d -d pty,raw,echo=0,link=$HOME/pty0 pty,raw,echo=0,link=$HOME/pty$(($i+1)) i="2"; socat -d -d pty,raw,echo=0,link=$HOME/pty2 pty,raw,echo=0,link=$HOME/pty$(($i+1)) 

This is due to the $(seq x y z;) at the end, where x = start #, y = increment by, and z = end # (or # of devices you need to spawn)

parallel 'i="{1}"; echo "make psuedo_devices {1} $(($i+1))"' ::: $(seq 0 2 3;)

Outputs:

make psuedo_devices 0 1 make psuedo_devices 2 3 

Gathering all this together the final above command symlinks the proper psuedo devices together regardless of whats in /dev/pts/ to whatever directory supplied to socat via the link flag.

pstree -c -a $PROC_ID gives:

perl /usr/bin/parallel i="{1}"; socat -d -d pty,raw,echo=0,link=$HOME/pty{1} pty,raw,echo=0,link=$HOME/pty$(($i+1)) ::: 0 2 ├─bash -c i="0"; socat -d -d pty,raw,echo=0,link=$HOME/pty0 pty,raw,echo=0,link=$HOME/pty$(($i+1)) │ └─socat -d -d pty,raw,echo=0,link=/home/user/pty0 pty,raw,echo=0,link=/home/user/pty1 └─bash -c i="2"; socat -d -d pty,raw,echo=0,link=$HOME/pty2 pty,raw,echo=0,link=$HOME/pty$(($i+1)) └─socat -d -d pty,raw,echo=0,link=/home/user/pty2 pty,raw,echo=0,link=/home/user/pty3 

ls -l $HOME/pty* yield:

lrwxrwxrwx 1 user user 10 Sep 7 11:46 /home/user/pty0 -> /dev/pts/4 lrwxrwxrwx 1 user user 10 Sep 7 11:46 /home/user/pty1 -> /dev/pts/6 lrwxrwxrwx 1 user user 10 Sep 7 11:46 /home/user/pty2 -> /dev/pts/7 lrwxrwxrwx 1 user user 10 Sep 7 11:46 /home/user/pty3 -> /dev/pts/8 

This was all because I was trying running tests against a platform where I needed to generated a lot of mach-serial connections and to test their input/output via containerization (Docker). Hopefully someone finds it useful.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.