/*
 * 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; // 实际不会到达
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