Как правильно отправить команду AT с помощью termios и получить ответ - PullRequest
0 голосов
/ 22 мая 2019

Я использую Termios на встроенной платформе Linux (с ограниченными ресурсами) (в C) для отправки команд и получения данных от различных периферийных устройств tty (CP2102 USB-UART).По-видимому, есть разные способы сделать это, и многие из них не подходят.Я пробовал мало с небольшим успехом, я столкнулся с чем-то, что работает, но я не уверен, что это оптимально или даже правильно:

#define BAUDRATE    115200
#define DEVICE      "/dev/ttyUSB0"
#define DATA        "BLK\r"

int handler(void){

    char buf[255];
    struct pollfd fds[1];
    int fd, ret, res, retry = 0;

connect:
    fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd == 0){
        perror(DEVICE);
        printf("Failed to open %s\n",DEVICE);
        sleepms(2000);
        if(retry++<5) goto connect;
        //exit(-1);
    }
    set_interface_attribs (fd, BAUDRATE, 0); // 8n1 no parity
    set_blocking (fd, 0);                    // not blocking
    fds[0].fd = fd;              // streams
    fds[0].events = POLLRDNORM;
    for (;;)
    {
        int count = write(fd, DATA, strlen(DATA));
        ret = poll(fds, 1, 1000);
        if (ret > 0){
            if (fds[0].revents & POLLHUP){ printf("Hangup\n"); close(fd); goto connect;}
            if (fds[0].revents & POLLRDNORM){
                res = read(fd,buf,255);
                if(!res){close(fd); goto connect;}
                buf[res-2]=0;
                printf("Received %d bytes : %s",res,buf);
            }
        }

    }
} 

В основном команда отправляется, затем ее опрос, пока некоторые данные не поступят или тайм-аут не произойдет,Это работало в течение более 24 часов и не показывало проблем со связью, однако есть одна проблема: если периферийное устройство отключено, то я получаю уведомление о «видеовстрече», но оно никогда не переподключается, я ожидал, что это произойдет, так как я закрываю файл и повторяю соединение с интерфейсом.

Кроме того, опрос является лучшим подходом здесь?я не хочу тратить процессорное время в чистой потере при опросе (если только опрос не имеет некоторого механизма для высвобождения процессорного времени другим потокам), но я все еще хочу, чтобы вызов блокировался до истечения времени ожидания или получения ответа (т.е. я не хочу отправлятькоманда и получить ответ позже с обратным вызовом).Ответ приходит от периферийного устройства менее чем за несколько миллисекунд.

nb Я знаю, что некоторые глаза кровоточат из-за заявления goto, не беспокойтесь, goto использовался здесь для быстрого тестирования подхода «переподключения» (который не работал) но в конце концов, если соединение должно быть перезапущено, оно будет в отдельной функции, goto никогда не будет использоваться в окончательной реализации.

EDIT:

После перезаписи.Это работает, но все еще есть проблемы, в основном, когда я отключаю периферийное устройство, Linux случайным образом сохранит имя порта или изменит его.Иногда он будет сохранять имя порта для нескольких последовательных отключений, иногда он будет переименовывать его и некоторое время придерживаться нового имени.Поэтому мне нужно найти способ идентифицировать периферийное устройство по USB и получить текущее имя порта, каким бы оно ни было.

При отключении выдает ошибку 9 из tcgetattr (исходя из set_interface_attribs или set_blocking, я думаю), но как только он снова подключаетсяи если Linux не меняет имя порта, то он сразу же повторно подключается и перезапускается для отправки и получения, как и должно, однако, когда Linux переименовывает порт, это приводит к ошибке.

int fd=-1,retry=0;
struct pollfd fds[1];

