Files
DTU-HMI/src/remoteDisplay.c

270 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称Remote Display
* 模块功能:实现 RemoDispBus 协议,提供远程 LCD 显示与按键转发功能
* 适用平台:通用嵌入式平台
* 作者:王建锋
* 创建日期2026-05-11
* 修改记录:
* 2026-05-11 王建锋 创建初始版本
*/
#include <stdio.h>
#include <string.h>
#include "Drv/lcd/lcd.h"
#include "TCP/tcp.h"
#include "Drv/key.h"
#include "remoteDisplay.h"
#include "remoteDisplayProtocol.h"
/* 回复帧最大长度头4 + 最大 payload(4+LCD_DISPLAYMEMORYSIZE) + crc1 */
#define REMO_REPLY_BUF_SIZE (4 + 4 + LCD_DISPLAYMEMORYSIZE + 1)
/*
* 函数功能计算数据的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;
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 请求并回复显存数据
* 入口参数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;
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, framebuffer + offset, copy_len);
}
else
{
copy_len = 0;
}
send_reply(client, CMD_LCDMEM, payload, 4 + copy_len);
}
/*
* 函数功能:处理 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;
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 请求并更新远程按键信号
* 入口参数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
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NEW;
g_tRemoteKey.byKeyValue = data[0];
}
/*
* 函数功能:处理 CMD_KEEPLIVE 请求并回复空帧
* 入口参数client - 客户端 socket int 有效的 socket 描述符
* 出口参数:无
* 返回值:无
* 限定条件client 已连接且有效
* 函数说明:发送保活响应帧,无数据部分
*/
static void handle_cmd_keeplive(int client) {
send_reply(client, CMD_KEEPLIVE, (const uint8_t*)NULL, 0);
}
/*
* 函数功能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) {
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);
/* ========== 外层循环:主线程未请求退出时,持续等待并接受新客户端 ========== */
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");
/* 为当前客户端初始化独立的协议解析实例 */
protocol_instance_t proto_ins;
protocol_init(&proto_ins);
/* ========== 内层循环:处理当前连接上的 RemoDispBus 请求,直到断开或退出 ========== */
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);
}
}
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 服务并创建服务器线程
* 入口参数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;
}
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;
}