Поддельная последовательная связь под 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