Почему этот последовательный / модемный код портит мой дисплей терминала? - PullRequest
0 голосов
/ 26 мая 2019

Я написал некоторый код для поиска любых модемов в системе Unix, использующих в основном regex / dev / tty *, а затем для любых совпадений посмотрите, можете ли открыть порт, и если да, отправьте команду AT и проверьте, содержит ли ответное сообщениесимволы «ОК».

Код не находит модем, но, к сожалению, он портит дисплей терминала.Увидеть ниже.Я заметил, что он также печатает команду AT - см. Вывод ниже.Почему изменился мой дисплей терминала и как я могу это исправить?

После запуска программы, если вы вводите команду и вводите, например, ls, команда не отображается, но когда вы нажимаете enter, вы видите вывод.

Вот код:

#include <iostream>
#include <string>
#include <unordered_map>
#include <iomanip>

#include <memory>

#include <sstream>
#include <thread>

#include <iostream>
#include <filesystem>
#include <regex>

#include <unistd.h>  // close
#include <fcntl.h>   // open, O_RDWR, etc
#include <termios.h>

#include <string.h>

#include <sys/select.h>  // timeouts for read

#include <sys/timeb.h>   // measure time taken

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        // Error from tcgetattr - can use strerror(errno)
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    /* setup for non-canonical mode */
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    /* fetch bytes as they become available */
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 1;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
      // Error from tcsetattr- use strerror(errno)
        return -1;
    }
    return 0;
}

long enumerate_ports(std::unordered_map <std::string, std::string>& ports) {

    // ls /dev | grep ^tty.*
    const std::regex my_filter( "^tty.*" );
    std::string path = "/dev/";
    for (const auto & entry : std::filesystem::directory_iterator(path)) {
     std::smatch sm;

     std::string tmp = entry.path().filename().string();
     // if we have a regex match attempt to open port and send AT command
     if (std::regex_match(tmp, sm, my_filter)) {
     std::string portname = entry.path().string();
         int fd = ::open(portname.c_str(), O_RDWR | O_NOCTTY);
         if (fd < 0) {
       // Error opening port
             continue;
         } else {
       // port was opened successfully
           // try to write AT command and do we get an OK response
           // baudrate 9600, 8 bits, no parity, 1 stop bit
           if(set_interface_attribs(fd, B9600) != 0) {
             ::close(fd);
         continue;
           }

           int wlen = ::write(fd, "AT\r\n", 4);
           if (wlen != 4) {
         // Error from write
               ::close(fd);
               continue;
           }

          // tcdrain() waits until all output written to the object referred 
          // to by fd has been transmitted.
           tcdrain(fd);

           fd_set set;
           struct timeval timeout;

           FD_ZERO(&set); /* clear the set */
           FD_SET(fd, &set); /* add our file descriptor to the set */

           timeout.tv_sec = 0;
           timeout.tv_usec = 100000; // 100 milliseconds

           // wait for data to be read or timeout
           int rv = select(fd + 1, &set, NULL, NULL, &timeout);
           if(rv > 0) {  // no timeout or error
               unsigned char buf[80];
               const int bytes_read = ::read(fd, buf, sizeof(buf) - 1);
               if (bytes_read > 0) {
                   buf[bytes_read] = 0;
                   unsigned char* p = buf;
                   // scan for "OK"
                   for (int i = 0; i < bytes_read; ++i) {
             if (*p == 'O' && i < bytes_read - 1 && *(p+1) == 'K') {
                       // we have a positive response from device so add to ports
                       ports[portname] = "";
                       break;
                     }
                     p++;
                   }
               }
        }
       ::close(fd);
         }
     }
   }

   return ports.size();
}


int main() {

    struct timeb start, end;
    int diff;
    ftime(&start);

    // get list of ports available on system
    std::unordered_map <std::string, std::string> ports;
    long result = enumerate_ports(ports);
    std::cout << "No. found modems: " << result << std::endl;
    for (const auto& item : ports) {
        std::cout << item.first << "->" << item.second << std::endl;
    }

    ftime(&end);
    diff = (int) (1000.0 * (end.time - start.time)
        + (end.millitm - start.millitm));

    printf("Operation took %u milliseconds\n", diff);
}

И вывод:

acomber@mail:~/Documents/projects/modem/serial/gdbplay$ ls
main.cpp  main.o  Makefile  serial
acomber@mail:~/Documents/projects/modem/serial/gdbplay$ make serial
g++ -Wall -Werror -ggdb3 -std=c++17 -pedantic  -c main.cpp
g++ -o serial -Wall -Werror -ggdb3 -std=c++17 -pedantic  main.o -L/usr/lib -lstdc++fs
acomber@mail:~/Documents/projects/modem/serial/gdbplay$ sudo ./serial
[sudo] password for acomber: 
AT
No. found modems: 1
                   /dev/ttyACM0->
                                 Operation took 8643 milliseconds
                                                                 acomber@mail:~/Documents/projects/modem/serial/gdbplay$

1 Ответ

1 голос
/ 27 мая 2019

Почему этот последовательный / модемный код портит мой терминал?

Точный ответ требует, чтобы вы опубликовали настройки терминала до выполнения вашего кода, т.е. stty -a.

Код находит модем, но, к сожалению, он портит дисплей терминала.

Самый простой (то есть простой) обходной путь / решение состоит в том, чтобы придерживаться старого (но редко соблюдаемого) совета о сохранении и последующем восстановлении настроек termios терминала, как в в этом примере .

Простые изменения, необходимые в вашем коде, были бы похожи на следующий (пожалуйста, не обращайте внимания на смешивание C и C ++; я знаю только C).

 struct termios savetty;

 int set_interface_attribs(int fd, int speed)
 {
+    struct termios tty;

     if (tcgetattr(fd, &tty) < 0) {
         // Error from tcgetattr - can use strerror(errno)
         return -1;
     }
+    savetty = tty;    /* preserve original settings for restoration */

     cfsetospeed(&tty, (speed_t)speed);
     cfsetispeed(&tty, (speed_t)speed);

Затем в enumerate_ports () последние два экземпляра ::close(fd); необходимо заменить на последовательность, которая будет выполнять восстановление:

+    if (tcsetattr(fd, &savetty) < 0) {
+        // report cannot restore attributes
+    }
     ::close(fd);

После запуска программы, если вы вводите команду и вводите, например, ls, команда не отображается ...

Это, очевидно, является результатом очистки атрибута ECHO.
«Отсутствие» возврата каретки, вероятно, связано с очищенным OPOST.
Другими существенными атрибутами, которые были очищены вашей программой, но, вероятно, ожидается, что они будут установлены оболочкой, являются ICANON, ICRNL и IEXTEN.
Но вместо того, чтобы пытаться определить, что именно нужно отменить, правильным и гарантированным решением будет просто восстановить настройки termios обратно в исходное состояние.

Альтернативный (ленивый) подход - использовать команду stty sane после выполнения вашей программы.

...