Files
DTU-HMI/src/remoteDisplay.c

249 lines
9.1 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.
/*
* 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 <stdio.h>
#include <string.h>
#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;
}