用termios发送AT命令并获得回复的正确方法是什么

时间:2019-05-22 20:38:08

标签: c linux select termios

我正在(资源受限的)嵌入式Linux平台(C语言)中使用Termios来发送命令并从各种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小时,显示没有问题,但是有一个问题:如果外围设备已断开连接,那么我会收到“环聊”通知,但它从未重新连接,因为我希望关闭文​​件并重试与接口的连接,因此

此外,民意调查是最好的方法吗?我不想将CPU时间完全浪费在轮询上(除非轮询具有某种机制,可以将CPU时间释放到其他线程上),但我仍然希望呼叫被阻塞,直到发生超时或响应到达为止(即,我不想发送命令,并稍后通过回调获取响应)。响应时间不到几毫秒就来自外围设备。

n.b。我知道有些眼睛因goto语句而流血,不用担心,这里使用goto来快速测试“重新连接”方法(该方法不起作用),但最后如果必须重新启动连接,它将进入一个单独的函数goto将永远不会在最终实现中使用。

编辑:

重写后。它可以工作,但仍然存在问题,主要是,当我断开外围设备的连接时,Linux将随机保留端口名称或对其进行更改。有时它将保留多个连续断开连接的端口名称,有时它将重命名该端口并使用新名称一段时间。因此,我必须找到一种从USB识别外围设备并获取当前端口名称的方法。

在断开连接时,它会从tcgetattr引发错误9(我认为是来自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;
        }
    }
}

我认为应该有一种更好的方法来处理断开连接(Detecting if a character device has disconnected in Linux in with termios api (c++)

1 个答案:

答案 0 :(得分:0)

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()传递了无效的fd,将POLLERR中设置revents-如果会忽略是否为负(例如失败的fd返回的open()),或者,如果为正,则设置POLLNVAL

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

POLLHUPPOLLRDNORM [1]可以在revents中一起返回,表示最后一次可能的读取,您的代码将错过该读取。

我强烈建议您使用strace(1) strace -p PID来运行程序。

[1]顺便说一句,POLLRDNORM等同于Linux中的POLLIN

[2]和EINTR,如果您正在使用的程序或库正在设置任何信号处理程序。