ui_net_sink 拆分为协议层与平台后端,新增 RemoDispBus 设计文档
This commit is contained in:
@@ -16,9 +16,17 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#include "net_types.h"
|
||||
|
||||
/*
|
||||
* OS 相关接口(FreeRTOS 消息队列 / 任务句柄 / netTask 内部 API)仅在启用
|
||||
* RTOS 的构建(MCU)编译;纯 PC 模拟器定义 NET_NO_OS 后只保留 BSD Socket 核心 API。
|
||||
*/
|
||||
#ifndef NET_NO_OS
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#endif /* NET_NO_OS */
|
||||
|
||||
#ifndef NET_NO_OS
|
||||
/*
|
||||
* 消息队列类型定义
|
||||
*/
|
||||
@@ -43,6 +51,7 @@ typedef struct {
|
||||
struct net_sockaddr_in addr; /* 连接地址 */
|
||||
int addrlen; /* 地址长度 */
|
||||
} net_msg_t;
|
||||
#endif /* NET_NO_OS */
|
||||
|
||||
/*
|
||||
* 网络初始化接口
|
||||
@@ -294,6 +303,7 @@ uint32_t net_htonl(uint32_t hostlong);
|
||||
*/
|
||||
uint32_t net_ntohl(uint32_t netlong);
|
||||
|
||||
#ifndef NET_NO_OS
|
||||
/*
|
||||
* 函数功能:处理消息队列中的请求(必须在 netTask 主循环中调用)
|
||||
* 限定条件:net_init() 已调用
|
||||
@@ -301,6 +311,7 @@ uint32_t net_ntohl(uint32_t netlong);
|
||||
* 在 netTask 上下文中串行化执行所有 CH395F 操作
|
||||
*/
|
||||
void net_process_messages(void);
|
||||
#endif /* NET_NO_OS */
|
||||
|
||||
/*
|
||||
* 函数功能:获取 Socket 控制块指针(内部使用)
|
||||
@@ -319,6 +330,7 @@ net_sock_t *net_get_sock(int sockfd);
|
||||
*/
|
||||
int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen);
|
||||
|
||||
#ifndef NET_NO_OS
|
||||
/*
|
||||
* 内部 API(需在 netTask 上下文中调用,绕过消息队列)
|
||||
*/
|
||||
@@ -359,6 +371,7 @@ int net_close_sock(net_sock_t *p_sock);
|
||||
* 限定条件:必须在 netTask 上下文中调用
|
||||
*/
|
||||
int net_listen_locked(int sockfd);
|
||||
#endif /* NET_NO_OS */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -75,15 +75,20 @@ struct net_sockaddr {
|
||||
char sa_data[14]; /* 地址数据 */
|
||||
};
|
||||
|
||||
/*
|
||||
* IPv4 地址(镜像 BSD in_addr;用命名结构体保证 ARMCC / MSVC 双端兼容)
|
||||
*/
|
||||
struct net_in_addr {
|
||||
uint32_t s_addr; /* IP 地址(网络字节序) */
|
||||
};
|
||||
|
||||
/*
|
||||
* IPv4 套接字地址结构体
|
||||
*/
|
||||
struct net_sockaddr_in {
|
||||
uint16_t sin_family; /* 地址族,必须为 AF_INET */
|
||||
uint16_t sin_port; /* 端口号(网络字节序) */
|
||||
struct {
|
||||
uint32_t s_addr; /* IP 地址(网络字节序) */
|
||||
} sin_addr;
|
||||
struct net_in_addr sin_addr; /* IP 地址(网络字节序) */
|
||||
char sin_zero[8]; /* 填充字节 */
|
||||
};
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* 模块名称:Network Configuration Store (模拟器后端)
|
||||
* 模块功能:net_cfg 存储后端——模拟器工作目录本地文件 sim_netcfg.bin
|
||||
* (磁盘格式与 MCU 端一致,两端可互读)
|
||||
* 适用平台:PC 模拟器
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-10 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <stdio.h>
|
||||
|
||||
#include "net_cfg_store.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
static const char s_path[] = "sim_netcfg.bin";
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:读取配置文件
|
||||
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
|
||||
* len - 文件长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int net_cfg_store_read(uint8_t *p_buf, size_t len) {
|
||||
FILE *fp;
|
||||
size_t n;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fp = fopen(s_path, "rb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
n = fread(p_buf, 1u, len, fp);
|
||||
fclose(fp);
|
||||
return (n == len) ? 0 : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:写配置文件(不存在则创建,存在则覆盖)
|
||||
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 写失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int net_cfg_store_write(const uint8_t *p_buf, size_t len) {
|
||||
FILE *fp;
|
||||
size_t n;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fp = fopen(s_path, "wb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
n = fwrite(p_buf, 1u, len, fp);
|
||||
fclose(fp);
|
||||
return (n == len) ? 0 : -1;
|
||||
}
|
||||
+274
-108
@@ -1,67 +1,76 @@
|
||||
/*
|
||||
* 模块名称:UI Network Sink (RemoDispBus 服务端)
|
||||
* 模块功能:实现 RemoDispBus 协议服务端,接收远端连接与导航按键,回送 1bpp 帧缓冲
|
||||
* 适用平台:STM32F407ZGTx + CH395F 以太网
|
||||
* 模块名称:UI Network Sink (RemoDispBus 服务端 — 双端共用)
|
||||
* 模块功能:实现 RemoDispBus 协议服务端(TCP 监听 + 帧收发 + 1bpp 推流),
|
||||
* 底层经 net_* BSD Socket API、平台相关操作经 ui_net_sink_backend 抽象,
|
||||
* 由 MCU 端(Keil/CH395F)与 PC 模拟器(CMake/Winsock)共同编译,行为一致
|
||||
* 适用平台:STM32F407ZGTx + CH395F / PC 模拟器
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-08-29
|
||||
* 修改记录:
|
||||
* 2026-08-29 王建锋 创建初始版本
|
||||
* 2026-08-29 王建锋 修正监听端口为 7003;移除连接后主动上报 INIT(避免污染客户端首帧读取)
|
||||
* 2026-09-10 王建锋 改为双端共用:底层改用 net_* API + 平台后端;接收改为逐字节
|
||||
* 状态机(失步自动重同步);断开/网络重建后经 net_get_sock 判活
|
||||
* 并重建监听(修复 MCU 单连接模型断线后无法重连的隐患)
|
||||
*/
|
||||
#include "ui_net_sink.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "ui_port.h"
|
||||
|
||||
#include "net_socket.h"
|
||||
#include "net_types.h"
|
||||
#include "ui_net_sink_backend.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
|
||||
/* RemoDispBus 标签与命令(与 ui_net_sink.h 的 UI_REMO_* 常量对齐) */
|
||||
#define TAG_CLIENT UI_REMO_TAG_CLIENT
|
||||
#define TAG_DEVICE UI_REMO_TAG_DEVICE
|
||||
#define CMD_KEEPLIVE UI_REMO_CMD_KEEP
|
||||
#define CMD_INIT UI_REMO_CMD_INIT
|
||||
#define CMD_KEY UI_REMO_CMD_KEY
|
||||
#define CMD_LCDMEM UI_REMO_CMD_LCDMEM
|
||||
|
||||
/* 屏幕尺寸与帧大小(160x160 1bpp = 3200 字节) */
|
||||
#define UI_W 160
|
||||
#define UI_H 160
|
||||
#define UI_MEMSIZE (UI_W * UI_H / 8) /* 3200 字节 */
|
||||
#define UI_MEMSIZE (UI_W * UI_H / 8)
|
||||
|
||||
/* 发送帧缓冲(最大: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];
|
||||
#define RM_MAX_RX_PAYLOAD 256 /* 客户端→服务端 payload 上限,防长度字段损坏引发失步 */
|
||||
#define RM_RX_BUF 64 /* 接收暂存区:整块 recv 后逐字节喂入解析状态机 */
|
||||
|
||||
static int g_listen = -1;
|
||||
static int g_client = -1;
|
||||
static uint16_t g_port; /* 监听端口(网络重建后重监听用) */
|
||||
static uint32_t g_gen; /* 建立监听时的网络代次号(变化 = 网络已重建) */
|
||||
static uint8_t g_tx[4 + 4 + UI_MEMSIZE + 1]; /* 发送组帧缓冲:[TAG][CMD][LEN][DATA][CRC] */
|
||||
static uint8_t g_lcdmem_data[4 + UI_MEMSIZE]; /* LCDMEM 应答缓冲:4B 偏移 + 1bpp 位图 */
|
||||
static uint8_t g_rx[RM_RX_BUF]; /* 接收暂存区:整块 recv 后逐字节喂入状态机 */
|
||||
|
||||
static int g_listen = -1; /* 监听 Socket fd(未启动或已停止时为 -1) */
|
||||
static int g_client = -1; /* 当前客户端 Socket fd(无连接时为 -1) */
|
||||
static uint16_t g_port; /* 监听端口(网络重建 / 断线后重监听用) */
|
||||
static uint32_t g_gen; /* 建立监听时的网络代次号(变化 = 网络已重建) */
|
||||
|
||||
/*
|
||||
* 函数功能:计算 RemoDispBus 数据段异或校验值
|
||||
* 入口参数:d - 数据首地址 const uint8_t * 非空
|
||||
* len - 数据长度 int >= 0
|
||||
* 返回值:校验值 uint8_t 0 - 255
|
||||
* 函数功能:计算 RemoDispBus 帧 payload 的 CRC 校验值
|
||||
* 入口参数:d - 待校验数据指针(len 为 0 时不访问)
|
||||
* len - 数据长度(字节)
|
||||
* 返回值:异或累加校验值
|
||||
* 限定条件:无
|
||||
* 函数说明:逐字节异或,结果为单字节,与 DTU-HMI 协议保持一致
|
||||
* 函数说明:对全部数据字节做异或累加(协议约定的简单校验);len 为 0 时返回 0
|
||||
*/
|
||||
static uint8_t remo_crc(const uint8_t *d, int len)
|
||||
{
|
||||
int i;
|
||||
uint8_t c = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
c ^= d[i];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:以大端序写入 16 位无符号整数
|
||||
* 入口参数:p - 目标缓冲区 uint8_t * 至少 2 字节
|
||||
* v - 待写入数值 uint16_t 0 - 65535
|
||||
* 函数功能:将 16 位值以大端序(高字节在前)写入缓冲区
|
||||
* 入口参数:p - 目标缓冲区(至少 2 字节);v - 待写入值
|
||||
* 返回值:无
|
||||
* 限定条件:p 指向有效内存
|
||||
* 函数说明:高位在前、低位在后(网络字节序)
|
||||
* 限定条件:p 指向至少 2 字节可写空间
|
||||
* 函数说明:网络协议多字节字段统一采用大端序传输
|
||||
*/
|
||||
static void put_u16(uint8_t *p, uint16_t v)
|
||||
{
|
||||
@@ -70,12 +79,11 @@ static void put_u16(uint8_t *p, uint16_t v)
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:以大端序写入 32 位无符号整数
|
||||
* 入口参数:p - 目标缓冲区 uint8_t * 至少 4 字节
|
||||
* v - 待写入数值 uint32_t 0 - 0xFFFFFFFF
|
||||
* 函数功能:将 32 位值以大端序(高字节在前)写入缓冲区
|
||||
* 入口参数:p - 目标缓冲区(至少 4 字节);v - 待写入值
|
||||
* 返回值:无
|
||||
* 限定条件:p 指向有效内存
|
||||
* 函数说明:最高位在前,依次存放 4 字节(网络字节序)
|
||||
* 限定条件:p 指向至少 4 字节可写空间
|
||||
* 函数说明:网络协议多字节字段统一采用大端序传输
|
||||
*/
|
||||
static void put_u32(uint8_t *p, uint32_t v)
|
||||
{
|
||||
@@ -85,24 +93,23 @@ static void put_u32(uint8_t *p, uint32_t v)
|
||||
p[3] = (uint8_t)(v & 0xFF);
|
||||
}
|
||||
|
||||
/* 组包并发送一帧(tag = DEVICE) */
|
||||
/*
|
||||
* 函数功能:组包并发送一帧(设备 -> 客户端)
|
||||
* 入口参数:cmd - 命令字 uint8_t CMD_*
|
||||
* data - 数据段首地址 const uint8_t * 可为 NULL
|
||||
* len - 数据段长度 uint16_t 0 - 65535
|
||||
* 函数功能:构造并发送一帧 RemoDispBus 响应(设备→客户端)
|
||||
* 入口参数:cmd - 命令码;data - payload 指针(len 非 0 时须非空);len - payload 长度
|
||||
* 返回值:无
|
||||
* 限定条件:g_client >= 0(已有连接)
|
||||
* 函数说明:1. 帧格式 [TAG_DEVICE][cmd][len_hi][len_lo][data][crc]
|
||||
* 2. 无连接时直接返回,不发送
|
||||
* 3. 阻塞发送,直至整帧写入 Socket
|
||||
* 限定条件:g_tx 空间足够容纳 4+len+1 字节;g_client >= 0(已有连接)
|
||||
* 函数说明:帧格式 [TAG_DEVICE][CMD][LEN_H][LEN_LO][DATA][CRC];CRC 为 payload 异或;
|
||||
* 无连接时直接返回
|
||||
*/
|
||||
static void remo_send(uint8_t cmd, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
uint8_t *p;
|
||||
|
||||
if (g_client < 0) {
|
||||
return;
|
||||
}
|
||||
uint8_t *p = g_tx;
|
||||
|
||||
p = g_tx;
|
||||
p[0] = TAG_DEVICE;
|
||||
p[1] = cmd;
|
||||
p[2] = (uint8_t)(len >> 8);
|
||||
@@ -111,19 +118,20 @@ static void remo_send(uint8_t cmd, const uint8_t *data, uint16_t len)
|
||||
memcpy(p + 4, data, len);
|
||||
}
|
||||
p[4 + len] = remo_crc(data, len);
|
||||
net_send(g_client, p, 4 + len + 1, 0);
|
||||
net_send(g_client, p, (int)(4 + len + 1), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:发送 INIT 应答(上报分辨率与帧大小)
|
||||
* 函数功能:回送 INIT 帧(告知客户端屏幕尺寸与帧缓冲大小)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:g_client >= 0
|
||||
* 函数说明:数据段为 [宽][高][帧字节数(4)],供客户端分配显示缓冲
|
||||
* 函数说明:payload 为 [宽u16][高u16][缓冲字节数u32],共 6 字节(大端)
|
||||
*/
|
||||
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);
|
||||
@@ -131,30 +139,34 @@ static void remo_send_init(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:解析一帧 RemoDispBus 请求并分发处理
|
||||
* 入口参数:f - 整帧数据首地址 const uint8_t * 非空
|
||||
* n - 帧字节数 int >= 0
|
||||
* 函数功能:处理一帧完整的远端命令(客户端→设备)
|
||||
* 入口参数:f - 完整帧 [TAG][CMD][LEN][DATA][CRC];n - 帧总长度
|
||||
* 返回值:无
|
||||
* 限定条件:f 指向完整帧缓冲
|
||||
* 函数说明:1. 校验长度与 CRC,标签必须为客户端 TAG
|
||||
* 2. 按命令字分发:KEEPLIVE/INIT 应答,KEY 驱动菜单,LCDMEM 回送帧
|
||||
* 3. 校验失败或格式错误时静默丢弃
|
||||
* 限定条件:调用前帧已通过 CRC 与 TAG 校验
|
||||
* 函数说明:按命令分发:KEEPLIVE 回心跳 / INIT 回尺寸 / KEY 注入按键 /
|
||||
* LCDMEM 回送 1bpp 帧缓冲(经平台后端取帧);非法情况静默丢弃
|
||||
*/
|
||||
static void remo_handle(const uint8_t *f, int n)
|
||||
{
|
||||
if (n < 6) {
|
||||
uint8_t tag;
|
||||
uint8_t cmd;
|
||||
uint16_t len;
|
||||
const uint8_t *data;
|
||||
uint8_t crc;
|
||||
|
||||
if (n < 5) {
|
||||
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;
|
||||
tag = f[0];
|
||||
cmd = f[1];
|
||||
len = (uint16_t)(((uint16_t)f[2] << 8) | (uint16_t)f[3]);
|
||||
data = f + 4;
|
||||
if ((int)(4 + len + 1) > n) {
|
||||
return; /* 帧不完整 */
|
||||
return;
|
||||
}
|
||||
uint8_t crc = f[4 + len];
|
||||
crc = f[4 + len];
|
||||
if (crc != remo_crc(data, len)) {
|
||||
return; /* 校验失败,丢弃 */
|
||||
return;
|
||||
}
|
||||
if (tag != TAG_CLIENT) {
|
||||
return;
|
||||
@@ -169,7 +181,7 @@ static void remo_handle(const uint8_t *f, int n)
|
||||
break;
|
||||
case CMD_KEY:
|
||||
if (len >= 1) {
|
||||
ui_port_queue_key(data[0]);
|
||||
ui_net_sink_inject_key(data[0]);
|
||||
}
|
||||
remo_send(CMD_KEY, NULL, 0);
|
||||
break;
|
||||
@@ -177,18 +189,18 @@ static void remo_handle(const uint8_t *f, int n)
|
||||
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);
|
||||
{
|
||||
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) {
|
||||
/* 整帧请求:经平台后端取 1bpp 位图后回送(失败则静默丢弃) */
|
||||
if (ui_net_sink_get_lcdmem(g_lcdmem_data + 4, UI_MEMSIZE) == 0) {
|
||||
memcpy(g_lcdmem_data, data, 4);
|
||||
remo_send(CMD_LCDMEM, g_lcdmem_data, (uint16_t)(4 + UI_MEMSIZE));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -197,84 +209,238 @@ static void remo_handle(const uint8_t *f, int n)
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= RemoDispBus 接收状态机(逐字节解析) =================
|
||||
* 网络接收的二进制流按字节喂入有限状态机重组帧:TAG 定界、长度定长、CRC 校验。
|
||||
* 一帧可跨多次 recv;任意偏移或噪声字节后,状态机在下一个 TAG 处自动重新同步。 */
|
||||
typedef enum {
|
||||
RM_ST_TAG = 0, /* 等待帧头 TAG(0xAA),非帧头字节一律丢弃 */
|
||||
RM_ST_CMD, /* 等待 CMD 字节 */
|
||||
RM_ST_LEN_H, /* 等待长度高字节 */
|
||||
RM_ST_LEN_L, /* 等待长度低字节,据此确定 payload 长度 */
|
||||
RM_ST_DATA, /* 等待 payload 数据字节 */
|
||||
RM_ST_CRC, /* 等待 CRC 校验字节,帧完整 */
|
||||
} rm_st_t;
|
||||
|
||||
typedef struct {
|
||||
rm_st_t st; /* 当前状态 */
|
||||
uint8_t frame[4 + RM_MAX_RX_PAYLOAD + 1]; /* 重组缓冲 */
|
||||
uint16_t pos; /* frame[] 已写入字节数 */
|
||||
uint16_t plen; /* 本帧 payload 长度 */
|
||||
uint16_t didx; /* payload 已接收字节数 */
|
||||
} rm_parser_t;
|
||||
|
||||
static rm_parser_t g_rm; /* 全局解析器:状态跨 poll 调用保持(一帧可跨多次 recv) */
|
||||
|
||||
/*
|
||||
* 函数功能:启动 UI 专用 TCP 监听 Socket
|
||||
* 入口参数:port - 监听端口号 uint16_t 1 - 65535
|
||||
* 返回值:0 - 成功,其他 - 失败(socket 创建/绑定/监听失败)
|
||||
* 限定条件:net_init() 已完成,网络子系统就绪
|
||||
* 函数说明:创建流式 Socket 并绑定端口,进入监听等待连接
|
||||
* 函数功能:复位接收解析状态机到初始(等待帧头)状态
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:新建连接、连接断开或长度越界失步时调用,清除半帧残留
|
||||
*/
|
||||
int ui_net_sink_start(uint16_t port)
|
||||
static void rm_parser_reset(void)
|
||||
{
|
||||
g_port = port;
|
||||
g_listen = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||
if (g_listen < 0) {
|
||||
g_rm.st = RM_ST_TAG;
|
||||
g_rm.pos = 0;
|
||||
g_rm.plen = 0;
|
||||
g_rm.didx = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:向解析状态机喂入一个字节并推进状态,凑齐合法整帧时分发
|
||||
* 入口参数:b - 待处理字节
|
||||
* 返回值:无
|
||||
* 限定条件:须在持有唯一客户端连接的上下文中调用
|
||||
* 函数说明:1. 状态机 TAG→CMD→LEN_H→LEN_L→DATA→CRC 逐字节重组帧
|
||||
* 2. TAG 态丢弃非帧头字节,实现失步后自动重同步
|
||||
* 3. payload 长度越界(>RM_MAX_RX_PAYLOAD)判为损坏并复位
|
||||
* 4. 整帧 CRC 通过才回调 remo_handle;状态跨多次 recv 保持
|
||||
*/
|
||||
static void rm_parser_feed(uint8_t b)
|
||||
{
|
||||
switch (g_rm.st) {
|
||||
case RM_ST_TAG:
|
||||
if (b != TAG_CLIENT) {
|
||||
break;
|
||||
}
|
||||
g_rm.frame[g_rm.pos++] = b;
|
||||
g_rm.st = RM_ST_CMD;
|
||||
break;
|
||||
|
||||
case RM_ST_CMD:
|
||||
g_rm.frame[g_rm.pos++] = b;
|
||||
g_rm.st = RM_ST_LEN_H;
|
||||
break;
|
||||
|
||||
case RM_ST_LEN_H:
|
||||
g_rm.frame[g_rm.pos++] = b;
|
||||
g_rm.st = RM_ST_LEN_L;
|
||||
break;
|
||||
|
||||
case RM_ST_LEN_L:
|
||||
g_rm.frame[g_rm.pos++] = b;
|
||||
g_rm.plen = (uint16_t)(((uint16_t)g_rm.frame[2] << 8) | (uint16_t)g_rm.frame[3]);
|
||||
if (g_rm.plen > RM_MAX_RX_PAYLOAD) {
|
||||
rm_parser_reset();
|
||||
} else {
|
||||
g_rm.didx = 0;
|
||||
g_rm.st = (g_rm.plen > 0) ? RM_ST_DATA : RM_ST_CRC;
|
||||
}
|
||||
break;
|
||||
|
||||
case RM_ST_DATA:
|
||||
g_rm.frame[g_rm.pos++] = b;
|
||||
if (++g_rm.didx >= g_rm.plen) {
|
||||
g_rm.st = RM_ST_CRC;
|
||||
}
|
||||
break;
|
||||
|
||||
case RM_ST_CRC: {
|
||||
uint16_t total;
|
||||
g_rm.frame[g_rm.pos++] = b;
|
||||
total = g_rm.pos;
|
||||
if (b == remo_crc(g_rm.frame + 4, g_rm.plen)) {
|
||||
remo_handle(g_rm.frame, (int)total);
|
||||
}
|
||||
rm_parser_reset();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
rm_parser_reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:创建并监听 UI 专用 TCP Socket(内部:socket+bind+listen)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功,-1 - 失败
|
||||
* 限定条件:net_* 网络子系统可用
|
||||
* 函数说明:绑定 g_port 与任意本地地址并进入监听;成功后写回 g_listen
|
||||
*/
|
||||
static int bind_listen(void)
|
||||
{
|
||||
int ls;
|
||||
struct net_sockaddr_in addr;
|
||||
|
||||
ls = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||
if (ls < 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_port = net_htons(g_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;
|
||||
if (net_bind(ls, (struct net_sockaddr *)&addr, (int)sizeof(addr)) < 0) {
|
||||
net_close(ls);
|
||||
return -1;
|
||||
}
|
||||
if (net_listen(g_listen) < 0) {
|
||||
net_close(g_listen);
|
||||
g_listen = -1;
|
||||
if (net_listen(ls) < 0) {
|
||||
net_close(ls);
|
||||
return -1;
|
||||
}
|
||||
g_client = -1;
|
||||
g_gen = net_get_generation(); /* 成功才记录代次;失败保持旧值以便下轮重试 */
|
||||
|
||||
g_listen = ls;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:非阻塞轮询 RemoDispBus 协议
|
||||
* 函数功能:启动 RemoDispBus 服务端(创建并监听 TCP Socket)
|
||||
* 入口参数:port - 监听端口号
|
||||
* 返回值:0 - 成功,-1 - 失败
|
||||
* 限定条件:网络子系统已初始化(模拟器端 Winsock 已就绪)
|
||||
* 函数说明:创建流式 Socket 并绑定端口进入监听;成功才记录网络代次号
|
||||
*/
|
||||
int ui_net_sink_start(uint16_t port)
|
||||
{
|
||||
g_port = port;
|
||||
|
||||
if (bind_listen() < 0) {
|
||||
g_listen = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_client = -1;
|
||||
g_gen = net_get_generation();
|
||||
rm_parser_reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:非阻塞轮询 RemoDispBus 协议(接受连接、收帧、逐字节解析)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:ui_net_sink_start() 已成功调用
|
||||
* 函数说明:1. 无客户端时尝试接受连接(不主动上报 INIT,由客户端按需请求)
|
||||
* 2. 有客户端时非阻塞收帧并解析
|
||||
* 3. 连接关闭或异常时复位,等待重监听,不阻塞界面刷新
|
||||
* 4. 网络重建(net_init 代次变化)时关闭旧监听并重建(IP 变更后自愈)
|
||||
* 函数说明:1. 网络代次变化(重建)时关闭旧监听/客户端并重建
|
||||
* 2. 无监听(启动失败)时重试启动
|
||||
* 3. 无客户端时尝试 accept,成功则复位状态机
|
||||
* 4. 有客户端时非阻塞 recv 逐字节喂入状态机
|
||||
* 5. 对端关闭/真错误时关闭客户端;若监听 Socket 随客户端失效
|
||||
* (MCU 单连接模型)则重建监听,等待重连
|
||||
*/
|
||||
void ui_net_sink_poll(void)
|
||||
{
|
||||
/* 网络重建(net_init 代次变化):旧监听/客户端已随芯片复位失效,重建监听 */
|
||||
int n;
|
||||
int i;
|
||||
int c;
|
||||
|
||||
/* 网络重建(代次变化):旧监听/客户端已随芯片复位失效,重建监听 */
|
||||
if (net_get_generation() != g_gen) {
|
||||
if (g_client >= 0 && g_client != g_listen) {
|
||||
net_close(g_client);
|
||||
}
|
||||
if (g_listen >= 0) {
|
||||
net_close(g_listen);
|
||||
g_listen = -1;
|
||||
}
|
||||
g_client = -1;
|
||||
(void)ui_net_sink_start(g_port); /* 失败则下轮再试(g_gen 未更新) */
|
||||
rm_parser_reset();
|
||||
(void)ui_net_sink_start(g_port);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_listen < 0) {
|
||||
/* 尚未启动或启动失败:重试(成功则 g_listen 更新) */
|
||||
(void)ui_net_sink_start(g_port);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 无客户端时尝试接受连接 */
|
||||
if (g_client < 0) {
|
||||
int c = net_accept(g_listen, NULL, NULL);
|
||||
c = net_accept(g_listen, NULL, NULL);
|
||||
if (c >= 0) {
|
||||
g_client = c;
|
||||
rm_parser_reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int n = net_recv(g_client, g_rx, sizeof(g_rx), NET_MSG_DONTWAIT);
|
||||
/* 有客户端时非阻塞收帧,逐字节喂入解析状态机 */
|
||||
n = net_recv(g_client, g_rx, (int)sizeof(g_rx), NET_MSG_DONTWAIT);
|
||||
if (n > 0) {
|
||||
remo_handle(g_rx, n);
|
||||
for (i = 0; i < n; i++) {
|
||||
rm_parser_feed((uint8_t)g_rx[i]);
|
||||
}
|
||||
} else if (n == 0) {
|
||||
g_client = -1; /* 对端关闭:等待下次 accept 自动重监听,不释放控制块 */
|
||||
/* 对端关闭:关闭客户端,必要时重建监听(单连接模型下监听随客户端失效) */
|
||||
net_close(g_client);
|
||||
g_client = -1;
|
||||
rm_parser_reset();
|
||||
if (net_get_sock(g_listen) == NULL) {
|
||||
(void)bind_listen();
|
||||
}
|
||||
} else {
|
||||
/* n < 0:区分"暂无数据"(继续等待)与"真错误"(断开并视情况重建监听) */
|
||||
if (net_get_errno() != NET_ERR_WOULDBLOCK) {
|
||||
net_close(g_client);
|
||||
g_client = -1;
|
||||
rm_parser_reset();
|
||||
if (net_get_sock(g_listen) == NULL) {
|
||||
(void)bind_listen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 模块名称:UI Network Sink Backend (平台后端接口)
|
||||
* 模块功能:抽象 RemoDispBus 服务端的平台相关操作(LCDMEM 数据源 / 远端按键注入),
|
||||
* 使 MCU 端与 PC 模拟器共用同一份 ui_net_sink.c 协议实现
|
||||
* 适用平台:跨平台接口(MCU / PC 模拟器各提供一份实现)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* 2026-09-10 王建锋 创建初始版本(RemoDispBus 双端共用协议实现的平台抽象)
|
||||
*/
|
||||
#ifndef UI_NET_SINK_BACKEND_H
|
||||
#define UI_NET_SINK_BACKEND_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 1bpp 帧缓冲尺寸(160x160 / 8 = 3200 字节,须与 ui_net_sink.c 的 UI_MEMSIZE 一致) */
|
||||
#define UI_NET_SINK_MONO_SIZE 3200
|
||||
|
||||
/*
|
||||
* 函数功能:读取当前 1bpp 屏幕位图(LCDMEM 应答的数据源)
|
||||
* 入口参数:out - 输出缓冲(调用方分配,≥ UI_NET_SINK_MONO_SIZE 字节)
|
||||
* out_size - out 容量(字节)
|
||||
* 返回值:0 - 成功,-1 - 失败
|
||||
* 限定条件:显示子系统已初始化
|
||||
* 函数说明:MCU 端从 LVGL 帧缓冲拷贝;模拟器端读回 SDL 实际呈现像素再转 1bpp
|
||||
*/
|
||||
int ui_net_sink_get_lcdmem(uint8_t *out, uint32_t out_size);
|
||||
|
||||
/*
|
||||
* 函数功能:向 UI 注入一个远端按键
|
||||
* 入口参数:remo_key - 远端按键码(ui_port.h 的 UI_KEY_* 之一)
|
||||
* 返回值:无
|
||||
* 限定条件:输入子系统已初始化
|
||||
* 函数说明:MCU 端经 ui_port_queue_key 缓存、由 keypad 回调上报;
|
||||
* 模拟器端映射为 LV_KEY_* 后经 lv_group_send_data 注入
|
||||
*/
|
||||
void ui_net_sink_inject_key(uint8_t remo_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* UI_NET_SINK_BACKEND_H */
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 模块名称:UI Network Sink Backend (MCU 实现)
|
||||
* 模块功能:RemoDispBus 服务端在 MCU 端的平台相关操作——
|
||||
* 读取 LVGL 帧缓冲(LCDMEM 数据源)与经 ui_port 注入远端按键
|
||||
* 适用平台:STM32F407ZGTx
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* 2026-09-10 王建锋 创建初始版本(双端共用 ui_net_sink 的平台后端)
|
||||
*/
|
||||
#include "ui_net_sink_backend.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "ui_port.h"
|
||||
|
||||
/*
|
||||
* 函数功能:读取 LVGL 帧缓冲为 1bpp 位图(LCDMEM 数据源)
|
||||
* 入口参数:out - 输出缓冲(≥ UI_NET_SINK_MONO_SIZE 字节)
|
||||
* out_size - out 容量(字节)
|
||||
* 返回值:0 - 成功,-1 - 参数或帧缓冲无效
|
||||
* 限定条件:ui_port_init() 已成功
|
||||
* 函数说明:帧缓冲本就是 1bpp(布局与协议一致),直接整帧拷贝
|
||||
*/
|
||||
int ui_net_sink_get_lcdmem(uint8_t *out, uint32_t out_size)
|
||||
{
|
||||
const uint8_t *fb;
|
||||
|
||||
if (out == NULL || out_size < UI_NET_SINK_MONO_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fb = ui_port_get_framebuffer();
|
||||
if (fb == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(out, fb, UI_NET_SINK_MONO_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:向 UI 输入队列注入一个远端按键
|
||||
* 入口参数:remo_key - 远端按键码(UI_KEY_* 之一)
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:缓存后由 keypad 读取回调在下次轮询上报(键码映射在 ui_port 内完成)
|
||||
*/
|
||||
void ui_net_sink_inject_key(uint8_t remo_key)
|
||||
{
|
||||
ui_port_queue_key(remo_key);
|
||||
}
|
||||
Reference in New Issue
Block a user