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

问题描述 投票:0回答:1

我在(资源受限)嵌入式 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 小时,并且没有显示通信方面的问题,但是有一个问题:如果外围设备断开连接,那么我会收到“hangout”通知,但它永远不会重新连接,我希望它会重新连接,因为我关闭了文件并重试连接到接口.

此外,民意调查是最好的方法吗?我不想通过轮询将 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++)

c linux posix-select termios
1个回答
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
开始之前写入而破坏内存。

请注意,如果传递了无效的 fd,

poll()
将不会POLLERR
 中设置 
revents
——如果它是负数,则 
忽略(就像失败的 fd
 返回的 
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

 相当于 Linux 中的 
POLLIN

[2] 和

EINTR

,如果您的程序或其使用的库正在设置任何信号处理程序。

© www.soinside.com 2019 - 2024. All rights reserved.