51

I know that I can use e.g. pySerial to talk to serial devices, but what if I don't have a device right now but still need to write a client for it? How can I write a "virtual serial device" in Python and have pySerial talk to it, like I would, say, run a local web server? Maybe I'm just not searching well, but I've been unable to find any information on this topic.

dsolimano
  • 8,272
  • 3
  • 45
  • 61
tdavis
  • 1,352
  • 2
  • 12
  • 12

6 Answers6

46

this is something I did and worked out for me so far:

import os, pty, serial

master, slave = pty.openpty()
s_name = os.ttyname(slave)

ser = serial.Serial(s_name)

# To Write to the device
ser.write('Your text')

# To read from the device
os.read(master,1000)

If you create more virtual ports you will have no problems as the different masters get different file descriptors even if they have the same name.

Aquiles
  • 751
  • 6
  • 13
  • This is exactly the answer I was looking for. This works for me on Mac OS X. – DrRobotNinja Jul 01 '14 at 15:11
  • 8
    This will not work on Windows, which lacks the termios module required by pty. – OYRM Dec 12 '14 at 20:16
  • 1
    I seem to be able to read from the fake device just fine (i.e. I have another program writing to the device at endpoint `s_name`), but whenever I issue `ser.write("...")`, that text just get echoed back the next time I `os.read(master,1000)`, regardless of if anything's connected to the port, and the other end of the port doesn't seem to get the data. – Ponkadoodle Jul 22 '15 at 23:23
  • 1
    When I run your code in Ubuntu 14.04 I get the following error: `File "", line 1, in File "/usr/local/lib/python2.7/dist-packages/serial/serialutil.py", line 180, in __init__ self.open() File "/usr/local/lib/python2.7/dist-packages/serial/serialposix.py", line 311, in open self._update_dtr_state() File "/usr/local/lib/python2.7/dist-packages/serial/serialposix.py", line 605, in _update_dtr_state fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str) IOError: [Errno 22] Invalid argument` – Samuel Góngora Mar 10 '16 at 14:20
  • Sorry Samuel, it works for me just fine right now. But I've since long ago stopped working on that project so I have not much input to give you at this point – Aquiles Mar 10 '16 at 17:39
  • @OYRM do you have some suggestions for Windows, I created question here http://stackoverflow.com/questions/36115831/programmatically-create-virtual-serial-port-on-windows – Ivan Borshchov Mar 20 '16 at 15:46
  • @SamuelGóngora, I was receiving the same error, make sure you have permissions to access the `/dev/tty` file by either running python with sudo, or changing ownership to the file to you. – Patrick Steadman May 26 '16 at 01:52
  • 1
    @SamuelGóngora Also see this: http://stackoverflow.com/questions/34831131/pyserial-does-not-play-well-with-virtual-port , I think this is probably the more accurate cause of the problem. – Patrick Steadman May 26 '16 at 19:36
  • 1
    pyserial's unit tests have a nice set of examples: https://github.com/pyserial/pyserial/blob/7bd427087857ba474180058b727578ca4cec5e2e/test/test_pty.py – Matt Mar 15 '17 at 22:58
6

It may be easier to using something like com0com (if you're on Windows) to set up a virtual serial port, and develop on that.

mtrw
  • 30,074
  • 7
  • 54
  • 68
  • is there a free `com0com` alternative (with API) that I can use in my commercial software `as it is` ? I just need the user to have virtual serial ports – mrid May 12 '20 at 04:15
6

I was able to emulate an arbitrary serial port ./foo using this code:

SerialEmulator.py

import os, subprocess, serial, time

# this script lets you emulate a serial device
# the client program should use the serial port file specifed by client_port

# if the port is a location that the user can't access (ex: /dev/ttyUSB0 often),
# sudo is required

class SerialEmulator(object):
    def __init__(self, device_port='./ttydevice', client_port='./ttyclient'):
        self.device_port = device_port
        self.client_port = client_port
        cmd=['/usr/bin/socat','-d','-d','PTY,link=%s,raw,echo=0' %
                self.device_port, 'PTY,link=%s,raw,echo=0' % self.client_port]
        self.proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        time.sleep(1)
        self.serial = serial.Serial(self.device_port, 9600, rtscts=True, dsrdtr=True)
        self.err = ''
        self.out = ''

    def write(self, out):
        self.serial.write(out)

    def read(self):
        line = ''
        while self.serial.inWaiting() > 0:
            line += self.serial.read(1)
        print line

    def __del__(self):
        self.stop()

    def stop(self):
        self.proc.kill()
        self.out, self.err = self.proc.communicate()

socat needs to be installed (sudo apt-get install socat), as well as the pyserial python package (pip install pyserial).

Open the python interpreter and import SerialEmulator:

>>> from SerialEmulator import SerialEmulator
>>> emulator = SerialEmulator('./ttydevice','./ttyclient') 
>>> emulator.write('foo')
>>> emulator.read()

Your client program can then wrap ./ttyclient with pyserial, creating the virtual serial port. You could also make client_port /dev/ttyUSB0 or similar if you can't modify client code, but might need sudo.

Also be aware of this issue: Pyserial does not play well with virtual port

Community
  • 1
  • 1
Patrick Steadman
  • 640
  • 6
  • 11
  • Do you have this code somewhere on GitHub? Why do I need to have 'ttydevice' and 'ttyclient'? I want to have a python program(device emulator) that I will run as a separate process. it will create './ttymydevice' and will reply to my serial commands as it is a really device. – Valentyn Sep 23 '19 at 17:24
5

If you are running Linux you can use the socat command for this, like so:

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

When the command runs, it will inform you of which serial ports it has created. On my machine this looks like:

2014/04/23 15:47:49 socat[31711] N PTY is /dev/pts/12
2014/04/23 15:47:49 socat[31711] N PTY is /dev/pts/13
2014/04/23 15:47:49 socat[31711] N starting data transfer loop with FDs [3,3] and [5,5]

Now I can write to /dev/pts/13 and receive on /dev/pts/12, and vice versa.

Richard
  • 44,865
  • 24
  • 144
  • 216
5

Maybe a loop device will do the job if you need to test your application without access to a device. It's included in pySerial 2.5 https://pythonhosted.org/pyserial/url_handlers.html#loop

Michael Burr
  • 311,791
  • 49
  • 497
  • 724
steko
  • 454
  • 5
  • 14
4

It depends a bit on what you're trying to accomplish now...

You could wrap access to the serial port in a class and write an implementation to use socket I/O or file I/O. Then write your serial I/O class to use the same interface and plug it in when the device is available. (This is actually a good design for testing functionality without requiring external hardware.)

Or, if you are going to use the serial port for a command line interface, you could use stdin/stdout.

Or, there's this other answer about virtual serial devices for linux.

Community
  • 1
  • 1
Dave Bacher
  • 14,787
  • 2
  • 60
  • 78
  • Yes, write a "virtual serial device" class that duplicates the pySerial interface. Then your code can use either your "virtual device" class, or the real pySerial interface, interchangeably. – Craig McQueen Feb 19 '10 at 00:02
  • This is a good implementation since it is rather easy to implement and it will work on all platforms! – Pithikos Jan 22 '15 at 13:27
  • Sorry to update this old post, but could you please elaborate a bit more? This would be very helpful for me. Thanks – rowman Jan 14 '18 at 15:44