Linux下的假串口通信

发布于 2024-08-26 18:32:55 字数 1018 浏览 5 评论 0 原文

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

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

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 用于写入,一个用于读取。我想要的是打开 ~/my_fake_serial_port 并对其进行读写。

我还使用了 pty 模块,但也无法使其工作。我可以从 pty.openpty() 获取主文件描述符和从文件描述符,但尝试读取或写入它们只会导致 IOError Bad File Descriptor 错误消息。

更新

评论向我指出了SO问题 Linux 中是否有像 COM0COM 这样的程序? 使用 socat 设置虚拟串行连接。 我这样使用它:

socat PTY,link=$HOME/COM1 PTY,link=$HOME/COM2

对于你们其他人,谢谢你们给我提供了宝贵的信息。 我选择接受 Vinay Sajips 的回答,因为这是我在 socat 建议出现之前寻求的解决方案。看起来效果很好。

I have an application where I want to simulate the connection between a device and a "modem". The device will be connected to a serial port and will talk to the software modem through that.

For testing purposes I want to be able to use a mock software device to test send and receive data.

Example Python code

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

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

Now, in my final app I will just pass /dev/ttyS1 or COM1 or whatever for the application to use.
But how can I do this in software? I am running Linux and application is written in Python.

I have tried making a FIFO (mkfifo ~/my_fifo) and that does work, but then I'll need one FIFO for writing and one for reading. What I want is to open ~/my_fake_serial_port and read and write to that.

I have also lpayed with the ptymodule, but can't get that to work either. I can get a master and slave file descriptor from pty.openpty() but trying to read or write to them only causes IOError Bad File Descriptor error message.

Update

Comments pointed me to the SO question Are there some program like COM0COM in linux? which uses socat to setup a virtual serial connection.
I used it like this:

socat PTY,link=$HOME/COM1 PTY,link=$HOME/COM2

To the rest of you, thank you for giving me valuable information.
I chose to accept Vinay Sajips's answer since that is the solution which I went for before the socat suggestion showed up. It seems to work well enough.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

戏舞 2024-09-02 18:32:55

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

It's probably best to use pyserial to communicate with the serial port, and you can just create a mock version of the serial.Serial class which implements read, readline, write and any other methods you need.

丶视觉 2024-09-02 18:32:55

使用伪终端,您走在正确的轨道上。为此,您的模拟软件设备需要首先打开一个伪终端主机 - 这是当它与您正在测试的串行软件通信时将读取和写入的文件描述符。然后需要授予对伪终端从机的访问权限和解锁,并获取从机设备的名称。然后它应该在某处打印出从属设备的名称,以便您可以告诉其他软件打开它,因为它是串行端口(即该软件将打开类似 /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。

You are on the right track with pseudo-terminals. To do this, your mock software device needs to first open a pseudo-terminal master - this is the file descriptor it will read from and write to, when it is talking to the serial software that you're testing. It then needs to grant access to and unlock the pseudo-terminal slave, and obtain the name of the slave device. It should then print out the name of the slave device somewhere, so that you can tell the other software to open that as it's serial port (ie. that software will be opening a name like /dev/pts/0 instead of /dev/ttyS1).

The simulator software then just reads and writes from the master side of the pseudoterminal. In C, it would look like this:

#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;
}

Hopefully that is easy enough to convert to Python.

俯瞰星空 2024-09-02 18:32:55

这是一些对我有用的代码(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

Here's some code that worked for me (Python 3.10.9 and pySerial 3.5), using posix.openpty(). It opens one end as a file and another as a pySerial port.

#!/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'))

Opening both ends as a serial port doesn't seem to work. I could write() data to one end, but read() would never return on the other end.

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

I didn't find a workaround until I saw this pySerial test which opens one end as a file:

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

山有枢 2024-09-02 18:32:55

这是 pts 模拟(caf)串行通信的 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) 作为串口名称。不过,我不能保证交换主设备和从设备会产生副作用。最值得注意的是,您可以关闭并重新打开从属设备,但是如果您关闭主从设备也会死亡。

如果您的测试代码在同一进程中运行,这就像一个魅力。我还没有解决多个/单独进程的问题。

Here's pythonic version of pts-emulated (caf's) serial communication:

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()

It has some advantages over mocking Serial, namely that Serial code is used and some serial port artefacts are exercised.

If you want to test opening of serial ports, you could swap master and slave around and use os.ttyname(salve_fd) as serial port name. I can't vouch for side-effects of swapping master and slave around though. Most notable is that you can close and reopen slave, but fi you close master slave dies too.

This works like a charm if your test code runs within same process. I didn't iron out the kinks with multiple/separate processes yet.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文