ui_net_sink 拆分为协议层与平台后端,新增 RemoDispBus 设计文档
This commit is contained in:
@@ -7,7 +7,8 @@ project(lvgl_sim C)
|
||||
set(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
set(LVGL_DIR "${PROJECT_ROOT}/Lib/lvgl")
|
||||
set(UI_DIR "${PROJECT_ROOT}/Drivers/BSP/UI")
|
||||
set(NETCFG_DIR "${PROJECT_ROOT}/Drivers/BSP/NETCFG")
|
||||
set(NET_DIR "${PROJECT_ROOT}/Drivers/BSP/NET")
|
||||
set(NETCFG_DIR "${PROJECT_ROOT}/Drivers/BSP/NETCFG")
|
||||
set(SDL2_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDL2/SDL2-2.32.10")
|
||||
|
||||
#=============================================================================
|
||||
@@ -44,10 +45,12 @@ add_subdirectory("${LVGL_DIR}" lvgl_build)
|
||||
# 包含路径
|
||||
#=============================================================================
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR} # sim_port.h, lvgl conf.h (LVGL 找 lv_conf.h), dbg_cfg.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR} # lv_conf.h (LVGL 找), dbg_log.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/sim # sim_port.h 等模拟器平台层
|
||||
${LVGL_DIR} # lvgl.h
|
||||
${LVGL_DIR}/src # lvgl 内部头文件
|
||||
${UI_DIR} # ui_menu.h, ui_font_cn_12.h
|
||||
${NET_DIR} # net_socket.h / net_types.h(net_* API 双端共享核心)
|
||||
${NETCFG_DIR} # net_cfg.h(网络配置,双端共享核心)
|
||||
${PROJECT_ROOT}/Drivers/BSP # dbg_log.h(使用 simulator 的 dbg_cfg.h 配置)
|
||||
${SDL2_DIR}/include # SDL2 头文件
|
||||
@@ -64,7 +67,7 @@ set(SDL2_LIB_X64 "${SDL2_DIR}/lib/x64")
|
||||
#=============================================================================
|
||||
set(SIM_SOURCES
|
||||
main.c
|
||||
sim_port.c
|
||||
sim/sim_port.c
|
||||
)
|
||||
|
||||
# UI 源文件(与 MCU 端共用,直接编译)
|
||||
@@ -80,24 +83,31 @@ set(UI_SOURCES
|
||||
${UI_DIR}/ui_page_net_cfg.c
|
||||
${UI_DIR}/ui_menu_tab.c
|
||||
${UI_DIR}/ui_font_cn_12.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui_net_sink.c
|
||||
${UI_DIR}/ui_net_sink.c
|
||||
)
|
||||
|
||||
# 网络配置(双端共享核心 + 模拟器存储后端;MCU 端编 net_cfg_store_mcu.c,见 Keil 工程)
|
||||
set(NETCFG_SOURCES
|
||||
${NETCFG_DIR}/net_cfg.c
|
||||
${NETCFG_DIR}/net_cfg_store_sim.c
|
||||
sim/net_cfg_store_sim.c
|
||||
)
|
||||
|
||||
# 网络后端(net_* 核心 API 的 Winsock 实现 + UI 推流平台后端;MCU 端另编 net_socket.c 与 _mcu 后端)
|
||||
set(NET_SOURCES
|
||||
sim/net_socket_sim.c
|
||||
sim/ui_net_sink_backend_sim.c
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# 构建可执行文件
|
||||
#=============================================================================
|
||||
add_executable(lvgl_sim WIN32 ${SIM_SOURCES} ${UI_SOURCES} ${NETCFG_SOURCES})
|
||||
add_executable(lvgl_sim WIN32 ${SIM_SOURCES} ${UI_SOURCES} ${NETCFG_SOURCES} ${NET_SOURCES})
|
||||
|
||||
target_compile_definitions(lvgl_sim PRIVATE
|
||||
LV_CONF_INCLUDE_SIMPLE # 让 lvgl.h 找到 lv_conf.h
|
||||
LV_LVGL_H_INCLUDE_SIMPLE # ui_font_cn_12.c 内部 include 简化
|
||||
WIN32_LEAN_AND_MEAN # 防止 Windows.h 拉入 winsock.h(与 winsock2.h 冲突)
|
||||
NET_NO_OS # 剥离 net_socket.h 的 FreeRTOS 相关接口(纯 BSD Socket)
|
||||
)
|
||||
|
||||
target_include_directories(lvgl_sim PRIVATE
|
||||
|
||||
+11
-6
@@ -13,8 +13,11 @@
|
||||
simulator/
|
||||
├── CMakeLists.txt # 构建脚本(CMake + MSVC,x64)
|
||||
├── main.c # 入口:SDL 初始化 → LVGL → 菜单 → 主循环
|
||||
├── sim_port.c / sim_port.h # 模拟器显示/输入端口(SDL 窗口 + 键盘),替代 MCU 端 ui_port.c
|
||||
├── ui_net_sink.c # RemoDispBus 推流服务端(Winsock TCP 7003),替代 MCU 端同名文件
|
||||
├── sim/ # 平台层(PC 专用实现;MCU 端对应实现在各 BSP 目录)
|
||||
│ ├── sim_port.c / .h # 显示/输入端口(SDL 窗口 + 键盘 + mono 主题),替代 MCU 端 ui_port.c
|
||||
│ ├── net_socket_sim.c # 网络后端:Winsock 实现 net_* BSD API,替代 MCU 端 net_socket.c(CH395F)
|
||||
│ ├── ui_net_sink_backend_sim.c # RemoDispBus 平台 backend:取帧/注键(配合共享 ui_net_sink.c)
|
||||
│ └── net_cfg_store_sim.c # 网口配置存储:本地 sim_netcfg.bin
|
||||
├── lv_conf.h # 模拟器专用 LVGL 配置(与 MCU 端 Lib/lv_conf.h 相互独立)
|
||||
├── i1_to_png.py # 截图转 PNG:把 --screenshot 的 I1(1bpp) 原始位图转成 PNG(见 §3.5)
|
||||
├── tests/ # 无头回归脚本 + 本地黄金参考(见 §3.6,流程文档 docs/UI 设计/UI_自动化测试流程.md)
|
||||
@@ -40,15 +43,17 @@ simulator/
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `main.c` | 程序入口。`SDL_Init(TIMER\|EVENTS)` → `lv_init()` → `sim_port_init(160,160)` → `ui_menu_init()`(共享代码)→ 主循环(SDL 事件 + `lv_timer_handler` + `lv_refr_now`) |
|
||||
| `sim_port.c` | 创建 SDL 窗口显示设备(`lv_sdl_window_create`)、初始化 **mono 主题**(`lv_theme_mono_init`,1bpp 必需)、创建键盘输入设备并挂默认输入组。**不创建鼠标**(纯键盘导航,与 MCU 端 keypad 一致)。`sim_port_get_screen_mono()` 经 `SDL_RenderReadPixels` 读回屏幕像素转 1bpp,供推流取帧 |
|
||||
| `ui_net_sink.c` | RemoDispBus 推流服务端(Winsock TCP 7003,与 MCU 端同名文件协议一致):连接后**不**主动上报 INIT,LCDMEM 取帧调 `sim_port_get_screen_mono` 转 1bpp 回传 |
|
||||
| `sim/sim_port.c` | 创建 SDL 窗口显示设备(`lv_sdl_window_create`)、初始化 **mono 主题**(`lv_theme_mono_init`,1bpp 必需)、创建键盘输入设备并挂默认输入组。**不创建鼠标**(纯键盘导航,与 MCU 端 keypad 一致)。`sim_port_get_screen_mono()` 经 `SDL_RenderReadPixels` 读回屏幕像素转 1bpp,供推流取帧 |
|
||||
| `sim/net_socket_sim.c` | 网络后端:Winsock 实现 `net_*` BSD Socket API(`socket`/`bind`/`listen`/`accept`/`recv`/`send`/`close`),替代 MCU 端 `Drivers/BSP/NET/net_socket.c`(CH395F) |
|
||||
| `sim/ui_net_sink_backend_sim.c` | RemoDispBus 平台 backend:`ui_net_sink_get_lcdmem`→`sim_port_get_screen_mono`、`ui_net_sink_inject_key`→`lv_group_send_data`;配合**共享** `Drivers/BSP/UI/ui_net_sink.c`(协议服务端,双端同一份,TCP 7003) |
|
||||
| `sim/net_cfg_store_sim.c` | 网口配置存储后端:本地文件 `sim_netcfg.bin`(`fopen`),替代 MCU 端 `Drivers/BSP/NETCFG/net_cfg_store_mcu.c`(Flash) |
|
||||
| `lv_conf.h` | 模拟器专用 LVGL 配置。与 MCU 端 `Lib/lv_conf.h` 的关键差异见 §3.3 |
|
||||
| `dbg_log.h` | MCU 版 `dbg_log.h` 依赖 `sys_clock.h`(RTC 时间戳),PC 无法编译。此 stub 把 `DBG_INFO/ERROR/DEBUG` 映射到 `printf`;因 `simulator/` 在 include 路径最前,**优先生效**覆盖 MCU 版 |
|
||||
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ 本目录端口代码,链接预编译 SDL2,产出 `lvgl_sim.exe` |
|
||||
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
|
||||
| `i1_to_png.py` | 把 `--screenshot` 产出的 I1(1bpp) 原始位图转成 PNG,便于离线比对设计稿。详见 §3.5 |
|
||||
| `tests/` | 无头回归脚本(菜单 8 状态像素回归、内容页文本断言 + 黄金参考回归);`ref/` 为**本地生成物、不入库**(首次 `--update` 生成)。详见 §3.6 |
|
||||
|
||||
> **共享代码不复制**:`ui_menu.c` / `ui_font_cn_12.c` 直接引用 `Drivers/BSP/UI/` 下的原件,
|
||||
> **共享代码不复制**:`ui_menu.c` / `ui_font_cn_12.c` / `ui_net_sink.c` 直接引用 `Drivers/BSP/UI/` 下的原件,
|
||||
> MCU 与 PC 两端永远同步。**不要**在本目录内复制 UI 源码。
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 模块名称: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;
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
/*
|
||||
* 模块名称:Network Socket API (Winsock 模拟器后端)
|
||||
* 模块功能:以 Windows Winsock2 实现 net_socket.h 的 BSD Socket 核心 API,
|
||||
* 使 PC 模拟器与 MCU 端可共用同一份上层网络代码(ui_net_sink.c 等)
|
||||
* 适用平台:Windows (Winsock2);构建时须定义 NET_NO_OS(剥离 FreeRTOS 相关接口)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* 2026-09-10 王建锋 创建初始版本(RemoDispBus 双端共用 net_* 后端)
|
||||
*
|
||||
* 设计要点:
|
||||
* 1. fd 采用与 MCU 端一致的"控制块索引"模型(0 ~ NET_MAX_SOCKETS-1)
|
||||
* 2. 所有 Socket 创建即置为非阻塞(ioctlsocket FIONBIO),使 net_recv /
|
||||
* net_accept 的 DONTWAIT 语义与 MCU 端一致(无数据返回 -1 + NET_ERR_WOULDBLOCK)
|
||||
* 3. 代次号在模拟器上恒为 0(无"网络重建"概念),net_get_generation 恒返回 0
|
||||
* 4. net_get_sock 以 in_use 判活,供上层判断"监听 Socket 是否随客户端一同失效"
|
||||
* 5. Winsock 句柄存放于并行数组 s_ws[],不侵入共享的 net_sock_t 结构体
|
||||
*/
|
||||
|
||||
/*
|
||||
* 注意:net_types.h(经 net_socket.h)必须先于 winsock2.h 包含。
|
||||
* MSVC 的 C 编译器在 winsock2.h 定义 Windows 宏之后再解析本项目的地址结构体
|
||||
* (struct net_in_addr / net_sockaddr_in)会报 C2143 / C2079;ARMCC 无此限制。
|
||||
* 故此处先引入 net 类型,再引入 Winsock(net_socket.h 本身不依赖 Winsock)。
|
||||
*/
|
||||
#include "net_socket.h"
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* WSA 版本号(MAKEWORD(2,2)) */
|
||||
#define NET_SIM_WSA_VERSION 0x0202
|
||||
/* 监听 backlog(单客户端) */
|
||||
#define NET_SIM_BACKLOG 1
|
||||
|
||||
/* 控制块公开视图(in_use 判活,供 net_get_sock)与 Winsock 句柄并行数组 */
|
||||
static net_sock_t s_socks[NET_MAX_SOCKETS];
|
||||
static SOCKET s_ws[NET_MAX_SOCKETS];
|
||||
static int s_net_errno = NET_OK; /* 最近一次操作错误码 */
|
||||
static int s_generation = 0; /* 网络代次(模拟器恒为 0) */
|
||||
|
||||
/*
|
||||
* 函数功能:确保 Winsock 已初始化(幂等)
|
||||
* 入口参数:无
|
||||
* 出口参数:无
|
||||
* 返回值:0 - 就绪,-1 - WSAStartup 失败
|
||||
* 限定条件:无
|
||||
* 函数说明:任何 Socket 操作前先调用;已初始化则直接返回
|
||||
*/
|
||||
static int ensure_wsa(void)
|
||||
{
|
||||
static int s_done = 0;
|
||||
|
||||
if (s_done == 0) {
|
||||
WSADATA wsa;
|
||||
if (WSAStartup(NET_SIM_WSA_VERSION, &wsa) != 0) {
|
||||
return -1;
|
||||
}
|
||||
s_done = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:分配一个空闲 fd(控制块索引)
|
||||
* 入口参数:无
|
||||
* 出口参数:无
|
||||
* 返回值:空闲索引(0 ~ NET_MAX_SOCKETS-1),-1 表示无空闲
|
||||
* 限定条件:s_socks 已定义
|
||||
* 函数说明:线性扫描,返回首个 in_use == 0 的控制块索引
|
||||
*/
|
||||
static int alloc_fd(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NET_MAX_SOCKETS; i++) {
|
||||
if (s_socks[i].in_use == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:net_sockaddr 拷贝为 Winsock sockaddr_in
|
||||
* 入口参数:addr - 源地址(net_sockaddr,AF_INET 实为 net_sockaddr_in)
|
||||
* out - 输出 Winsock 地址
|
||||
* 出口参数:out - 填充
|
||||
* 返回值:无
|
||||
* 限定条件:两结构体布局一致(均 16 字节,字段偏移相同)
|
||||
* 函数说明:字段偏移一致,按 sizeof 直接拷贝
|
||||
*/
|
||||
static void to_ws_addr(const struct net_sockaddr *addr, struct sockaddr_in *out)
|
||||
{
|
||||
memcpy(out, addr, sizeof(struct sockaddr_in));
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:Winsock sockaddr_in 拷贝为 net_sockaddr
|
||||
* 入口参数:in - 源 Winsock 地址
|
||||
* out - 输出 net_sockaddr
|
||||
* 出口参数:out - 填充
|
||||
* 返回值:无
|
||||
* 限定条件:两结构体布局一致(均 16 字节,字段偏移相同)
|
||||
* 函数说明:字段偏移一致,按 sizeof 直接拷贝
|
||||
*/
|
||||
static void from_ws_addr(const struct sockaddr_in *in, struct net_sockaddr *out)
|
||||
{
|
||||
memcpy(out, in, sizeof(struct sockaddr_in));
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:网络子系统初始化(模拟器:仅确保 Winsock 就绪)
|
||||
* 入口参数:ip/mask/gateway/mac - 均忽略(模拟器无 CH395F 协议栈)
|
||||
* 出口参数:无
|
||||
* 返回值:0 - 成功,-1 - WSA 初始化失败
|
||||
* 限定条件:无
|
||||
* 函数说明:与 MCU 端 net_init 语义对齐,供上层统一调用
|
||||
*/
|
||||
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac)
|
||||
{
|
||||
(void)ip;
|
||||
(void)mask;
|
||||
(void)gateway;
|
||||
(void)mac;
|
||||
|
||||
if (ensure_wsa() != 0) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取网络代次号
|
||||
* 入口参数:无
|
||||
* 返回值:代次号(模拟器恒为 0)
|
||||
* 限定条件:无
|
||||
* 函数说明:模拟器无网络重建,代次恒 0,使上层代次自愈逻辑空转
|
||||
*/
|
||||
uint32_t net_get_generation(void)
|
||||
{
|
||||
return (uint32_t)s_generation;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:递增网络代次号
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:模拟器不会触发,保留接口以对齐 API
|
||||
*/
|
||||
void net_generation_inc(void)
|
||||
{
|
||||
s_generation++;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:轮询所有 Socket 状态
|
||||
* 入口参数:无
|
||||
* 返回值:0(Winsock 非阻塞 I/O 直接反映事件,无需中断轮询)
|
||||
* 限定条件:无
|
||||
* 函数说明:模拟器无 CH395F 中断,空操作
|
||||
*/
|
||||
int net_poll(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:创建 socket
|
||||
* 入口参数:domain - 地址族(仅 AF_INET)
|
||||
* type - NET_SOCK_STREAM(TCP) 或 NET_SOCK_DGRAM(UDP)
|
||||
* 出口参数:无
|
||||
* 返回值:socket 描述符(0 ~ NET_MAX_SOCKETS-1),失败 -1
|
||||
* 限定条件:Winsock 已就绪
|
||||
* 函数说明:分配空闲控制块,创建 Winsock SOCKET 并置非阻塞
|
||||
*/
|
||||
int net_socket(int domain, int type)
|
||||
{
|
||||
int idx;
|
||||
int is_udp;
|
||||
SOCKET ws;
|
||||
u_long nb = 1;
|
||||
|
||||
(void)domain;
|
||||
|
||||
if (ensure_wsa() != 0) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
idx = alloc_fd();
|
||||
if (idx < 0) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
is_udp = (type == NET_SOCK_DGRAM);
|
||||
ws = socket(AF_INET, is_udp ? SOCK_DGRAM : SOCK_STREAM,
|
||||
is_udp ? IPPROTO_UDP : IPPROTO_TCP);
|
||||
if (ws == INVALID_SOCKET) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ioctlsocket(ws, FIONBIO, &nb) != 0) {
|
||||
closesocket(ws);
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&s_socks[idx], 0, sizeof(net_sock_t));
|
||||
s_socks[idx].in_use = 1;
|
||||
s_socks[idx].type = (uint8_t)type;
|
||||
s_socks[idx].state = NET_SOCK_STATE_CREATED;
|
||||
s_socks[idx].ch395_sock = (uint8_t)idx;
|
||||
s_ws[idx] = ws;
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
return idx;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:绑定本地地址和端口
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* addr - 本地地址(net_sockaddr,AF_INET)
|
||||
* addrlen - 地址结构体长度
|
||||
* 出口参数:无
|
||||
* 返回值:0 成功,-1 失败
|
||||
* 限定条件:socket 已创建
|
||||
* 函数说明:设置 SO_REUSEADDR 后 bind,便于断线后重监听复用端口
|
||||
*/
|
||||
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen)
|
||||
{
|
||||
struct sockaddr_in wsaddr;
|
||||
int yes = 1;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (addr == NULL || addrlen < (int)sizeof(struct sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
setsockopt(s_ws[sockfd], SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
|
||||
to_ws_addr(addr, &wsaddr);
|
||||
|
||||
if (bind(s_ws[sockfd], (struct sockaddr *)&wsaddr, sizeof(wsaddr)) == SOCKET_ERROR) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_socks[sockfd].state = NET_SOCK_STATE_BOUND;
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:TCP Server 监听
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* 出口参数:无
|
||||
* 返回值:0 成功,-1 失败
|
||||
* 限定条件:socket 已绑定
|
||||
* 函数说明:backlog 为 1(单客户端),监听 Socket 保持非阻塞
|
||||
*/
|
||||
int net_listen(int sockfd)
|
||||
{
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen(s_ws[sockfd], NET_SIM_BACKLOG) == SOCKET_ERROR) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_socks[sockfd].state = NET_SOCK_STATE_LISTENING;
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:TCP Server 接受连接
|
||||
* 入口参数:sockfd - 监听 socket 描述符
|
||||
* addr - 输出:客户端地址(可为 NULL)
|
||||
* addrlen - 输入输出:地址长度(addr 非空时有效)
|
||||
* 出口参数:addr/addrlen - 填充客户端地址
|
||||
* 返回值:新 socket 描述符,失败 -1
|
||||
* 限定条件:socket 正在监听
|
||||
* 函数说明:非阻塞监听下无新连接时返回 -1 并置 NET_ERR_WOULDBLOCK;
|
||||
* 与 MCU 端不同,Winsock 的 accept 返回独立于监听 Socket 的新 fd
|
||||
*/
|
||||
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen)
|
||||
{
|
||||
struct sockaddr_in wscli;
|
||||
int wslen;
|
||||
SOCKET cws;
|
||||
int nidx;
|
||||
u_long nb = 1;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
wslen = sizeof(wscli);
|
||||
cws = accept(s_ws[sockfd], (addr != NULL) ? (struct sockaddr *)&wscli : NULL,
|
||||
(addr != NULL) ? &wslen : NULL);
|
||||
if (cws == INVALID_SOCKET) {
|
||||
int err = WSAGetLastError();
|
||||
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (addr != NULL && addrlen != NULL) {
|
||||
from_ws_addr(&wscli, addr);
|
||||
*addrlen = (int)sizeof(struct net_sockaddr_in);
|
||||
}
|
||||
|
||||
nidx = alloc_fd();
|
||||
if (nidx < 0) {
|
||||
closesocket(cws);
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ioctlsocket(cws, FIONBIO, &nb);
|
||||
memset(&s_socks[nidx], 0, sizeof(net_sock_t));
|
||||
s_socks[nidx].in_use = 1;
|
||||
s_socks[nidx].type = (uint8_t)NET_SOCK_STREAM;
|
||||
s_socks[nidx].state = NET_SOCK_STATE_ESTABLISHED;
|
||||
s_socks[nidx].ch395_sock = (uint8_t)nidx;
|
||||
s_ws[nidx] = cws;
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
return nidx;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:TCP Client 发起连接
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* addr - 服务器地址
|
||||
* addrlen - 地址长度
|
||||
* 出口参数:无
|
||||
* 返回值:0 成功(或已发起),-1 失败
|
||||
* 限定条件:socket 已创建
|
||||
* 函数说明:非阻塞 Socket 上连接未完成时返回 -1 + NET_ERR_WOULDBLOCK
|
||||
*/
|
||||
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen)
|
||||
{
|
||||
struct sockaddr_in wsaddr;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (addr == NULL || addrlen < (int)sizeof(struct sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
to_ws_addr(addr, &wsaddr);
|
||||
|
||||
if (connect(s_ws[sockfd], (struct sockaddr *)&wsaddr, sizeof(wsaddr)) == 0) {
|
||||
s_socks[sockfd].state = NET_SOCK_STATE_ESTABLISHED;
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
int err = WSAGetLastError();
|
||||
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:发送数据(TCP)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 数据缓冲区
|
||||
* len - 数据长度
|
||||
* flags - 通常为 0
|
||||
* 出口参数:无
|
||||
* 返回值:实际发送字节数,-1 错误;发送缓冲满时返回 0 + NET_ERR_WOULDBLOCK
|
||||
* 限定条件:socket 已建立,buf 非空
|
||||
* 函数说明:非阻塞单次发送(保持 UI 主循环不阻塞);协议帧小且对端主动
|
||||
* 读取,实践中一次即发完
|
||||
*/
|
||||
int net_send(int sockfd, const void *buf, int len, int flags)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
r = send(s_ws[sockfd], (const char *)buf, len, flags);
|
||||
if (r >= 0) {
|
||||
s_socks[sockfd].send_bytes += (uint32_t)r;
|
||||
s_net_errno = NET_OK;
|
||||
return r;
|
||||
}
|
||||
|
||||
{
|
||||
int err = WSAGetLastError();
|
||||
if (err == WSAEWOULDBLOCK) {
|
||||
s_net_errno = NET_ERR_WOULDBLOCK;
|
||||
return 0;
|
||||
}
|
||||
s_net_errno = NET_ERR;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(TCP)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* len - 缓冲区大小
|
||||
* flags - 通常为 0,可设 NET_MSG_DONTWAIT(Socket 本就非阻塞)
|
||||
* 出口参数:buf - 填充接收数据
|
||||
* 返回值:实际接收字节数;0 = 对端关闭;-1 = 无数据(NET_ERR_WOULDBLOCK)或错误
|
||||
* 限定条件:socket 已建立
|
||||
* 函数说明:与 MCU 端语义一致——>0 有数据,0 对端关闭,<0 且 errno 区分
|
||||
* "暂无数据"与"真错误"
|
||||
*/
|
||||
int net_recv(int sockfd, void *buf, int len, int flags)
|
||||
{
|
||||
int r;
|
||||
|
||||
(void)flags;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
r = recv(s_ws[sockfd], (char *)buf, len, 0);
|
||||
if (r > 0) {
|
||||
s_socks[sockfd].recv_bytes += (uint32_t)r;
|
||||
s_net_errno = NET_OK;
|
||||
return r;
|
||||
}
|
||||
if (r == 0) {
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
int err = WSAGetLastError();
|
||||
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:发送 UDP 数据报(含目标地址)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 数据缓冲区
|
||||
* len - 数据长度
|
||||
* flags - 通常为 0
|
||||
* dest_addr - 目标地址
|
||||
* addrlen - 地址长度
|
||||
* 出口参数:无
|
||||
* 返回值:实际发送字节数,-1 失败
|
||||
* 限定条件:socket 已创建,buf/dest_addr 非空
|
||||
*/
|
||||
int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
const struct net_sockaddr *dest_addr, int addrlen)
|
||||
{
|
||||
struct sockaddr_in wsaddr;
|
||||
int r;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0 || dest_addr == NULL ||
|
||||
addrlen < (int)sizeof(struct sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
to_ws_addr(dest_addr, &wsaddr);
|
||||
|
||||
r = sendto(s_ws[sockfd], (const char *)buf, len, flags,
|
||||
(struct sockaddr *)&wsaddr, sizeof(wsaddr));
|
||||
if (r >= 0) {
|
||||
s_net_errno = NET_OK;
|
||||
return r;
|
||||
}
|
||||
|
||||
{
|
||||
int err = WSAGetLastError();
|
||||
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收 UDP 数据报(含源地址)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* buf - 接收缓冲区
|
||||
* len - 缓冲区大小
|
||||
* flags - 通常为 0
|
||||
* src_addr - 输出:发送方地址(可为 NULL)
|
||||
* addrlen - 输入输出:源地址长度(可为 NULL)
|
||||
* 出口参数:buf/src_addr/addrlen - 填充
|
||||
* 返回值:实际接收字节数,-1 失败
|
||||
* 限定条件:socket 已创建,buf 非空
|
||||
*/
|
||||
int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
struct net_sockaddr *src_addr, int *addrlen)
|
||||
{
|
||||
struct sockaddr_in wsaddr;
|
||||
int wslen;
|
||||
int r;
|
||||
|
||||
(void)flags;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (buf == NULL || len < 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
wslen = sizeof(wsaddr);
|
||||
r = recvfrom(s_ws[sockfd], (char *)buf, len, 0,
|
||||
(struct sockaddr *)&wsaddr, &wslen);
|
||||
if (r < 0) {
|
||||
int err = WSAGetLastError();
|
||||
s_net_errno = (err == WSAEWOULDBLOCK) ? NET_ERR_WOULDBLOCK : NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (src_addr != NULL && addrlen != NULL) {
|
||||
from_ws_addr(&wsaddr, src_addr);
|
||||
*addrlen = (int)sizeof(struct net_sockaddr_in);
|
||||
}
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:关闭 socket
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* 出口参数:无
|
||||
* 返回值:0 成功,-1 失败
|
||||
* 限定条件:socket 已打开(in_use == 1)
|
||||
* 函数说明:关闭 Winsock 句柄并释放控制块(in_use 清零,net_get_sock 转返回 NULL)
|
||||
*/
|
||||
int net_close(int sockfd)
|
||||
{
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
closesocket(s_ws[sockfd]);
|
||||
s_socks[sockfd].in_use = 0;
|
||||
s_socks[sockfd].state = NET_SOCK_STATE_CLOSED;
|
||||
s_ws[sockfd] = INVALID_SOCKET;
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:注册事件回调
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* cb - 回调函数(NULL 取消注册)
|
||||
* arg - 用户参数
|
||||
* 出口参数:无
|
||||
* 返回值:0 成功,-1 失败
|
||||
* 限定条件:socket 已创建
|
||||
* 函数说明:模拟器以非阻塞 I/O 直接感知事件,回调仅存储不主动触发
|
||||
*/
|
||||
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg)
|
||||
{
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_socks[sockfd].event_cb = cb;
|
||||
s_socks[sockfd].event_arg = arg;
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:获取 socket 最后错误码
|
||||
* 入口参数:无
|
||||
* 返回值:最近一次 net_* 操作设置的错误码(NET_OK / NET_ERR_xxx)
|
||||
* 限定条件:无
|
||||
*/
|
||||
int net_get_errno(void)
|
||||
{
|
||||
return s_net_errno;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:将点分十进制 IP 字符串转为网络字节序 32 位地址
|
||||
* 入口参数:cp - 点分十进制字符串(如 "192.168.1.100")
|
||||
* 出口参数:无
|
||||
* 返回值:网络字节序 IP(参数无效返回 0)
|
||||
* 限定条件:无
|
||||
* 函数说明:逐段解析后按 NET_HTONL 转网络字节序(与 MCU 端实现一致)
|
||||
*/
|
||||
uint32_t net_inet_addr(const char *cp)
|
||||
{
|
||||
uint32_t addr = 0;
|
||||
int i;
|
||||
|
||||
if (cp == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
uint32_t val = 0;
|
||||
while (*cp >= '0' && *cp <= '9') {
|
||||
val = val * 10 + (uint32_t)(*cp - '0');
|
||||
cp++;
|
||||
}
|
||||
addr |= (val << (i * 8));
|
||||
if (*cp == '.') {
|
||||
cp++;
|
||||
}
|
||||
}
|
||||
|
||||
return NET_HTONL(addr);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:将网络字节序 32 位 IP 转为点分十进制字符串
|
||||
* 入口参数:addr - 网络字节序 IP
|
||||
* buf - 输出缓冲区(至少 16 字节)
|
||||
* 出口参数:buf - 填充字符串
|
||||
* 返回值:buf 指针(buf 为 NULL 时返回 NULL)
|
||||
* 限定条件:buf 非空且长度足够
|
||||
*/
|
||||
char *net_inet_ntoa(uint32_t addr, char *buf)
|
||||
{
|
||||
uint32_t naddr = NET_NTOHL(addr);
|
||||
|
||||
if (buf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(buf, 16, "%lu.%lu.%lu.%lu",
|
||||
(unsigned long)(naddr & 0xFF),
|
||||
(unsigned long)((naddr >> 8) & 0xFF),
|
||||
(unsigned long)((naddr >> 16) & 0xFF),
|
||||
(unsigned long)((naddr >> 24) & 0xFF));
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:端口字节序转换(主机序 -> 网络序)
|
||||
* 入口参数:hostshort - 主机序端口
|
||||
* 返回值:网络序端口
|
||||
* 限定条件:无
|
||||
*/
|
||||
uint16_t net_htons(uint16_t hostshort)
|
||||
{
|
||||
return NET_HTONS(hostshort);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:端口字节序转换(网络序 -> 主机序)
|
||||
* 入口参数:netshort - 网络序端口
|
||||
* 返回值:主机序端口
|
||||
* 限定条件:无
|
||||
*/
|
||||
uint16_t net_ntohs(uint16_t netshort)
|
||||
{
|
||||
return NET_NTOHS(netshort);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:IP 地址字节序转换(主机序 -> 网络序)
|
||||
* 入口参数:hostlong - 主机序 IP
|
||||
* 返回值:网络序 IP
|
||||
* 限定条件:无
|
||||
*/
|
||||
uint32_t net_htonl(uint32_t hostlong)
|
||||
{
|
||||
return NET_HTONL(hostlong);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:IP 地址字节序转换(网络序 -> 主机序)
|
||||
* 入口参数:netlong - 网络序 IP
|
||||
* 返回值:主机序 IP
|
||||
* 限定条件:无
|
||||
*/
|
||||
uint32_t net_ntohl(uint32_t netlong)
|
||||
{
|
||||
return NET_NTOHL(netlong);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:获取 Socket 控制块指针
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* 返回值:控制块指针;描述符越界或已释放(in_use == 0)时返回 NULL
|
||||
* 限定条件:无
|
||||
* 函数说明:上层据此判断"监听 Socket 是否随客户端一同失效"(单连接模型)
|
||||
*/
|
||||
net_sock_t *net_get_sock(int sockfd)
|
||||
{
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
return NULL;
|
||||
}
|
||||
if (s_socks[sockfd].in_use == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return &s_socks[sockfd];
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:获取 Socket 本地地址(IP + Port)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
* addr - 输出:本地地址
|
||||
* addrlen - 输入输出:地址结构体长度
|
||||
* 出口参数:addr/addrlen - 填充
|
||||
* 返回值:0 成功,-1 失败
|
||||
* 限定条件:socket 已创建
|
||||
* 函数说明:经 Winsock getsockname 读取实际绑定地址
|
||||
*/
|
||||
int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen)
|
||||
{
|
||||
struct sockaddr_in wsaddr;
|
||||
int wslen;
|
||||
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS || s_socks[sockfd].in_use == 0) {
|
||||
s_net_errno = NET_ERR_BADF;
|
||||
return -1;
|
||||
}
|
||||
if (addr == NULL || addrlen == NULL ||
|
||||
*addrlen < (int)sizeof(struct net_sockaddr_in)) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
wslen = sizeof(wsaddr);
|
||||
if (getsockname(s_ws[sockfd], (struct sockaddr *)&wsaddr, &wslen) == SOCKET_ERROR) {
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
from_ws_addr(&wsaddr, addr);
|
||||
*addrlen = (int)sizeof(struct net_sockaddr_in);
|
||||
|
||||
s_net_errno = NET_OK;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 模块名称:UI Network Sink Backend (模拟器实现)
|
||||
* 模块功能:RemoDispBus 服务端在 PC 模拟器端的平台相关操作——
|
||||
* 读回 SDL 屏幕转 1bpp(LCDMEM 数据源)与经 LVGL 注入远端按键
|
||||
* 适用平台:Windows / Linux / macOS (SDL2)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* 2026-09-10 王建锋 创建初始版本(双端共用 ui_net_sink 的平台后端)
|
||||
*/
|
||||
#include "ui_net_sink_backend.h"
|
||||
|
||||
#include "ui_port.h" /* 远端按键码 UI_KEY_* 常量 */
|
||||
#include "sim_port.h" /* sim_port_get_screen_mono */
|
||||
#include "lvgl.h" /* lv_group_send_data / LV_KEY_* */
|
||||
|
||||
/*
|
||||
* 函数功能:将远端按键码映射为 LVGL 按键码
|
||||
* 入口参数:remo_key - 远端按键码(UI_KEY_* 之一)
|
||||
* 返回值:对应 LV_KEY_* 值;无法识别返回 0
|
||||
* 限定条件:无
|
||||
* 函数说明:远端按键码与 LVGL 按键码一一对应
|
||||
*/
|
||||
static uint32_t sim_map_key(uint8_t remo_key)
|
||||
{
|
||||
switch (remo_key) {
|
||||
case UI_KEY_UP: return LV_KEY_UP;
|
||||
case UI_KEY_DOWN: return LV_KEY_DOWN;
|
||||
case UI_KEY_LEFT: return LV_KEY_LEFT;
|
||||
case UI_KEY_RIGHT: return LV_KEY_RIGHT;
|
||||
case UI_KEY_ENTER: return LV_KEY_ENTER;
|
||||
case UI_KEY_ESC: return LV_KEY_ESC;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读回 SDL 屏幕实际呈现像素并转为 1bpp 位图(LCDMEM 数据源)
|
||||
* 入口参数:out - 输出缓冲(≥ UI_NET_SINK_MONO_SIZE 字节)
|
||||
* out_size - out 容量(字节)
|
||||
* 返回值:0 - 成功,-1 - 失败
|
||||
* 限定条件:sim_port_init() 已成功;须在 SDL 所属(主循环)线程调用
|
||||
* 函数说明:窗口模式显示缓冲经 I1→ARGB8888 转换,不能直读,
|
||||
* 故读回屏幕像素再按红通道阈值打包 1bpp(布局与 MCU 端一致)
|
||||
*/
|
||||
int ui_net_sink_get_lcdmem(uint8_t *out, uint32_t out_size)
|
||||
{
|
||||
if (out == NULL || out_size < UI_NET_SINK_MONO_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
return sim_port_get_screen_mono(out, out_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:经 LVGL 输入组注入一个远端按键
|
||||
* 入口参数:remo_key - 远端按键码(UI_KEY_* 之一)
|
||||
* 返回值:无
|
||||
* 限定条件:默认输入组已建立
|
||||
* 函数说明:映射为 LV_KEY_* 后经 lv_group_send_data 发向焦点对象
|
||||
*/
|
||||
void ui_net_sink_inject_key(uint8_t remo_key)
|
||||
{
|
||||
lv_group_t *g;
|
||||
uint32_t key;
|
||||
|
||||
key = sim_map_key(remo_key);
|
||||
if (key == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g = lv_group_get_default();
|
||||
if (g != NULL) {
|
||||
lv_group_send_data(g, key);
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
/*
|
||||
* 模块名称:UI Network Sink (RemoDispBus 服务端 — 模拟器 Winsock 版)
|
||||
* 模块功能:与 MCU 端相同协议,用 Windows Winsock 实现 TCP 监听 + RemoDispBus 帧收发
|
||||
* 适用平台:Windows (Winsock2)
|
||||
*/
|
||||
#include "ui_net_sink.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include "lvgl.h"
|
||||
#include "sim_port.h"
|
||||
|
||||
/* RemoDispBus 标签与命令(与 ui_net_sink.h / MCU 端一致) */
|
||||
#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 字节 */
|
||||
|
||||
/* 远端按键码(与 ui_port.h 一致) */
|
||||
#define UI_KEY_UP 0x02
|
||||
#define UI_KEY_DOWN 0x40
|
||||
#define UI_KEY_LEFT 0x10
|
||||
#define UI_KEY_RIGHT 0x08
|
||||
#define UI_KEY_ENTER 0x20
|
||||
#define UI_KEY_ESC 0x01
|
||||
|
||||
static uint8_t g_tx[4 + 4 + UI_MEMSIZE + 1];
|
||||
static uint8_t g_lcdmem_data[4 + UI_MEMSIZE];
|
||||
static uint8_t g_rx[4096];
|
||||
|
||||
static SOCKET g_listen = INVALID_SOCKET;
|
||||
static SOCKET g_client = INVALID_SOCKET;
|
||||
static int g_recv_off = 0; /* g_rx 已累积字节数 */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void put_u16(uint8_t *p, uint16_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v >> 8);
|
||||
p[1] = (uint8_t)(v & 0xFF);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void remo_send(SOCKET s, uint8_t cmd, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (s == INVALID_SOCKET) {
|
||||
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);
|
||||
send(s, (const char *)p, 4 + len + 1, 0);
|
||||
}
|
||||
|
||||
static void remo_send_init(SOCKET s)
|
||||
{
|
||||
uint8_t init[8];
|
||||
put_u16(init, UI_W);
|
||||
put_u16(init + 2, UI_H);
|
||||
put_u32(init + 4, UI_MEMSIZE);
|
||||
remo_send(s, CMD_INIT, init, 6);
|
||||
}
|
||||
|
||||
static uint32_t sim_map_key(uint8_t remo_key)
|
||||
{
|
||||
switch (remo_key) {
|
||||
case UI_KEY_UP: return LV_KEY_UP;
|
||||
case UI_KEY_DOWN: return LV_KEY_DOWN;
|
||||
case UI_KEY_LEFT: return LV_KEY_LEFT;
|
||||
case UI_KEY_RIGHT: return LV_KEY_RIGHT;
|
||||
case UI_KEY_ENTER: return LV_KEY_ENTER;
|
||||
case UI_KEY_ESC: return LV_KEY_ESC;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void remo_handle(SOCKET s, const uint8_t *f, int n)
|
||||
{
|
||||
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;
|
||||
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(s, CMD_KEEPLIVE, NULL, 0);
|
||||
break;
|
||||
case CMD_INIT:
|
||||
remo_send_init(s);
|
||||
break;
|
||||
case CMD_KEY:
|
||||
if (len >= 1) {
|
||||
uint32_t key = sim_map_key(data[0]);
|
||||
if (key != 0) {
|
||||
lv_group_send_data(lv_group_get_default(), key);
|
||||
}
|
||||
}
|
||||
remo_send(s, 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) {
|
||||
/* 窗口模式 I1+PARTIAL 显示缓冲系分块且经 I1→ARGB8888 转换,
|
||||
* 不能直接读;读回屏幕实际呈现像素再转 1bpp(布局与 MCU 端一致) */
|
||||
if (sim_port_get_screen_mono(g_lcdmem_data + 4, UI_MEMSIZE) == 0) {
|
||||
memcpy(g_lcdmem_data, data, 4);
|
||||
remo_send(s, CMD_LCDMEM, g_lcdmem_data, 4 + UI_MEMSIZE);
|
||||
}
|
||||
/* 读回失败则静默丢弃(与 MCU 端一致) */
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 尝试从已接收数据中解析一帧并处理 */
|
||||
static void try_parse_frames(SOCKET s)
|
||||
{
|
||||
for (;;) {
|
||||
if (g_recv_off < 5) {
|
||||
break; /* 不够最小帧长 */
|
||||
}
|
||||
uint16_t payload_len = ((uint16_t)g_rx[2] << 8) | g_rx[3];
|
||||
int frame_total = 4 + payload_len + 1;
|
||||
if (g_recv_off < frame_total) {
|
||||
break; /* 帧不完整,等更多数据 */
|
||||
}
|
||||
remo_handle(s, g_rx, frame_total);
|
||||
/* 移除已处理帧(RemoDispBus 是请求-响应模式,不会有粘包;但以防万一保留移动逻辑) */
|
||||
int remain = g_recv_off - frame_total;
|
||||
if (remain > 0) {
|
||||
memmove(g_rx, g_rx + frame_total, remain);
|
||||
}
|
||||
g_recv_off = remain;
|
||||
}
|
||||
}
|
||||
|
||||
int ui_net_sink_start(uint16_t port)
|
||||
{
|
||||
g_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (g_listen == INVALID_SOCKET) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int yes = 1;
|
||||
setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if (bind(g_listen, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
closesocket(g_listen);
|
||||
g_listen = INVALID_SOCKET;
|
||||
return -1;
|
||||
}
|
||||
if (listen(g_listen, 1) == SOCKET_ERROR) {
|
||||
closesocket(g_listen);
|
||||
g_listen = INVALID_SOCKET;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 设为非阻塞 */
|
||||
u_long mode = 1;
|
||||
ioctlsocket(g_listen, FIONBIO, &mode);
|
||||
|
||||
g_client = INVALID_SOCKET;
|
||||
g_recv_off = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ui_net_sink_stop(void)
|
||||
{
|
||||
if (g_client != INVALID_SOCKET) {
|
||||
closesocket(g_client);
|
||||
g_client = INVALID_SOCKET;
|
||||
}
|
||||
if (g_listen != INVALID_SOCKET) {
|
||||
closesocket(g_listen);
|
||||
g_listen = INVALID_SOCKET;
|
||||
}
|
||||
g_recv_off = 0;
|
||||
}
|
||||
|
||||
void ui_net_sink_poll(void)
|
||||
{
|
||||
if (g_listen == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 无客户端时尝试接受连接 */
|
||||
if (g_client == INVALID_SOCKET) {
|
||||
struct sockaddr_in cli;
|
||||
int cli_len = sizeof(cli);
|
||||
SOCKET c = accept(g_listen, (struct sockaddr *)&cli, &cli_len);
|
||||
if (c != INVALID_SOCKET) {
|
||||
u_long mode = 1;
|
||||
ioctlsocket(c, FIONBIO, &mode);
|
||||
g_client = c;
|
||||
g_recv_off = 0;
|
||||
/* 不主动上报 INIT(与 MCU 端一致:避免污染客户端首帧读取,由客户端按需请求) */
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* 有客户端时非阻塞收帧 */
|
||||
int n = recv(g_client, (char *)(g_rx + g_recv_off),
|
||||
(int)(sizeof(g_rx) - g_recv_off), 0);
|
||||
if (n > 0) {
|
||||
g_recv_off += n;
|
||||
try_parse_frames(g_client);
|
||||
} else if (n == 0) {
|
||||
closesocket(g_client);
|
||||
g_client = INVALID_SOCKET;
|
||||
g_recv_off = 0;
|
||||
} else {
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) {
|
||||
closesocket(g_client);
|
||||
g_client = INVALID_SOCKET;
|
||||
g_recv_off = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user