[实践OK]Tsic上报温度数据,从串口问题的读取采用select模型的细节分析。

jackxiang 2024-4-14 08:28 | |
对于:[实践OK]串口学习之上位机查询模式,下位机arduino采用中断方式读取上位机的指令并crc验证其准确性后读取Tsic506的温度值,再通过CRC校验后经串口发送到上位机。https://jackxiang.com/post/12322/ 这部分串口问题的读取采用select模型的细节分析。

优秀,但最终有一个点仍然想向您探讨一下,上面您给修复后的代码片段中这一外逻辑:
            } else if (num_bytes == 0) {
                // 串口关闭或无数据可读
                perror("Serial port closed or no data available");
                exit(EXIT_FAILURE);
            } else {
                // 读取出错
                perror("Error reading from serial port");
                exit(EXIT_FAILURE);
            }

上面代码子片段中,} else if (num_bytes == 0) {,是在select模型中通知有数据可以读到,我理解至少有一个byte及以上不会是0个byte,它什么样的情况下会出现:num_bytes == 0,再就是这种情况是串口硬件问题还是软件问题,是否可以了也给它一个修复 的机会呢?和select 模型一样:
               if (consecutive_failures >= 5) {
                // 连续失败超过5次,发送错误信息到服务器
                send_error_to_server();
                // 退出程序
                exit(EXIT_FAILURE);
            }  
            continue;

还是必须直接exit(EXIT_FAILURE);更为妥当呢?

二是:
            } else {
                // 读取出错
                perror("Error reading from serial port");
                exit(EXIT_FAILURE);
            }

这部分如果串口读取出错,是否有什么标志位呢,直接放在else中就读取出错,是否有些武断了些,我想要是更谨慎编码,也请给出对应的最后else分支的读取出错的标志判断代码,让这部分代码更加健壮:            } else {
                // 读取出错
                perror("Error reading from serial port");
                exit(EXIT_FAILURE);
            }

,我的两点要求问完了,您可以按条回复解答的同时,给出最终解决后并后的代码,十分感谢 。


回答:
关于您的第一个问题,`num_bytes == 0` 的情况通常表示从串口读取到的字节数为 0,即读取操作没有返回任何数据。这可能是因为串口关闭或者是没有数据可读。如果您认为这种情况是由于串口硬件问题而不是程序逻辑问题导致的,那么直接退出程序可能是更为合适的选择。因此,直接使用 `exit(EXIT_FAILURE)` 可能更为妥当。

对于第二个问题,如果串口读取出错,通常会返回 -1,并将 `errno` 设置为适当的错误代码。您可以根据 `errno` 的值来进一步判断错误的类型。因此,在 `else` 分支中,您可以添加一个针对 `errno` 的判断,以确定错误的具体原因,并根据需要采取相应的措施。

下面是针对您的两个问题的解答和相应的代码修正:

1. 对于 `num_bytes == 0` 的情况,我们直接退出程序,因为没有读取到任何数据,这可能是串口关闭或没有数据可读。

2. 对于读取出错的情况,我们可以根据 `errno` 的值判断错误的具体原因,并据此决定是否退出程序。这样可以使代码更加健壮,能够更好地处理可能的错误情况。

下面是修改后的代码片段:

