Files
STM32F4-Base/Drivers/BSP/UI/ui_net_sink.c
T

281 lines
8.7 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.
/*
* 模块名称: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;
static uint16_t g_port; /* 监听端口(网络重建后重监听用) */
static uint32_t g_gen; /* 建立监听时的网络代次号(变化 = 网络已重建) */
/*
* 函数功能:计算 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_port = 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;
g_gen = net_get_generation(); /* 成功才记录代次;失败保持旧值以便下轮重试 */
return 0;
}
/*
* 函数功能:非阻塞轮询 RemoDispBus 协议
* 入口参数:无
* 返回值:无
* 限定条件:ui_net_sink_start() 已成功调用
* 函数说明:1. 无客户端时尝试接受连接(不主动上报 INIT,由客户端按需请求)
* 2. 有客户端时非阻塞收帧并解析
* 3. 连接关闭或异常时复位,等待重监听,不阻塞界面刷新
* 4. 网络重建(net_init 代次变化)时关闭旧监听并重建(IP 变更后自愈)
*/
void ui_net_sink_poll(void)
{
/* 网络重建(net_init 代次变化):旧监听/客户端已随芯片复位失效,重建监听 */
if (net_get_generation() != g_gen) {
if (g_listen >= 0) {
net_close(g_listen);
g_listen = -1;
}
g_client = -1;
(void)ui_net_sink_start(g_port); /* 失败则下轮再试(g_gen 未更新) */
return;
}
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;
}
}
}