nuttx实战项目:多路串口合并功能之十主app程序设计(copilot版本)
·
/*
* yumo_intelligence_main.c
*
* 概述
* - 一体化的多端口设备探测与转发程序:与上位机(MAIN串口)进行统一协议交互,并与四个设备串口进行数据透传。
* - 运行环境:NuttX(内存紧张,典型总内存仅 ~128KB),因此本程序严格控制栈和堆使用。
* - 并发模型:3个线程 + 主线程
* 1) main_rx_thread:读取 MAIN 串口(/dev/ttyS1)下行帧,按照“设备类型(dev_id)”转发到对应已绑定的设备端口。
* 2) detect_thread:设备类型探测、热插拔(拔出)判定、brain_o2心跳、每秒状态上报至 MAIN。
* 3) 主线程:poll 所有已绑定设备串口,收数据后组帧并上送 MAIN。
*
* 目标与关键点
* - 精确的协议处理:修复过 MAIN 帧解析 off-by-one 问题;所有 MAIN 帧严格校验头、长度、CRC、尾。
* - 消除“乱帧”:针对 MAIN 串口写操作增加互斥锁与完整写(循环write+poll),避免多线程短写交错。
* - 设备探测优化(方案A):未绑定端口先进行“空闲快速窗口”(DETECT_IDLE_WINDOW_MS),若无字节立刻进行brain探针等待;如果有字节再扩展为“正常窗口”滑动判别 BIS/BLOOD/MUSCLE。
* - 热插拔(拔出)调试:已绑定端口连续 HOTPLUG_INACTIVE_UNBIND_SEC 秒未收到任何数据即解绑,随后由 detect 线程自动重启探测流程。
* - 内存使用:将较大的缓冲(3KB探测缓冲、若干512B IO缓冲)放在静态全局区;线程栈限制为4KB。
*
* 协议(MAIN 串口)
* - 帧格式(大端长度):
* +--------+--------+---------------------+--------+--------+
* | 头(3) | 长度(2)| dev_id(1)+payload(N)| CRC(1)| 尾(3) |
* +--------+--------+---------------------+--------+--------+
* - 头:固定 0xAA 0xAA 0xAA
* - 长度plen:大端,表示 dev_id(1) + payload(N) 的总长度
* - CRC:对 [lenH,lenL,dev_id,payload...] 共 (2 + plen) 个字节做逐字节和,取低8位
* - 尾:固定 0xFF 0xFF 0xFF
* - 语义:
* - MAIN->设备:dev_id 为“设备类型”而非“端口号”,会被广播到所有“已绑定该类型”的端口,payload 原样写入设备端口(不含外层头/尾/CRC)
* - 设备->MAIN:本程序将设备上来的原始payload封装为 MAIN 帧上行,dev_id 为该端口绑定的类型
* - 本地控制帧:dev_id == 0xFF 为“本地处理”预留(如配置/查询),当前未实现逻辑,后续可扩展。
*
* 设备类型与探测
* - 支持设备类型:
* - DEV_BIS(0):判据为在滑窗中发现 bis_header(0x66 0x66 0x66)
* - DEV_BRAIN_O2(1):通过发送 BRAIN_PROBE(19字节)后,检测响应片段 BRAIN_RESP(6字节)
* - DEV_MUSCLE(2):在滑窗中完全匹配 MUSCLE_HEADER(10字节)
* - DEV_BLOOD(3):滑窗中满足 blood_header(0xFA 0xFA 0xFA)且偏移+7/+8为 BLOOD_MID(0x00 0x17)
* - 方案A探测流程(未绑定端口):
* 1) 空闲快速窗口(DETECT_IDLE_WINDOW_MS,默认100ms):
* - 若窗口内无任何字节:立即发送 brain 探针,等待 DETECT_BRAIN_PROBE_RESP_TIMEOUT_MS(默认200ms)
* - 若窗口内已有字节:进入正常窗口(DETECT_WINDOW_MS,默认1.1s)收集更多数据后滑动判别 BIS/BLOOD/MUSCLE
* 2) 一旦判别成功:标记 device_present=true、dev_type=对应类型,立刻刷新 last_recv
* - BRAIN_O2 心跳(已绑定端口):
* - 每秒发送一次 BRAIN_PROBE(1Hz),其响应由主线程捕获并更新 last_recv
*
* 热插拔与状态上报
* - 拔出判据:已绑定端口若在 HOTPLUG_INACTIVE_UNBIND_SEC(默认3s)内未收到任何设备数据,则认为设备拔出,解绑后自动进入下一轮探测。
* - 状态帧上报周期:STATUS_REPORT_INTERVAL_SEC(默认1s);用于定期报告四个端口的绑定状态(设备类型或未知)。
* - 终端日志:每次状态上报会打印 [STATUS] ...,拔出解绑会打印 [HOTPLUG] ...,可用于现场调试。
*
* 并发与锁
* - main_tx_lock:保护对 main_fd 的写(main_write_frame),确保一次写入完整帧,避免交错。
* - 每个设备端口独立的 mutex:保护对端口属性(device_present/dev_type/last_recv/last_brain_probe)的读写与设备写操作。
* - IO为非阻塞模式:结合 select/poll 实现定时等待和非阻塞读写。
*
* 内存与栈
* - 线程栈:4KB(pthread_attr_setstacksize(&attr, 4096)),注意避免函数大栈对象。
* - 大缓冲:静态全局区管理:
* - g_detect_probe_buf:3KB,用于探测阶段的窗口与探针响应缓存
* - g_main_rxbuf/g_main_pktbuf:各512B,用于 MAIN 下行接收/解析
* - g_port_rxbuf/g_port_pktbuf:各512B,用于设备上行到 MAIN 的封装
*
* 可调宏与推荐范围
* - STATUS_REPORT_INTERVAL_SEC:1(1~5)秒
* - HOTPLUG_INACTIVE_UNBIND_SEC:3(1~10)秒,根据现场实际掉包/静默情况调整
* - DETECT_BRAIN_PROBE_RESP_TIMEOUT_MS:200(100~300)ms,brain 探针等待时间
* - DETECT_BRAIN_PROBE_GAP_MS:1(1~10)ms,探针发送后的微小延时
* - DETECT_IDLE_WINDOW_MS:100(50~200)ms,无设备时首轮探测加速关键参数
* - DETECT_WINDOW_MS:1100 ms,有活动数据时的收集窗口,影响误判率与识别鲁棒性
* - DETECT_POLL_STEP_MS:20 ms,窗口内 poll 的步进,越小越灵敏但CPU占用略增
*
* 已知限制/注意事项
* - 若设备协议会对未知帧敏感(例如对 BRAIN_PROBE 帧做出异常响应),请评估“先探针”策略的影响;目前预期设备忽略无关帧或无副作用。
* - dev_id 广播语义:MAIN->设备的下行可能同时到达多个相同类型的已绑定端口,设备端需要容忍。
* - 本地控制指令(dev_id=0xFF)暂未实现,后续可扩展(如动态修改宏参数/日志级别等)。
*
* 调试开关
* - DEBUG_MAIN_RX / DEBUG_MAIN_TX / DEBUG_DEVICE_RX / DEBUG_DETECT:按需打开,可观测帧内容与流程,但注意输出过多可能影响实时性。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <pthread.h>
#include <sys/time.h>
#include <poll.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <errno.h>
// ===== 可选调试开关 =====
// #define DEBUG_MAIN_RX
// #define DEBUG_MAIN_TX
// #define DEBUG_DEVICE_RX
// #define DEBUG_DETECT
// 串口设备路径(根据硬件实际映射)
#define UART_MAIN "/dev/ttyS1"
#define UART_DEVICEA "/dev/ttyS2"
#define UART_DEVICEB "/dev/ttyS3"
#define UART_DEVICEC "/dev/ttyS4"
#define UART_DEVICED "/dev/ttyS5"
// 波特率(MAIN 较高,设备端适中)
#define UART_MAIN_BAUD 460800
#define UART_DEVICE_BAUD 115200
#define DEVICE_NUM 4
#define PORT_NUM 4
// 状态上报周期(秒),每次会上送四个端口的设备类型快照,并在终端打印 [STATUS]
#define STATUS_REPORT_INTERVAL_SEC 1
// 热插拔:已绑定端口若超过该时间没有任何设备数据,则自动解绑,随后重新进入探测流程
#define HOTPLUG_INACTIVE_UNBIND_SEC 3
// Brain 探针交互参数
#define DETECT_BRAIN_PROBE_RESP_TIMEOUT_MS 200 // 探针后等待响应的超时时间
#define DETECT_BRAIN_PROBE_GAP_MS 1 // 探针发送后的微小延时(ms)
// 探测窗口大小(单位:毫秒)
#define PROBE_BUF_SIZE 3000 // 探测缓存大小(静态全局)
#define DETECT_WINDOW_MS 1100 // 正常窗口:1.1s(有数据时)
#define DETECT_IDLE_WINDOW_MS 100 // 空闲快速窗口:建议100ms(无数据时尽快走brain探针)
#define DETECT_POLL_STEP_MS 20 // poll 步进,灵敏度与CPU负载的折中
// MAIN 帧头尾宏数组(减少魔数)
#define MAIN_HEADER_LEN 3
#define MAIN_TAIL_LEN 3
static const uint8_t MAIN_HEADER[MAIN_HEADER_LEN] = {0xAA, 0xAA, 0xAA};
static const uint8_t MAIN_TAIL[MAIN_TAIL_LEN] = {0xFF, 0xFF, 0xFF};
// 缓冲区统一尺寸(MAIN与设备的收发缓存)
#define MAIN_RX_BUF_SIZE 512
#define PORT_RX_BUF_SIZE 512
// 状态上报帧长度(plen=6: dev_id(FF)+sub(00)+dev_types[4])
#define STATUS_PLEN 6
#define STATUS_FRAME_TOTAL_LEN (MAIN_HEADER_LEN + 2 /*len*/ + STATUS_PLEN + 1 /*crc*/ + MAIN_TAIL_LEN)
// 设备类型枚举(dev_id 同步使用)
typedef enum {
DEV_BIS = 0,
DEV_BRAIN_O2 = 1,
DEV_MUSCLE = 2,
DEV_BLOOD = 3,
DEV_UNKNOWN = 0xFF
} device_type_t;
// 每个设备端口的上下文信息
typedef struct {
char name[32]; // 端口名称(/dev/ttySx)
int fd; // 文件描述符
bool device_present; // 是否已绑定某设备
device_type_t dev_type; // 已绑定设备类型
struct timeval last_recv; // 最近一次收到“设备数据”的时间(不含 MAIN 下行)
pthread_mutex_t lock; // 端口级互斥锁
time_t last_brain_probe; // 仅用于 BRAIN_O2 已绑定时的 1Hz 探针节流
} uart_port_t;
// 设备特征(滑动检测所需特征头/响应)
static const uint8_t bis_header[3] = {0x66, 0x66, 0x66};
static const uint8_t MUSCLE_HEADER[10] = {0xFA,0x0C,0x19,0x04,0x00,0x00,0x00,0x00,0x00,0x01};
static const uint8_t blood_header[3] = {0xFA, 0xFA, 0xFA};
static const uint8_t BLOOD_MID[2] = {0x00,0x17};
// brain_o2 探针与响应片段
static const uint8_t BRAIN_PROBE[19] = {0xEF,0xEF,0xEF,0x0C,0x11,0x25,0x01,0x41,0x52,0x46,0x4C,0x41,0x4C,0x46,0x4C,0x7B,0xFE,0xFE,0xFE};
static const uint8_t BRAIN_RESP[6] = {0xEF,0xEF,0xEF,0x11,0x11,0x25};
// 设备端口路径数组(顺序即端口索引)
static const char *device_port_names[PORT_NUM] = {UART_DEVICEA, UART_DEVICEB, UART_DEVICEC, UART_DEVICED};
// 全局端口数组与 MAIN 串口fd
static uart_port_t uart_ports[PORT_NUM];
static int main_fd = -1;
// MAIN 串口写锁,确保同一时刻只有一个线程对 main_fd 进行帧写,避免短写/交错
static pthread_mutex_t main_tx_lock = PTHREAD_MUTEX_INITIALIZER;
// 静态全局缓冲(避免大栈)
// detect 线程使用的探测缓冲(窗口/探针)
static uint8_t g_detect_probe_buf[PROBE_BUF_SIZE];
// main_rx 线程使用的收包/临时解析缓冲
static uint8_t g_main_rxbuf[MAIN_RX_BUF_SIZE];
static uint8_t g_main_pktbuf[MAIN_RX_BUF_SIZE];
// 主线程(设备->MAIN)使用的收包/封帧缓冲
static uint8_t g_port_rxbuf[PORT_RX_BUF_SIZE];
static uint8_t g_port_pktbuf[PORT_RX_BUF_SIZE];
// 获取当前时间(us精度),封装一层以便后续如需替换为clock_gettime等
static void get_now(struct timeval *tv) { gettimeofday(tv, NULL); }
// 计算毫秒差值(now - last)
static long time_diff_ms(const struct timeval *last, const struct timeval *now) {
return (now->tv_sec - last->tv_sec) * 1000L + (now->tv_usec - last->tv_usec) / 1000L;
}
// 计算秒差值(now - last),用于超时判据
static long time_diff_sec(const struct timeval *last, const struct timeval *now) {
return (now->tv_sec - last->tv_sec);
}
// 简单和校验(低8位),用于 MAIN 帧 CRC
static uint8_t calc_crc(const uint8_t *data, size_t len) {
uint32_t sum = 0;
for (size_t i = 0; i < len; ++i) sum += data[i];
return (uint8_t)(sum & 0xFF);
}
// 打开串口并设置为原始模式(非阻塞)
static int uart_open(const char *dev, int baud) {
int fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
perror("open uart");
return -1;
}
struct termios tio;
memset(&tio, 0, sizeof(tio));
if (tcgetattr(fd, &tio) != 0) {
perror("tcgetattr");
close(fd);
return -1;
}
cfmakeraw(&tio);
tio.c_cflag |= (CLOCAL | CREAD);
tio.c_cflag &= ~CSIZE;
tio.c_cflag |= CS8;
#ifdef CRTSCTS
tio.c_cflag &= ~CRTSCTS; // 关闭硬件流控,以免阻塞
#endif
// 设定波特率(若系统不支持 B460800,会回落到 B115200)
speed_t speed;
switch (baud) {
case 115200: speed = B115200; break;
#ifdef B460800
case 460800: speed = B460800; break;
#endif
default: speed = B115200; break;
}
cfsetispeed(&tio, speed);
cfsetospeed(&tio, speed);
if (tcsetattr(fd, TCSANOW, &tio) != 0) {
perror("tcsetattr");
close(fd);
return -1;
}
return fd;
}
// 在给定超时内等待并读取(用于探针响应等待等简短读取)
static ssize_t uart_read_timeout(int fd, uint8_t *buf, size_t len, int timeout_ms) {
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
int ret = select(fd+1, &rfds, NULL, NULL, &tv);
if (ret <= 0) return 0;
return read(fd, buf, len);
}
// 循环完整写(处理非阻塞短写/EAGAIN),必要时以 poll 等待可写;用于 main_fd 与设备fd
static ssize_t write_full_poll(int fd, const uint8_t *buf, size_t len, int per_poll_timeout_ms) {
size_t off = 0;
while (off < len) {
ssize_t n = write(fd, buf + off, len - off);
if (n > 0) {
#ifdef DEBUG_MAIN_TX
// printf("[TX] write %zd/%zu (fd=%d)\n", n, len - off, fd);
#endif
off += (size_t)n;
continue;
}
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
struct pollfd pfd = { .fd = fd, .events = POLLOUT, .revents = 0 };
int pret = poll(&pfd, 1, per_poll_timeout_ms > 0 ? per_poll_timeout_ms : 50);
if (pret <= 0) {
// 超时/中断后继续重试,避免丢帧;也可根据需求增加总体超时逻辑
continue;
}
continue;
} else if (n == 0) {
// 理论上很少发生,按可重试处理
continue;
} else {
#ifdef DEBUG_MAIN_TX
perror("[TX] write error");
#endif
return -1;
}
}
return (ssize_t)off;
}
// 包装 MAIN 写入(带全局互斥,保证“整帧原子性”)
static void main_write_frame(const uint8_t *buf, size_t len) {
pthread_mutex_lock(&main_tx_lock);
(void)write_full_poll(main_fd, buf, len, 50);
pthread_mutex_unlock(&main_tx_lock);
}
// 设备端口写入(端口锁应在调用前已加;与 MAIN 写相同的“完整写”策略)
static void device_write_frame_locked(uart_port_t *port, const uint8_t *buf, size_t len) {
(void)write_full_poll(port->fd, buf, len, 50);
}
// 十六进制打印辅助(默认限制前64字节,避免刷屏)
static void print_hex(const char *prefix, const uint8_t *buf, size_t len) {
printf("%s", prefix);
size_t limit = len < 64 ? len : 64; // 打印前 64 字节
for (size_t i = 0; i < limit; ++i) printf("%02X ", buf[i]);
if (len > limit) printf("... ");
printf("(len=%zu)\n", len);
}
// ========== 滑动窗口检测分拆(尽量只读,不改缓冲内容) ==========
// BIS:检测到连续 bis_header(0x66 0x66 0x66)即认为存在
static device_type_t probe_bis(const uint8_t *buf, size_t len) {
// 使用 bis_header 宏数组替代硬编码 0x66
for (size_t i = 0; i + sizeof(bis_header) - 1 < len; ++i) {
if (memcmp(buf + i, bis_header, sizeof(bis_header)) == 0)
return DEV_BIS;
}
return DEV_UNKNOWN;
}
// BLOOD:检测到 blood_header(0xFA 0xFA 0xFA)且偏移+7/+8为 BLOOD_MID(0x00 0x17)
static device_type_t probe_blood(const uint8_t *buf, size_t len) {
// 使用 blood_header 宏数组替代硬编码 0xFA
for (size_t i = 0; i + 8 < len; ++i) {
if (memcmp(buf + i, blood_header, sizeof(blood_header)) == 0 &&
buf[i + 7] == BLOOD_MID[0] && buf[i + 8] == BLOOD_MID[1]) {
return DEV_BLOOD;
}
}
return DEV_UNKNOWN;
}
// MUSCLE:窗口中完整匹配 MUSCLE_HEADER(10字节)
static device_type_t probe_muscle(const uint8_t *buf, size_t len) {
for (size_t i = 0; i + 9 < len; ++i) {
if (memcmp(buf + i, MUSCLE_HEADER, 10) == 0) return DEV_MUSCLE;
}
return DEV_UNKNOWN;
}
// brain_o2:响应中出现 BRAIN_RESP(6字节)表示存在
static device_type_t probe_brain(const uint8_t *buf, size_t len) {
for (size_t i = 0; i + 5 < len; ++i) {
if (memcmp(buf + i, BRAIN_RESP, 6) == 0) return DEV_BRAIN_O2;
}
return DEV_UNKNOWN;
}
// ========== main_rx线程(解析 MAIN->设备 下行) ==========
// 职责:从 MAIN 串口接收帧;校验头/长度/CRC/尾;将 payload 广播写入对应“设备类型”的所有已绑定端口。
// 注意:已修复 off-by-one;按照 need = 3(头)+2(长度)+plen(dev_id+payload)+1(CRC)+3(尾) 解析。
static void *main_rx_thread(void *arg) {
uint8_t *rxbuf = g_main_rxbuf; // MAIN_RX_BUF_SIZE
uint8_t *pkt = g_main_pktbuf; // MAIN_RX_BUF_SIZE
size_t rxlen = 0;
while (1) {
ssize_t n = read(main_fd, rxbuf + rxlen, MAIN_RX_BUF_SIZE - rxlen);
if (n > 0) rxlen += (size_t)n;
size_t pos = 0;
while (rxlen - pos >= (MAIN_HEADER_LEN + 2)) { // 至少头+长度
if (memcmp(rxbuf + pos, MAIN_HEADER, MAIN_HEADER_LEN) == 0) {
if (rxlen - pos < (MAIN_HEADER_LEN + 2)) break; // 不足以读长度
uint16_t plen = ((uint16_t)rxbuf[pos+3] << 8) | (uint16_t)rxbuf[pos+4]; // dev_id(1)+payload
size_t need = MAIN_HEADER_LEN + 2 + plen + 1 /*crc*/ + MAIN_TAIL_LEN;
if (rxlen - pos < need) break;
memcpy(pkt, rxbuf + pos, need);
uint8_t dev_id = pkt[5];
uint8_t crc = pkt[6 + plen - 1];
if (crc != calc_crc(pkt + 3, (size_t)(2 + plen))) { // lenH,lenL,dev_id+payload
pos += 1;
continue;
}
if (memcmp(pkt + 6 + plen, MAIN_TAIL, MAIN_TAIL_LEN) != 0) {
pos += 1;
continue;
}
#ifdef DEBUG_MAIN_RX
// print_hex("[MAIN_RX] got frame: ", pkt, need);
#endif
if (dev_id == 0xFF) {
// 本地指令,预留(如配置/查询/复位等),当前不处理
} else if (dev_id < DEVICE_NUM) {
// 广播给所有绑定为该类型的端口(剥离 MAIN 外层,仅发 payload)
for (int i = 0; i < PORT_NUM; ++i) {
uart_port_t *port = &uart_ports[i];
pthread_mutex_lock(&port->lock);
if (port->device_present && port->dev_type == dev_id) {
device_write_frame_locked(port, pkt + 6, (size_t)(plen - 1)); // plen = 1+payload
}
pthread_mutex_unlock(&port->lock);
}
}
pos += need; // 正常推进到下一帧
} else {
pos += 1; // 未对齐帧头,滑动继续寻找
}
}
if (pos > 0) {
if (pos < rxlen) memmove(rxbuf, rxbuf + pos, rxlen - pos);
rxlen -= pos;
}
usleep(1000);
}
return NULL;
}
// ========== detect_thread(方案A:空闲快速窗口 + 正常窗口 + 心跳 + 状态上报 + 拔出解绑) ==========
// 职责:
// 1) 对未绑定端口进行设备类型探测:
// - 先跑“空闲快速窗口”(DETECT_IDLE_WINDOW_MS),若无字节立刻发送 brain 探针并等待响应;若有字节则扩展为“正常窗口”(DETECT_WINDOW_MS)后滑动判别 BIS/BLOOD/MUSCLE。
// 2) 对已绑定 BRAIN_O2 端口每秒发送一次探针(1Hz心跳)。设备响应由主线程读到并刷新 last_recv。
// 3) 热插拔:对已绑定端口,若超过 HOTPLUG_INACTIVE_UNBIND_SEC 没有任何设备数据,自动解绑并在后续周期重启探测。
// 4) 每 STATUS_REPORT_INTERVAL_SEC 秒向 MAIN 上报一次所有端口的设备类型状态(并在终端打印 [STATUS])。
static void *detect_thread(void *arg) {
uint8_t *probe_buf = g_detect_probe_buf; // 3KB,避免大栈
struct timeval now, idle_start, win_start, tcur;
time_t last_report = 0;
while (1) {
get_now(&now);
// 拔出检测:已绑定端口超过阈值无数据则解绑(触发重新探测)
for (int i = 0; i < PORT_NUM; ++i) {
uart_port_t *port = &uart_ports[i];
pthread_mutex_lock(&port->lock);
if (port->device_present && time_diff_sec(&port->last_recv, &now) > HOTPLUG_INACTIVE_UNBIND_SEC) {
printf("[HOTPLUG] Inactive %lds -> unbind %s (type=%d)\n",
(long)HOTPLUG_INACTIVE_UNBIND_SEC, port->name, port->dev_type);
port->device_present = false;
port->dev_type = DEV_UNKNOWN;
port->last_brain_probe = 0;
}
pthread_mutex_unlock(&port->lock);
}
// 探测所有未绑定端口 + BRAIN_O2心跳
time_t now_s = time(NULL);
for (int i = 0; i < PORT_NUM; ++i) {
uart_port_t *port = &uart_ports[i];
pthread_mutex_lock(&port->lock);
if (!port->device_present) {
size_t probe_len = 0;
memset(probe_buf, 0, PROBE_BUF_SIZE);
// 1) 空闲快速窗口:尽量在短时间内发现“是否有任何字节”
get_now(&idle_start);
while (1) {
get_now(&tcur);
if (time_diff_ms(&idle_start, &tcur) >= DETECT_IDLE_WINDOW_MS || probe_len >= PROBE_BUF_SIZE)
break;
struct pollfd pfd = { .fd = port->fd, .events = POLLIN, .revents = 0 };
int pret = poll(&pfd, 1, DETECT_POLL_STEP_MS);
if (pret > 0 && (pfd.revents & POLLIN)) {
ssize_t n = read(port->fd, probe_buf + probe_len, PROBE_BUF_SIZE - probe_len);
if (n > 0) probe_len += (size_t)n;
}
}
if (probe_len > 0) {
// 2) 已经有字节,扩展为正常窗口,收集更多数据后进行滑动判别
get_now(&win_start);
while (1) {
get_now(&tcur);
if (time_diff_ms(&win_start, &tcur) >= DETECT_WINDOW_MS || probe_len >= PROBE_BUF_SIZE)
break;
struct pollfd pfd = { .fd = port->fd, .events = POLLIN, .revents = 0 };
int pret = poll(&pfd, 1, DETECT_POLL_STEP_MS);
if (pret > 0 && (pfd.revents & POLLIN)) {
ssize_t n = read(port->fd, probe_buf + probe_len, PROBE_BUF_SIZE - probe_len);
if (n > 0) probe_len += (size_t)n;
}
}
// 3) 滑动判别(优先顺序:BIS -> BLOOD -> MUSCLE)
device_type_t dtype = DEV_UNKNOWN;
if ((dtype = probe_bis(probe_buf, probe_len)) == DEV_UNKNOWN) {
if ((dtype = probe_blood(probe_buf, probe_len)) == DEV_UNKNOWN) {
dtype = probe_muscle(probe_buf, probe_len);
}
}
if (dtype != DEV_UNKNOWN) {
port->device_present = true;
port->dev_type = dtype;
get_now(&port->last_recv);
port->last_brain_probe = 0;
#ifdef DEBUG_DETECT
printf("[DETECT] Device %d detected on %s (by data, %zuB)\n", dtype, port->name, probe_len);
#endif
}
} else {
// 空闲窗口无任何数据:快速走 brain 探针
device_write_frame_locked(port, BRAIN_PROBE, sizeof(BRAIN_PROBE)); // 19字节
usleep(DETECT_BRAIN_PROBE_GAP_MS * 1000);
ssize_t n = uart_read_timeout(port->fd, probe_buf, PROBE_BUF_SIZE, DETECT_BRAIN_PROBE_RESP_TIMEOUT_MS);
if (n > 0) {
if (probe_brain(probe_buf, (size_t)n) == DEV_BRAIN_O2) {
port->device_present = true;
port->dev_type = DEV_BRAIN_O2;
get_now(&port->last_recv);
port->last_brain_probe = now_s; // 绑定即刻记录,下一次心跳基于 1Hz
#ifdef DEBUG_DETECT
printf("[DETECT] brain_o2 detected on %s (probe)\n", port->name);
#endif
} else {
#ifdef DEBUG_DETECT
printf("[DETECT] Brain probe response NOT matched on %s\n", port->name);
#endif
}
} else {
#ifdef DEBUG_DETECT
printf("[DETECT] Brain probe no response on %s (timeout)\n", port->name);
#endif
}
}
} else {
// BRAIN_O2 已绑定:按 1Hz 发送探针作为心跳
if (port->dev_type == DEV_BRAIN_O2) {
if (now_s - port->last_brain_probe >= 1) {
device_write_frame_locked(port, BRAIN_PROBE, sizeof(BRAIN_PROBE));
port->last_brain_probe = now_s;
}
}
}
pthread_mutex_unlock(&port->lock);
}
// 周期性状态上报(每 STATUS_REPORT_INTERVAL_SEC 秒一次)
time_t now_s2 = time(NULL);
if (now_s2 - last_report >= STATUS_REPORT_INTERVAL_SEC) {
last_report = now_s2;
uint8_t dev_types[PORT_NUM];
for (int i = 0; i < PORT_NUM; ++i) {
pthread_mutex_lock(&uart_ports[i].lock);
dev_types[i] = uart_ports[i].device_present ? uart_ports[i].dev_type : DEV_UNKNOWN;
pthread_mutex_unlock(&uart_ports[i].lock);
}
// 组帧: HEADER + LEN(0x0006) + dev_id(FF) + sub(00) + dev_types[4] + CRC + TAIL
uint8_t buf[STATUS_FRAME_TOTAL_LEN] = {0};
size_t o = 0;
memcpy(buf + o, MAIN_HEADER, MAIN_HEADER_LEN); o += MAIN_HEADER_LEN;
buf[o++] = 0x00; buf[o++] = STATUS_PLEN; // 大端长度
buf[o++] = 0xFF; // dev_id = 0xFF(本地)
buf[o++] = 0x00; // 子命令(预留)
buf[o++] = dev_types[0];
buf[o++] = dev_types[1];
buf[o++] = dev_types[2];
buf[o++] = dev_types[3];
buf[o++] = calc_crc(buf + MAIN_HEADER_LEN, 2 + STATUS_PLEN); // lenH,lenL, dev_id..dev_types
memcpy(buf + o, MAIN_TAIL, MAIN_TAIL_LEN); o += MAIN_TAIL_LEN;
// 防交错写(带锁)发送至 MAIN
main_write_frame(buf, sizeof(buf));
// 终端可读打印,便于观测端口在线类型(UNKNOWN=0xFF)
printf("[STATUS] %ld s ports: %s(%d) | %s(%d) | %s(%d) | %s(%d)\n",
(long)now_s2,
uart_ports[0].name, dev_types[0],
uart_ports[1].name, dev_types[1],
uart_ports[2].name, dev_types[2],
uart_ports[3].name, dev_types[3]);
}
// 50ms tick:使 detect 线程拥有较好的响应性,同时避免高占用
usleep(50000);
}
return NULL;
}
// ========== 主函数:初始化 + 线程创建 + 主循环(设备->MAIN) ==========
// 主循环职责:
// - 每次遍历收集当前所有“已绑定端口”,使用 poll 监听其可读事件;
// - 一旦某个端口有数据可读,读出 payload,包装为 MAIN 上行帧并发送(带 main_tx_lock 保护);
// - 第一时间刷新端口 last_recv,以免被 detect 线程误认为拔出。
int main(void) {
// 打开 MAIN 串口
main_fd = uart_open(UART_MAIN, UART_MAIN_BAUD);
if (main_fd < 0) { printf("Failed to open MAIN uart\n"); return -1; }
// 打开四个设备端口并初始化上下文
for (int i = 0; i < PORT_NUM; ++i) {
uart_ports[i].fd = uart_open(device_port_names[i], UART_DEVICE_BAUD);
if (uart_ports[i].fd < 0) { printf("Failed to open %s\n", device_port_names[i]); return -1; }
strncpy(uart_ports[i].name, device_port_names[i], sizeof(uart_ports[i].name)-1);
uart_ports[i].name[sizeof(uart_ports[i].name)-1] = '\0';
uart_ports[i].device_present = false;
uart_ports[i].dev_type = DEV_UNKNOWN;
get_now(&uart_ports[i].last_recv);
uart_ports[i].last_brain_probe = 0;
pthread_mutex_init(&uart_ports[i].lock, NULL);
}
// 创建线程(使用小栈,避免大栈对象)
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 4096); // 4KB栈
pthread_t rx_tid, detect_tid;
if (pthread_create(&rx_tid, &attr, main_rx_thread, NULL) != 0) {
perror("pthread_create main_rx_thread");
return -1;
}
if (pthread_create(&detect_tid, &attr, detect_thread, NULL) != 0) {
perror("pthread_create detect_thread");
return -1;
}
pthread_attr_destroy(&attr);
// 主循环:poll 已绑定端口,进行设备->MAIN 封帧上报
struct pollfd pollfds[PORT_NUM];
int portidx[PORT_NUM];
while (1) {
int ndev = 0;
// 收集当前已绑定端口,准备 poll
for (int i = 0; i < PORT_NUM; ++i) {
pthread_mutex_lock(&uart_ports[i].lock);
if (uart_ports[i].device_present) {
pollfds[ndev].fd = uart_ports[i].fd;
pollfds[ndev].events = POLLIN;
pollfds[ndev].revents = 0;
portidx[ndev] = i;
++ndev;
}
pthread_mutex_unlock(&uart_ports[i].lock);
}
// 监听可读事件(100ms 超时),避免占用过高
int ret = poll(pollfds, ndev, 100);
if (ret > 0) {
for (int j = 0; j < ndev; ++j) {
if (pollfds[j].revents & POLLIN) {
int i = portidx[j];
uart_port_t *port = &uart_ports[i];
pthread_mutex_lock(&port->lock);
uint8_t *rxbuf = g_port_rxbuf; // PORT_RX_BUF_SIZE
uint8_t *pkt = g_port_pktbuf; // PORT_RX_BUF_SIZE
ssize_t n = read(port->fd, rxbuf, PORT_RX_BUF_SIZE);
if (n > 0) {
// 刷新“最近活跃”时间,避免被 detect 线程当作拔出
get_now(&port->last_recv);
#ifdef DEBUG_DEVICE_RX
// print_hex("[DEV_RX] ", rxbuf, (size_t)n);
#endif
// 组装 MAIN 上行帧:HEADER + LEN + dev_id + payload + CRC + TAIL
size_t pktlen = 0;
memcpy(pkt + pktlen, MAIN_HEADER, MAIN_HEADER_LEN); pktlen += MAIN_HEADER_LEN;
uint16_t total_len = (uint16_t)(n + 1); // dev_id(1) + payload(n)
pkt[pktlen++] = (uint8_t)((total_len >> 8) & 0xFF);
pkt[pktlen++] = (uint8_t)(total_len & 0xFF);
pkt[pktlen++] = (uint8_t)port->dev_type;
memcpy(pkt + pktlen, rxbuf, (size_t)n);
pktlen += (size_t)n;
// CRC 覆盖:lenH,lenL,dev_id+payload 共 2+total_len 字节
pkt[pktlen] = calc_crc(pkt + MAIN_HEADER_LEN, (size_t)(2 + total_len));
pktlen += 1;
memcpy(pkt + pktlen, MAIN_TAIL, MAIN_TAIL_LEN); pktlen += MAIN_TAIL_LEN;
// 发送至 MAIN(带锁,防交错)
main_write_frame(pkt, pktlen);
}
pthread_mutex_unlock(&port->lock);
}
}
}
// 主循环休眠1ms,保持良好响应的同时降低占用
usleep(1000);
}
// return 0; // 实际不会到达
}
更多推荐



所有评论(0)