265 lines
7.9 KiB
C
265 lines
7.9 KiB
C
/*
|
||
* 模块名称:UI Network Sink (RemoDispBus 服务端)
|
||
* 模块功能:实现 RemoDispBus 协议服务端,接收远端连接与导航按键,回送 1bpp 帧缓冲
|
||
* 适用平台:STM32F407ZGTx + CH395F 以太网
|
||
* 作者:王建锋
|
||
* 创建日期:2026-08-29
|
||
* 修改记录:
|
||
* 2026-08-29 王建锋 创建初始版本
|
||
* 2026-08-29 王建锋 修正监听端口为 7003;移除连接后主动上报 INIT(避免污染客户端首帧读取)
|
||
*/
|
||
#include "ui_net_sink.h"
|
||
|
||
#include <string.h>
|
||
#include "ui_port.h"
|
||
#include "net_socket.h"
|
||
#include "net_types.h"
|
||
|
||
/* ---------------- RemoDispBus 协议 ---------------- */
|
||
#define TAG_CLIENT 0xAA
|
||
#define TAG_DEVICE 0xBB
|
||
#define CMD_KEEPLIVE 0
|
||
#define CMD_INIT 1
|
||
#define CMD_KEY 2
|
||
#define CMD_LCDMEM 3
|
||
|
||
#define UI_W 160
|
||
#define UI_H 160
|
||
#define UI_MEMSIZE (UI_W * UI_H / 8) /* 3200 字节 */
|
||
|
||
/* 发送帧缓冲(最大:LCDMEM 应答 = 4 字节偏移 + 3200 帧 + 头4 + 校验1) */
|
||
static uint8_t g_tx[4 + 4 + UI_MEMSIZE + 1];
|
||
/* LCDMEM 应答数据区(4 字节大端偏移 + 整帧) */
|
||
static uint8_t g_lcdmem_data[4 + UI_MEMSIZE];
|
||
static uint8_t g_rx[64];
|
||
|
||
static int g_listen = -1;
|
||
static int g_client = -1;
|
||
|
||
/*
|
||
* 函数功能:计算 RemoDispBus 数据段异或校验值
|
||
* 入口参数:d - 数据首地址 const uint8_t * 非空
|
||
* len - 数据长度 int >= 0
|
||
* 返回值:校验值 uint8_t 0 - 255
|
||
* 限定条件:无
|
||
* 函数说明:逐字节异或,结果为单字节,与 DTU-HMI 协议保持一致
|
||
*/
|
||
static uint8_t remo_crc(const uint8_t *d, int len)
|
||
{
|
||
uint8_t c = 0;
|
||
for (int i = 0; i < len; i++) {
|
||
c ^= d[i];
|
||
}
|
||
return c;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:以大端序写入 16 位无符号整数
|
||
* 入口参数:p - 目标缓冲区 uint8_t * 至少 2 字节
|
||
* v - 待写入数值 uint16_t 0 - 65535
|
||
* 返回值:无
|
||
* 限定条件:p 指向有效内存
|
||
* 函数说明:高位在前、低位在后(网络字节序)
|
||
*/
|
||
static void put_u16(uint8_t *p, uint16_t v)
|
||
{
|
||
p[0] = (uint8_t)(v >> 8);
|
||
p[1] = (uint8_t)(v & 0xFF);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:以大端序写入 32 位无符号整数
|
||
* 入口参数:p - 目标缓冲区 uint8_t * 至少 4 字节
|
||
* v - 待写入数值 uint32_t 0 - 0xFFFFFFFF
|
||
* 返回值:无
|
||
* 限定条件:p 指向有效内存
|
||
* 函数说明:最高位在前,依次存放 4 字节(网络字节序)
|
||
*/
|
||
static void put_u32(uint8_t *p, uint32_t v)
|
||
{
|
||
p[0] = (uint8_t)(v >> 24);
|
||
p[1] = (uint8_t)(v >> 16);
|
||
p[2] = (uint8_t)(v >> 8);
|
||
p[3] = (uint8_t)(v & 0xFF);
|
||
}
|
||
|
||
/* 组包并发送一帧(tag = DEVICE) */
|
||
/*
|
||
* 函数功能:组包并发送一帧(设备 -> 客户端)
|
||
* 入口参数:cmd - 命令字 uint8_t CMD_*
|
||
* data - 数据段首地址 const uint8_t * 可为 NULL
|
||
* len - 数据段长度 uint16_t 0 - 65535
|
||
* 返回值:无
|
||
* 限定条件:g_client >= 0(已有连接)
|
||
* 函数说明:1. 帧格式 [TAG_DEVICE][cmd][len_hi][len_lo][data][crc]
|
||
* 2. 无连接时直接返回,不发送
|
||
* 3. 阻塞发送,直至整帧写入 Socket
|
||
*/
|
||
static void remo_send(uint8_t cmd, const uint8_t *data, uint16_t len)
|
||
{
|
||
if (g_client < 0) {
|
||
return;
|
||
}
|
||
uint8_t *p = g_tx;
|
||
p[0] = TAG_DEVICE;
|
||
p[1] = cmd;
|
||
p[2] = (uint8_t)(len >> 8);
|
||
p[3] = (uint8_t)(len & 0xFF);
|
||
if (len && data) {
|
||
memcpy(p + 4, data, len);
|
||
}
|
||
p[4 + len] = remo_crc(data, len);
|
||
net_send(g_client, p, 4 + len + 1, 0);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:发送 INIT 应答(上报分辨率与帧大小)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:g_client >= 0
|
||
* 函数说明:数据段为 [宽][高][帧字节数(4)],供客户端分配显示缓冲
|
||
*/
|
||
static void remo_send_init(void)
|
||
{
|
||
uint8_t init[8];
|
||
put_u16(init, UI_W);
|
||
put_u16(init + 2, UI_H);
|
||
put_u32(init + 4, UI_MEMSIZE);
|
||
remo_send(CMD_INIT, init, 6);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:解析一帧 RemoDispBus 请求并分发处理
|
||
* 入口参数:f - 整帧数据首地址 const uint8_t * 非空
|
||
* n - 帧字节数 int >= 0
|
||
* 返回值:无
|
||
* 限定条件:f 指向完整帧缓冲
|
||
* 函数说明:1. 校验长度与 CRC,标签必须为客户端 TAG
|
||
* 2. 按命令字分发:KEEPLIVE/INIT 应答,KEY 驱动菜单,LCDMEM 回送帧
|
||
* 3. 校验失败或格式错误时静默丢弃
|
||
*/
|
||
static void remo_handle(const uint8_t *f, int n)
|
||
{
|
||
if (n < 6) {
|
||
return;
|
||
}
|
||
uint8_t tag = f[0];
|
||
uint8_t cmd = f[1];
|
||
uint16_t len = ((uint16_t)f[2] << 8) | f[3];
|
||
const uint8_t *data = f + 4;
|
||
if ((int)(4 + len + 1) > n) {
|
||
return; /* 帧不完整 */
|
||
}
|
||
uint8_t crc = f[4 + len];
|
||
if (crc != remo_crc(data, len)) {
|
||
return; /* 校验失败,丢弃 */
|
||
}
|
||
if (tag != TAG_CLIENT) {
|
||
return;
|
||
}
|
||
|
||
switch (cmd) {
|
||
case CMD_KEEPLIVE:
|
||
remo_send(CMD_KEEPLIVE, NULL, 0);
|
||
break;
|
||
case CMD_INIT:
|
||
remo_send_init();
|
||
break;
|
||
case CMD_KEY:
|
||
if (len >= 1) {
|
||
ui_port_queue_key(data[0]);
|
||
}
|
||
remo_send(CMD_KEY, NULL, 0);
|
||
break;
|
||
case CMD_LCDMEM: {
|
||
if (len < 4) {
|
||
break;
|
||
}
|
||
uint32_t off = ((uint32_t)data[0] << 24) |
|
||
((uint32_t)data[1] << 16) |
|
||
((uint32_t)data[2] << 8) |
|
||
(uint32_t)data[3];
|
||
if (off == 0) {
|
||
uint8_t *fb = ui_port_get_framebuffer();
|
||
g_lcdmem_data[0] = data[0];
|
||
g_lcdmem_data[1] = data[1];
|
||
g_lcdmem_data[2] = data[2];
|
||
g_lcdmem_data[3] = data[3];
|
||
memcpy(g_lcdmem_data + 4, fb, UI_MEMSIZE);
|
||
remo_send(CMD_LCDMEM, g_lcdmem_data, 4 + UI_MEMSIZE);
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:启动 UI 专用 TCP 监听 Socket
|
||
* 入口参数:port - 监听端口号 uint16_t 1 - 65535
|
||
* 返回值:0 - 成功,其他 - 失败(socket 创建/绑定/监听失败)
|
||
* 限定条件:net_init() 已完成,网络子系统就绪
|
||
* 函数说明:创建流式 Socket 并绑定端口,进入监听等待连接
|
||
*/
|
||
int ui_net_sink_start(uint16_t port)
|
||
{
|
||
g_listen = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
if (g_listen < 0) {
|
||
return -1;
|
||
}
|
||
struct net_sockaddr_in addr;
|
||
memset(&addr, 0, sizeof(addr));
|
||
addr.sin_family = NET_AF_INET;
|
||
addr.sin_port = net_htons(port);
|
||
addr.sin_addr.s_addr = NET_INADDR_ANY;
|
||
|
||
if (net_bind(g_listen, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
|
||
net_close(g_listen);
|
||
g_listen = -1;
|
||
return -1;
|
||
}
|
||
if (net_listen(g_listen) < 0) {
|
||
net_close(g_listen);
|
||
g_listen = -1;
|
||
return -1;
|
||
}
|
||
g_client = -1;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:非阻塞轮询 RemoDispBus 协议
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:ui_net_sink_start() 已成功调用
|
||
* 函数说明:1. 无客户端时尝试接受连接(不主动上报 INIT,由客户端按需请求)
|
||
* 2. 有客户端时非阻塞收帧并解析
|
||
* 3. 连接关闭或异常时复位,等待重监听,不阻塞界面刷新
|
||
*/
|
||
void ui_net_sink_poll(void)
|
||
{
|
||
if (g_listen < 0) {
|
||
return;
|
||
}
|
||
|
||
if (g_client < 0) {
|
||
int c = net_accept(g_listen, NULL, NULL);
|
||
if (c >= 0) {
|
||
g_client = c;
|
||
}
|
||
return;
|
||
}
|
||
|
||
int n = net_recv(g_client, g_rx, sizeof(g_rx), NET_MSG_DONTWAIT);
|
||
if (n > 0) {
|
||
remo_handle(g_rx, n);
|
||
} else if (n == 0) {
|
||
g_client = -1; /* 对端关闭:等待下次 accept 自动重监听,不释放控制块 */
|
||
} else {
|
||
if (net_get_errno() != NET_ERR_WOULDBLOCK) {
|
||
g_client = -1;
|
||
}
|
||
}
|
||
}
|