Поддельная последовательная связь под Linux

У меня есть приложение, в котором я хочу смоделировать соединение между устройством и "модемом". Устройство будет подключено к последовательному порту и через него будет общаться с программным модемом.

В целях тестирования я хочу иметь возможность использовать макет программного устройства для проверки отправки и получения данных.

Пример кода 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 модуль, но не может заставить это работать тоже. Я могу получить дескриптор файла master и slave от pty.openpty() но попытка прочитать или написать им только вызывает IOError Bad File Descriptor сообщение об ошибке.

Обновить

Комментарии указали мне на вопрос SO. Есть ли какая-нибудь программа типа COM0COM в linux? который использует socat настроить виртуальное последовательное соединение. Я использовал это так:

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

Всем остальным спасибо за предоставленную мне ценную информацию. Я решил принять ответ Винай Саджипса, потому что это решение, которое я искал до того, как появилось предложение socat. Кажется, это работает достаточно хорошо.

4 ответа

Решение

Вероятно, лучше использовать pyserial для связи с последовательным портом, и вы можете просто создать фиктивную версию serial.Serial класс, который реализует read, readline, write и любые другие методы, которые вам нужны.

Вы на правильном пути с псевдо-терминалами. Для этого вашему программному устройству необходимо сначала открыть мастер псевдотерминала - это дескриптор файла, с которого он будет считывать и записывать, когда общается с программным обеспечением, которое вы тестируете. Затем ему необходимо предоставить доступ и разблокировать псевдотерминальное ведомое устройство и получить имя подчиненного устройства. Затем он должен распечатать имя ведомого устройства где-нибудь, чтобы вы могли указать другому программному обеспечению открыть его как его последовательный порт (т.е. это программное обеспечение будет открывать имя, подобное /dev/pts/0 вместо /dev/ttyS1).

Программное обеспечение симулятора затем просто читает и пишет с главной стороны псевдотерминала. В Си это будет выглядеть так:

#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.

Вот питонная версия последовательной связи pts-эмулированной (caf's):

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) как имя последовательного порта. Я не могу ручаться за побочные эффекты обмена хозяина и раба, хотя. Наиболее примечательным является то, что вы можете закрыть и снова открыть раба, но если вы закроете его, мастер-раб тоже умрет.

Это работает как талисман, если ваш тестовый код выполняется в том же процессе. Я еще не сгладил изломы с несколькими / отдельными процессами.

Вот код, который у меня сработал (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

Другие вопросы по тегам