int connect(void){

    if(fd==-1) fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
    else return 0; // already opened
    printf("Connecting to fd #%d\n",fd);
    if (fd==0){
        perror(MODEMDEVICE);
        printf("Failed to open %s\n",MODEMDEVICE);
        retry++;
        sleepms(2000);
        return -1;
    }
    set_interface_attribs (fd, BAUDRATE, 0);    // 8n1 no parity
    set_blocking (fd, 0);                       // not blocking
    fds[0].fd = fd;                             // streams
    fds[0].events = POLLERR|POLLHUP|POLLRDNORM;
    return 0;
}
int handlePoll(){

    int ret=0,res=0;
    char buf[255];

    ret = poll(fds, 1, 1000); // 1000ms
    if (ret > 0){
        if (fds[0].revents & POLLERR){ close(fd); fd=-1; return -1; } // IO error
        if (fds[0].revents & POLLHUP){ close(fd); fd=-1; return -2; } // interface closed
        if (fds[0].revents & POLLRDNORM){
            res = read(fd,buf,255);
            if(!res){ close(fd); return -3; } // data receive error
            buf[res-2]=0;
            printf("Received %d bytes : %s\n",res,buf);
            return 0;
        }
        return -4; // unknown error
    }
    return -5; // timeout
}
int sendCMD(char* cmd){

    int res=1;
    retry=0;

    while(res && retry<5){ res=connect(); }
    if(res) return -7;
    if(retry>5) return -8;
    int len = strlen(cmd);
    int count = write(fd, cmd, len);
    if(count<len){ return -6;}
    return handlePoll();
}
int main(void){

    while(1){
        switch(sendCMD(DATA)){
        case -1: printf("IO error\n"); break;
        case -2: printf("Interface closed error\n"); break;
        case -3: printf("data receive error\n"); break;
        case -4: printf("Unknown error\n"); break;
        case -5: printf("Timeout\n"); break;
        case -6: printf("Command send error\n"); sleepms(200); break;
        case -7: printf("Interface open error\n"); break;
        case -8: printf("Cannot open interface after 5 try\n"); break;
        default: break;
        }
    }
}

Я думаю, что должно бытьлучший способ справиться с отключением ( Обнаружение того, что символическое устройство отключено в Linux с помощью termios api (c ++) )

1 Ответ

0 голосов
/ 23 мая 2019
connect:
    fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd == 0){
        perror(DEVICE);

open(2) не возвращает 0, но -1 в случае ошибки. 0 является допустимым файловым дескриптором - по соглашению стандартный ввод.

        int count = write(fd, DATA, strlen(DATA));
        ret = poll(fds, 1, 1000);
        if (ret > 0){

Почему вы не проверяете возвращаемое значение write()? Если open() выше действительно не удалось и вернул -1, этот write() тоже не удастся, верните -1 и установите errno в EBADF.

                res = read(fd,buf,255);
                if(!res){close(fd); goto connect;}
                buf[res-2]=0;

Так же, вам все равно, если read() не удастся, что, безусловно, будет, если open() выше не удалось открыть файл. Как и open(), write() и все системные вызовы, read() вернет -1 в случае ошибки.

Поскольку ваш файловый дескриптор неблокирующий, будьте готовы обрабатывать временные ошибки, такие как EAGAIN отдельно [2].

В случае, когда res == -1 buf[res-2]=0 повредит память, записав ее до начала buf.

Обратите внимание, что poll() будет не установить POLLERR в revents, если передан недопустимый fd - если будет либо игнорировать , если он отрицательный (например, * 1042) * возвращается по ошибке open()) или установите POLLNVAL, если оно положительное.

            if (fds[0].revents & POLLHUP){ printf("Hangup\n"); close(fd); goto connect;}
            if (fds[0].revents & POLLRDNORM){

POLLHUP и POLLRDNORM [1] могут быть возвращены вместе в revents, сигнализируя о последнем возможном чтении, которое ваш код пропустит.

Я настоятельно рекомендую вам strace(1) вашу программу, которую вы могли бы сделать, пока она работает, с strace -p PID.

[1] Кстати, POLLRDNORM эквивалентно POLLIN в Linux.

[2] и EINTR, если ваша программа или используемые библиотеки устанавливают какой-либо обработчик сигнала.

...