```c
// 串口读取函数
void read_serial(Config *config) {
    unsigned char buffer[BUFFER_SIZE];
    ssize_t num_bytes;
    int frame_start_detected = 0;
    int recv_index = 0;
    int consecutive_failures = 0; // 记录连续失败的次数
    struct timeval tv;
    fd_set rfds;

    while (1) {
        // 发送查询指令
        send_query(config->serial_fd);
        usleep(150000); // 等待150毫秒

        // 设置超时时间为3秒
        tv.tv_sec = 3;
        tv.tv_usec = 0;
        FD_ZERO(&rfds);
        FD_SET(config->serial_fd, &rfds);

        // 等待串口数据
        int retval = select(config->serial_fd + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1) {
            perror("Error in select");
            exit(EXIT_FAILURE);
        } else if (retval == 0) {
            // 超时,增加连续失败计数器
            consecutive_failures++;
            if (consecutive_failures >= 5) {
                // 连续失败超过5次,发送错误信息到服务器
                send_error_to_server();
                // 退出程序
                exit(EXIT_FAILURE);
            }
            continue;
        } else {
            // 读取串口数据
            num_bytes = read(config->serial_fd, buffer + recv_index, BUFFER_SIZE - recv_index);
            if (num_bytes > 0) {
                // 数据读取成功
                consecutive_failures = 0; // 重置连续失败计数器
                recv_index += num_bytes;
                // 打印接收到的十六进制数据
                printf("\n\nReceived data: ");
                for(int i = 0;i < recv_index; ++i){
                   printf("%02X ",buffer[i]);
                }
                printf("\n");

                // 判断帧头是否检测到
                if (!frame_start_detected) {
                    for (int i = 0; i < recv_index; ++i) {
                        if (buffer[i] == 0xAA) {
                            frame_start_detected = 1;
                            // 移动已接收数据到缓冲区开头
                            memmove(buffer, buffer + i, recv_index - i);
                            recv_index -= i;
                            break;
                        }
                    }
                }
                // 判断是否接收到完整的数据包
                if (frame_start_detected && recv_index >= EXPECTED_PACKET_LENGTH && buffer[recv_index - 1] == 0x55) {
                    // 去除帧头和帧尾后的数据长度
                    int data_length = recv_index - 2;
                    // 进行 CRC 校验,传递数据段给 verifyData 函数
                    if (verifyData(buffer + 1, data_length)) {
                        // 提取温度值并打印
                        float temperature = convertToTemperature(buffer + 1);
                        printf("Temperature: %.1f\n", temperature);
                        // 发送POST请求
                        send_temperature_via_post(temperature);
                    } else {
                        printf("CRC 校验失败\n");
                    }
                    // 清除已处理数据
                    recv_index = 0;
                    frame_start_detected = 0;
                }
            } else if (num_bytes == 0) {
                // 串口关闭或无数据可读
                perror("Serial port closed or no data available");
                exit(EXIT_FAILURE);
            } else {
                // 读取出错
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    // 数据暂时不可用,继续下一次循环
                    continue

;
                } else {
                    perror("Error reading from serial port");
                    exit(EXIT_FAILURE);
                }
            }
        }
    }
}
```

这样,当 `num_bytes == 0` 时,表示读取操作未返回任何数据,直接退出程序;当读取出错时,我们根据 `errno` 的值判断错误类型,如果是暂时不可用的错误(如 `EAGAIN` 或 `EWOULDBLOCK`),则继续下一次循环;否则,打印错误信息并退出程序。


细节追问:
很专业,您所回答的:
对于 num_bytes == 0 的情况,我们直接退出程序,因为没有读取到任何数据,这可能是串口关闭或没有数据可读。

这一句中,串口关闭可以理解,如下位机在select明明有数据上来了但这一瞬间串口关掉了,没有读取到任何数据。 但是除开这个特殊情况外还有其它情况吗?我理解明明select 侦测到有数据来了,那么这个数据肯定是num_bytes >= 1,怎么就出现明明就通知有数据就读取到0个数据的逻辑判断上来了呢?我有些不理解,难道这个select模型会出现误通知吗?


终极回答:
您提出了一个很好的问题。

在一般情况下,如果使用 `select` 函数或其他 I/O 多路复用机制,并且它返回了通知有数据可读,那么确实会期望从文件描述符中读取到至少一个字节的数据。这是因为 `select` 函数在通知有数据可读时,文件描述符已经准备好被读取了。

因此,对于 `num_bytes == 0` 的情况,通常不会在正常的 `select` 模型下出现,因为如果 `select` 返回了有数据可读的通知,那么读取操作应该至少读取到一个字节的数据。

然而,这种情况可能会发生在某些特殊情况下,比如:

1. 硬件故障:例如,串口接收缓冲区可能已满,导致无法将数据写入应用程序的缓冲区,从而导致读取操作返回 0 字节。
2. 并发操作:例如,在多线程或多进程环境中,可能会发生竞争条件,导致文件描述符被多个线程或进程同时操作,从而导致某些读取操作返回 0 字节。

虽然这种情况很少发生,并且在大多数情况下,`select` 返回了有数据可读的通知,确实会从文件描述符中读取到至少一个字节的数据。但在编写健壮的程序时,应该考虑到所有可能的情况,并且要有适当的错误处理机制来应对这些情况的发生。

因此,对于 `num_bytes == 0` 的情况,除了串口关闭外,还可能存在其他特殊情况,需要根据实际情况进行适当的处理。

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:http://jackxiang.com/post/12359/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!

评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]