/* * remoteDisplay.c - TCP 服务器线程实现 * 实现 RemoDispBus 协议:解析 remo_disp_server.py 的请求,返回 lcd 显存数据等。 * 帧格式: [TAG][cmd][len_hi][len_lo][data][crc],CRC = data 区逐字节异或低 8 位。 * 客户端 TAG=0xAA,设备回复 TAG=0xBB。 */ #include #include #include "Drv/lcd.h" #include "TCP/tcp.h" #include "Drv/key.h" #include "remoteDisplay.h" /* RemoDispBus 协议常量(与 remo_disp_server.py 一致) */ #define TAG_CLIENT 0xAAu #define TAG_DEVICE 0xBBu #define CMD_KEEPLIVE 0 #define CMD_INIT 1 #define CMD_KEY 2 #define CMD_LCDMEM 3 /* 计算 data 区 CRC:逐字节异或取低 8 位 */ static uint8_t calc_crc(const uint8_t* data, unsigned int len) { uint8_t crc = 0; while (len--) crc ^= *data++; return crc; } /* 从 recv 缓冲区解析一帧: [TAG][cmd][len_hi][len_lo][data...][crc] * 成功返回 1 并设置 *p_cmd, *p_data, *p_data_len;失败返回 0。 * *p_consume 返回本帧总字节数(含头尾),调用方从缓冲区移除。 */ static int parse_frame(const uint8_t* buf, unsigned int buf_len, uint8_t* p_cmd, const uint8_t** p_data, unsigned int* p_data_len, unsigned int* p_consume) { if (buf_len < 5 || buf[0] != TAG_CLIENT) return 0; unsigned int data_len = ((unsigned int)buf[2] << 8) | buf[3]; unsigned int frame_len = 4 + data_len + 1; /* 头4 + data + crc */ if (buf_len < frame_len) return 0; uint8_t crc = calc_crc(buf + 4, data_len); if (crc != buf[4 + data_len]) return 0; *p_cmd = buf[1]; *p_data = buf + 4; *p_data_len = data_len; *p_consume = frame_len; return 1; } /* 回复帧最大长度:头4 + 最大 payload(4+LCD_DISPLAYMEMORYSIZE) + crc1 */ #define REMO_REPLY_BUF_SIZE (4 + 4 + LCD_DISPLAYMEMORYSIZE + 1) /* 构造设备回复帧并发送: [TAG_DEVICE][cmd][len_hi][len_lo][data][crc],一次性发送 */ static int send_reply(int client, uint8_t cmd, const uint8_t* data, unsigned int data_len) { uint8_t buf[REMO_REPLY_BUF_SIZE]; unsigned int total = 4 + data_len + 1; if (total > REMO_REPLY_BUF_SIZE) return -1; buf[0] = TAG_DEVICE; buf[1] = cmd & 0xFF; buf[2] = (data_len >> 8) & 0xFF; buf[3] = data_len & 0xFF; if (data_len) memcpy(buf + 4, data, data_len); buf[4 + data_len] = calc_crc(data, data_len); return TcpClient_Send(client, (const char*)buf, total) == (int)total ? 0 : -1; } /* 处理 CMD_LCDMEM:请求 data 为 4 字节大端起始地址;回复 [4B 地址][显存数据] */ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int req_len) { unsigned int start_addr = 0; if (req_len >= 4) start_addr = ((unsigned int)req_data[0] << 24) | ((unsigned int)req_data[1] << 16) | ((unsigned int)req_data[2] << 8) | req_data[3]; /* 回复 payload = [4B 地址大端][显存],Python 取 payload[4:] 为位图 */ uint8_t payload[4 + LCD_DISPLAYMEMORYSIZE]; payload[0] = (start_addr >> 24) & 0xFF; payload[1] = (start_addr >> 16) & 0xFF; payload[2] = (start_addr >> 8) & 0xFF; payload[3] = start_addr & 0xFF; unsigned int copy_len = LCD_DISPLAYMEMORYSIZE; if (start_addr < LCD_DISPLAYMEMORYSIZE) { unsigned int offset = start_addr; copy_len = LCD_DISPLAYMEMORYSIZE - offset; memcpy(payload + 4, g_tCVsr.pwbyLCDMemory + offset, copy_len); } else { copy_len = 0; } send_reply(client, CMD_LCDMEM, payload, 4 + copy_len); } /* 处理 CMD_INIT:回复宽、高、显存大小(与 Python 端约定一致时可扩展) */ static void handle_cmd_init(int client) { uint8_t data[8]; data[0] = (LCD_SIZE_X >> 8) & 0xFF; data[1] = LCD_SIZE_X & 0xFF; data[2] = (LCD_SIZE_Y >> 8) & 0xFF; data[3] = LCD_SIZE_Y & 0xFF; data[4] = (LCD_DISPLAYMEMORYSIZE >> 24) & 0xFF; data[5] = (LCD_DISPLAYMEMORYSIZE >> 16) & 0xFF; data[6] = (LCD_DISPLAYMEMORYSIZE >> 8) & 0xFF; data[7] = LCD_DISPLAYMEMORYSIZE & 0xFF; send_reply(client, CMD_INIT, data, 8); } /* 处理 CMD_KEY:可选转交菜单,此处仅回 ACK */ static void handle_cmd_key(int client, const uint8_t* data, unsigned int len) { #ifdef DEBUG printf("handle_cmd_key: 0x%02X\n", data[0]); #endif g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NEW; g_tRemoteKey.byKeyValue = data[0]; } /* 处理 CMD_KEEPLIVE:保活,回空 */ static void handle_cmd_keeplive(int client) { send_reply(client, CMD_KEEPLIVE, (const uint8_t*)NULL, 0); } void tcp_server_thread_fn(void* arg) { server_thread_arg_t* ctx = (server_thread_arg_t*)arg; int server_sock = TcpServer_Listen((uint16_t)ctx->port); if (server_sock == TCP_INVALID_SOCKET) { fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port); return; } *ctx->p_server_sock = server_sock; printf("[TCP Server] RemoDispBus 监听端口 %d,等待客户端连接...\n", ctx->port); #define REMO_BUF_SIZE 4096 uint8_t recv_buf[4096]; unsigned int recv_len = 0; /* ========== 外层循环:主线程未请求退出时,持续等待并接受新客户端 ========== */ while (!*ctx->p_quit) { /* 阻塞等待一个客户端连接;主线程关闭 server_sock 时 Accept 会失败并返回 INVALID */ int client = TcpServer_Accept(server_sock); if (client == TCP_INVALID_SOCKET) { if (*ctx->p_quit) break; continue; } printf("[TCP Server] 客户端连接成功\n"); recv_len = 0; /* 新连接对应新的接收缓冲区,避免混用上一连接的残留数据 */ /* ========== 内层循环:处理当前连接上的 RemoDispBus 请求,直到断开或退出 ========== */ while (!*ctx->p_quit) { /* ----- 1. 若缓冲区中已有一条完整且校验通过的帧,则解析并分发处理 ----- */ uint8_t cmd; const uint8_t* pdata; unsigned int data_len, consume; if (parse_frame(recv_buf, recv_len, &cmd, &pdata, &data_len, &consume)) { switch (cmd) { case CMD_KEEPLIVE: handle_cmd_keeplive(client); break; case CMD_INIT: handle_cmd_init(client); break; case CMD_KEY: handle_cmd_key(client, pdata, data_len); break; case CMD_LCDMEM: handle_cmd_lcdmem(client, pdata, data_len); break; default: send_reply(client, cmd, (const uint8_t*)NULL, 0); break; } /* 从缓冲区头部移除已消费的 consume 字节,避免重复解析同一帧 */ memmove(recv_buf, recv_buf + consume, recv_len - consume); recv_len -= consume; continue; /* 继续检查是否还有完整帧,避免不必要的 recv */ } /* ----- 2. 缓冲区中尚无完整帧:先防止溢出,再收一批数据 ----- */ if (recv_len >= REMO_BUF_SIZE - 256) { recv_len = 0; /* 异常情况:数据过多且始终不成帧,清空缓冲区防止越界 */ } int n = TcpClient_Recv(client, (char*)(recv_buf + recv_len), REMO_BUF_SIZE - recv_len - 1); if (n > 0) { recv_len += (unsigned int)n; } else { /* n==0 表示对方正常关闭连接,n<0 表示读取出错;均退出本连接循环 */ printf("[TCP Server] 客户端关闭连接\n"); break; } } /* 当前客户端处理结束,关闭该连接;外层循环继续 Accept 下一个客户端 */ TcpClient_Close(client); } TcpServer_Close(server_sock); *ctx->p_server_sock = TCP_INVALID_SOCKET; printf("[TCP Server] 已退出\n"); } /* ---------------------------------------------------------------------------- * 启动 TCP 服务与服务器线程(Tcp_Init + 创建线程 + 短暂等待就绪) * port: 监听端口(如 7070) * out_server_sock: 输出监听 socket,供主线程退出时 TcpServer_Close * out_server_quit: 输出退出标志,主线程置 1 通知服务器线程退出 * out_server_th: 输出线程句柄,供主线程 Thread_Join * 返回:0 成功,1 失败(已调用 Tcp_Cleanup) * ---------------------------------------------------------------------------- */ int StartTcpServerThread(thread_handle_t *out_server_th, server_thread_arg_t *io_server_arg) { if (Tcp_Init() != 0) { fprintf(stderr, "Tcp_Init failed\n"); return 1; } if (Thread_Create(tcp_server_thread_fn, io_server_arg, out_server_th) != 0) { fprintf(stderr, "Thread_Create(server) failed\n"); Tcp_Cleanup(); return 1; } Sleep(200); return 0; }