在Linux下的虚假串口通信

17

我有一个应用程序,想要模拟设备与“调制解调器”之间的连接。设备将连接到串行端口,并通过该端口与软件调制解调器通信。

为了测试目的,我希望能够使用模拟软件设备来测试发送和接收数据。

示例Python代码

device = Device()
modem  = Modem()
device.connect(modem)

device.write("Hello")
modem_reply = device.read()

现在,在我的最终应用程序中,我只需要传递/dev/ttyS1或COM1或其他内容给应用程序使用。 但是我该如何在软件中实现这一点呢?我正在运行Linux,应用程序是用Python编写的。
我尝试过制作FIFO(mkfifo ~/my_fifo),它确实起作用,但是那么我就需要一个FIFO用于读取和一个FIFO用于写入。我想要的是打开~/my_fake_serial_port并对其进行读写。
我还尝试使用pty模块,但也无法使其工作。我可以从pty.openpty()获取主文件描述符和从文件描述符,但尝试读取或写入它们只会导致IOError Bad File Descriptor错误消息。
更新
评论指向了SO问题Are there some program like COM0COM in linux?,它使用socat来设置虚拟串行连接。 我像这样使用它:
socat PTY,link=$HOME/COM1 PTY,link=$HOME/COM2
对于你们中的其他人,感谢您提供宝贵的信息。 我选择接受的答案,因为这是我在socat建议出现之前采取的解决方案。它似乎足够好用。

1
你尝试过像这样的东西吗?https://dev59.com/k0vSa4cB1Zd3GeqPga9l,用于PTY模块? - mtrw
请查看以下内容:http://superuser.com/questions/874133/screen-vs-socat/ - 0andriy
4个回答

11

最好使用pyserial与串口通信,你可以创建一个serial.Serial类的模拟版本,实现readreadlinewrite以及其他需要的方法。


谢谢,这就是我最终做的。 - Hannes Ovrén

5
您使用伪终端是正确的选择。为此,您的模拟软件设备需要首先打开一个伪终端主设备 - 当它与您正在测试的串行软件通信时,它将从该文件描述符中读取和写入。然后,它需要授予访问并解锁伪终端从设备,并获取从设备名称。接下来,它应该在某个地方打印出从设备的名称,以便您可以告诉其他软件打开该名称作为其串行端口(即该软件将打开类似于/dev/pts/0而不是/dev/ttyS1的名称)。
然后,模拟器软件只需从伪终端主设备读取和写入即可。在C语言中,它看起来像这样:
#define _XOPEN_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int pt;

    pt = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (pt < 0)
    {
        perror("open /dev/ptmx");
        return 1;
    }

    grantpt(pt);
    unlockpt(pt);

    fprintf(stderr, "Slave device: %s\n", ptsname(pt));

    /* Now start pretending to be a modem, reading and writing "pt" */
    /* ... */
    return 0;
}

希望这足够容易转换成Python了。

1
这是模拟pts-emulated (caf's)串行通信的Pythonic版本:
from serial import Serial

driver = MyDriver()  # what I want to test
peer = serial.Serial()
driver.port.fd, peer.fd = posix.openpty()
driver.port._reconfigurePort()
peer.setTimeout(timeout=0.1)
peer._reconfigurePort()
driver.start()

# peer.write("something")
# driver.get_data_from_serial()

相对于模拟串口,它有一些优点,尤其是使用了串口代码并且可以测试一些串口相关的工件。

如果您想测试串口的打开,您可以交换主从设备,将os.ttyname(salve_fd)用作串口名称。但是我无法保证交换主从设备会产生的副作用。最显著的是,您可以关闭并重新打开从设备,但如果关闭主设备,从设备也会停止运行。

如果您的测试代码在同一进程中运行,则此方法非常有效。但我还没有解决多个/分离进程的问题。


1
这个技巧似乎在较新版本的pySerial中不起作用。当端口关闭时,无法调用_reconfigure_port(),而且要想open()一个端口,唯一的方法是提供一个非None_port值。 - remcycles
1
一开始我错过了关于 os.ttyname() 的部分。这是一个好提示! - remcycles

1
这是一些对我有效的代码(Python 3.10.9 和 pySerial 3.5),使用 posix.openpty()。它将一个端口打开为文件,另一个端口打开为 pySerial 端口。
#!/usr/bin/env python3

import os, serial, posix

# Create a pySerial port and a binary file linked to a PTY (simulated
# serial cable).
def sim_serial():
    fd1, fd2 = posix.openpty()
    fd_file = os.fdopen(fd1, "wb")
    serial_port = serial.Serial(os.ttyname(fd2), 115200)
    os.close(fd2)

    return fd_file, serial_port

if __name__ == "__main__":
    fd_file, serial_port = sim_serial()
    fd_file.write(b'This is a test.\n')
    fd_file.flush()
    print(serial_port.read_until(b'\n'))

打开串口的两端似乎不起作用。我可以向一个端口write()数据,但是在另一个端口上read()永远不会返回。

def sim_serial():               # Doesn't work.
    fd1, fd2 = posix.openpty()
    p1 = serial.Serial(os.ttyname(fd1), 115200)
    p2 = serial.Serial(os.ttyname(fd2), 115200)

    return p1, p2

在看到这个pySerial测试并将其中一端作为文件打开之前,我没有找到解决方法:

https://github.com/pyserial/pyserial/blob/master/test/test_pty.py


1
代码如所提议,泄漏了文件描述符 fd2 - Dima Tisnek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接