远程通信协议更改为 状态机处理
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
/*
|
||||
* remoteDisplay.c - TCP 服务器线程实现
|
||||
* 实现 RemoDispBus 协议:解析 remo_disp_server.py 的请求,返回 lcd 显存数据等。
|
||||
* 帧格式: [TAG][cmd][len_hi][len_lo][data][crc],CRC = data 区逐字节异或低 8 位。
|
||||
* 客户端 TAG=0xAA,设备回复 TAG=0xBB。
|
||||
* 模块名称:Remote Display
|
||||
* 模块功能:实现 RemoDispBus 协议,提供远程 LCD 显示与按键转发功能
|
||||
* 适用平台:通用嵌入式平台
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-05-11
|
||||
* 修改记录:
|
||||
* 2026-05-11 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -12,53 +15,49 @@
|
||||
#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;
|
||||
}
|
||||
#include "remoteDisplayProtocol.h"
|
||||
|
||||
/* 回复帧最大长度:头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)
|
||||
{
|
||||
/*
|
||||
* 函数功能:计算数据的XOR校验和
|
||||
* 入口参数:p_data - 数据缓冲区指针 const uint8_t* 非NULL
|
||||
* len - 数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN
|
||||
* 返回值:校验和值 uint8_t 0 - 255
|
||||
* 限定条件:p_data指针必须指向有效的内存区域,长度至少为len字节
|
||||
* 函数说明:1. 采用XOR算法计算校验和
|
||||
* 2. 将所有数据字节进行异或运算
|
||||
* 3. 当len为0时,返回0
|
||||
*/
|
||||
static uint8_t calc_crc(const uint8_t *p_data, unsigned int len) {
|
||||
uint8_t crc = 0;
|
||||
unsigned int i;
|
||||
|
||||
if (p_data == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
crc ^= p_data[i];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:构造设备回复帧并发送
|
||||
* 入口参数:client - 客户端 socket int 有效的 socket 描述符
|
||||
* cmd - 命令字 uint8_t 参考 RemoDispBus 命令定义
|
||||
* data - 回复数据指针 const uint8_t* 可为 NULL
|
||||
* data_len - 回复数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN
|
||||
* 出口参数:无
|
||||
* 返回值:0 - 发送成功
|
||||
* -1 - 发送失败或参数非法
|
||||
* 限定条件:client 已连接且有效
|
||||
* 函数说明:构造 [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;
|
||||
|
||||
@@ -75,9 +74,17 @@ static int send_reply(int client, uint8_t cmd, const uint8_t* data, unsigned int
|
||||
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)
|
||||
{
|
||||
/*
|
||||
* 函数功能:处理 CMD_LCDMEM 请求并回复显存数据
|
||||
* 入口参数:client - 客户端 socket int 有效的 socket 描述符
|
||||
* req_data - 请求数据指针 const uint8_t* 期望至少 4 个字节
|
||||
* req_len - 请求数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:req_data 可为 NULL,但 req_len 必须有效
|
||||
* 函数说明:解析大端起始地址,读取显存并通过 send_reply 发送结果
|
||||
*/
|
||||
static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int req_len) {
|
||||
const uint8_t *framebuffer = Lcd_GetFrameBuffer();
|
||||
unsigned int start_addr = 0;
|
||||
|
||||
@@ -106,9 +113,15 @@ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int
|
||||
send_reply(client, CMD_LCDMEM, payload, 4 + copy_len);
|
||||
}
|
||||
|
||||
/* 处理 CMD_INIT:回复宽、高、显存大小(与 Python 端约定一致时可扩展) */
|
||||
static void handle_cmd_init(int client)
|
||||
{
|
||||
/*
|
||||
* 函数功能:处理 CMD_INIT 请求并回复 LCD 参数
|
||||
* 入口参数:client - 客户端 socket int 有效的 socket 描述符
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:client 已连接且有效
|
||||
* 函数说明:回复 LCD 宽度、高度和显存大小的 8 字节信息
|
||||
*/
|
||||
static void handle_cmd_init(int client) {
|
||||
uint8_t data[8];
|
||||
data[0] = (LCD_SIZE_X >> 8) & 0xFF;
|
||||
data[1] = LCD_SIZE_X & 0xFF;
|
||||
@@ -121,9 +134,17 @@ static void handle_cmd_init(int client)
|
||||
send_reply(client, CMD_INIT, data, 8);
|
||||
}
|
||||
|
||||
/* 处理 CMD_KEY:可选转交菜单,此处仅回 ACK */
|
||||
static void handle_cmd_key(int client, const uint8_t* data, unsigned int len)
|
||||
{
|
||||
/*
|
||||
* 函数功能:处理 CMD_KEY 请求并更新远程按键信号
|
||||
* 入口参数:client - 客户端 socket int 有效的 socket 描述符
|
||||
* data - 键值数据指针 const uint8_t* 期望至少 1 字节
|
||||
* len - 数据长度 unsigned int 0 - PROTOCOL_MAX_DATA_LEN
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:data 必须包含至少 1 个字节键值
|
||||
* 函数说明:将按键值写入全局远程按键结构,供主循环处理
|
||||
*/
|
||||
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
|
||||
@@ -131,14 +152,26 @@ static void handle_cmd_key(int client, const uint8_t* data, unsigned int len)
|
||||
g_tRemoteKey.byKeyValue = data[0];
|
||||
}
|
||||
|
||||
/* 处理 CMD_KEEPLIVE:保活,回空 */
|
||||
static void handle_cmd_keeplive(int client)
|
||||
{
|
||||
/*
|
||||
* 函数功能:处理 CMD_KEEPLIVE 请求并回复空帧
|
||||
* 入口参数:client - 客户端 socket int 有效的 socket 描述符
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:client 已连接且有效
|
||||
* 函数说明:发送保活响应帧,无数据部分
|
||||
*/
|
||||
static void handle_cmd_keeplive(int client) {
|
||||
send_reply(client, CMD_KEEPLIVE, (const uint8_t*)NULL, 0);
|
||||
}
|
||||
|
||||
void tcp_server_thread_fn(void* arg)
|
||||
{
|
||||
/*
|
||||
* 函数功能:TCP 服务器线程入口函数
|
||||
* 入口参数:arg - 线程参数指针 void* 实际类型为 server_thread_arg_t*
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:arg 必须指向有效的 server_thread_arg_t 实例
|
||||
* 函数说明:创建 TCP 监听 socket,接受客户端连接并按 RemoDispBus 协议处理请求
|
||||
*/
|
||||
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) {
|
||||
@@ -148,69 +181,55 @@ void tcp_server_thread_fn(void* arg)
|
||||
*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)
|
||||
if (client == TCP_INVALID_SOCKET) {
|
||||
if (*ctx->p_quit) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
printf("[TCP Server] 客户端连接成功\n");
|
||||
recv_len = 0; /* 新连接对应新的接收缓冲区,避免混用上一连接的残留数据 */
|
||||
|
||||
/* 为当前客户端初始化独立的协议解析实例 */
|
||||
protocol_instance_t proto_ins;
|
||||
protocol_init(&proto_ins);
|
||||
|
||||
/* ========== 内层循环:处理当前连接上的 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;
|
||||
while (!*ctx->p_quit) {
|
||||
uint8_t recv_byte; /* 逐字节接收 */
|
||||
int n = TcpClient_Recv(client, (uint8_t*)&recv_byte, 1);
|
||||
if (n > 0) { /* 成功收到1字节 */
|
||||
/* 喂给状态机逐字节处理 */
|
||||
protocol_byte_handler(&proto_ins, recv_byte);
|
||||
/* 检查是否有完整帧解析完成 */
|
||||
if (proto_ins.frame_done_flag == 1) {
|
||||
/* 根据解析到的CMD分发处理 */
|
||||
switch (proto_ins.recv_cmd)
|
||||
{
|
||||
case CMD_KEEPLIVE:
|
||||
handle_cmd_keeplive(client);
|
||||
break;
|
||||
case CMD_INIT:
|
||||
handle_cmd_init(client);
|
||||
break;
|
||||
case CMD_KEY:
|
||||
handle_cmd_key(client, proto_ins.recv_buf, proto_ins.expect_data_len);
|
||||
break;
|
||||
case CMD_LCDMEM:
|
||||
handle_cmd_lcdmem(client, proto_ins.recv_buf, proto_ins.expect_data_len);
|
||||
break;
|
||||
default:
|
||||
send_reply(client, proto_ins.recv_cmd, (const uint8_t*)NULL, 0);
|
||||
break;
|
||||
}
|
||||
/* 处理完成后,复位协议实例,准备接收下一帧 */
|
||||
protocol_init(&proto_ins);
|
||||
}
|
||||
/* 从缓冲区头部移除已消费的 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 表示读取出错;均退出本连接循环 */
|
||||
else { /* n==0(客户端关闭)或 n<0(读取错误)*/
|
||||
printf("[TCP Server] 客户端关闭连接\n");
|
||||
break;
|
||||
}
|
||||
@@ -223,16 +242,17 @@ void tcp_server_thread_fn(void* arg)
|
||||
*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)
|
||||
{
|
||||
/*
|
||||
* 函数功能:启动 TCP 服务并创建服务器线程
|
||||
* 入口参数:out_server_th - 输出线程句柄 thread_handle_t* 非空
|
||||
* io_server_arg - 输入输出服务器线程参数 server_thread_arg_t* 非空
|
||||
* 出口参数:out_server_th - 输出创建的线程句柄
|
||||
* 返回值:0 - 成功
|
||||
* 1 - 失败(已调用 Tcp_Cleanup)
|
||||
* 限定条件:io_server_arg 必须初始化并提供有效端口、socket 和退出标志指针
|
||||
* 函数说明:初始化 TCP 子系统并创建服务器线程,若失败则清理资源
|
||||
*/
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user