Browse Source

【调通打标】UDP文件发送调通

master
Lizongdi 5 hours ago
parent
commit
716091c608
  1. 8
      bspMCU/l_stm32.c
  2. 82
      bspMCU/l_uart.c
  3. 874
      bspMCU/rd_udp.c
  4. 102
      peripheral/include/rd_udp.h

8
bspMCU/l_stm32.c

@ -767,8 +767,16 @@ int luaopen_stm32(lua_State *L)
lua_call(L, 0, 1); lua_call(L, 0, 1);
lua_getfield(L, -1, "new"); lua_getfield(L, -1, "new");
lua_setfield(L, -3, "COM"); lua_setfield(L, -3, "COM");
lua_getfield(L, -1, "new");
lua_setglobal(L, "COM"); /* 全局 COM: 上位机/新脚本用法 (st.COM 保留兼容) */
lua_getfield(L, -1, "console"); lua_getfield(L, -1, "console");
lua_setfield(L, -3, "CON"); lua_setfield(L, -3, "CON");
lua_getfield(L, -1, "console");
lua_setglobal(L, "CON"); /* 全局 CON */
#ifdef USE_LWIP
lua_getfield(L, -1, "udp");
lua_setglobal(L, "UDP"); /* 全局 UDP: 上位机 provision 行 c=UDP(...) 依赖 */
#endif
lua_pop(L, 1); lua_pop(L, 1);
#endif #endif

82
bspMCU/l_uart.c

@ -3,16 +3,25 @@
#include "lua_base.h" #include "lua_base.h"
#include "bsp_uart.h" #include "bsp_uart.h"
#include "ymodem.h" #include "ymodem.h"
#ifdef USE_LWIP
#include "rd_udp.h" /* UDP 链路句柄 (上位机 provision 经 telnet 下发 c=UDP(...)) */
#endif
#ifdef PRINT_ID #ifdef PRINT_ID
#ifdef USE_USB #ifdef USE_USB
#include "bsp_usb.h" #include "bsp_usb.h"
#endif #endif
#endif #endif
/* 句柄类型 (主仓库 lacmd.c 同款语义) */
#define COM_TYPE_SERIAL 0
#define COM_TYPE_UDP 1
// 串口对象结构体 // 串口对象结构体
typedef struct typedef struct
{ {
TComCtrl *m_ptComCtrl; TComCtrl *m_ptComCtrl;
int m_iType; /* COM_TYPE_* */
int m_iOwned; /* 1=自建句柄 GC 关闭 (仅 UDP); 0=借用, 维持 robot 现状不关 */
} stm32_com_t; } stm32_com_t;
TComCtrl *g_ptCom1; TComCtrl *g_ptCom1;
@ -42,6 +51,8 @@ static int l_com_new(lua_State *L)
// 创建 userdata // 创建 userdata
stm32_com_t *u = (stm32_com_t*)lua_newuserdata(L, sizeof(stm32_com_t)); stm32_com_t *u = (stm32_com_t*)lua_newuserdata(L, sizeof(stm32_com_t));
u->m_iType = COM_TYPE_SERIAL;
u->m_iOwned = 0; /* 串口句柄维持 robot 现状: 不随 GC 关闭 */
if (id == 1 || id == 2) if (id == 1 || id == 2)
{ {
@ -184,6 +195,8 @@ static int l_con_new(lua_State *L)
return 1; return 1;
} }
stm32_com_t *u = (stm32_com_t *)lua_newuserdata(L, sizeof(stm32_com_t)); stm32_com_t *u = (stm32_com_t *)lua_newuserdata(L, sizeof(stm32_com_t));
u->m_iType = COM_TYPE_SERIAL;
u->m_iOwned = 0;
u->m_ptComCtrl = ptCtrl; u->m_ptComCtrl = ptCtrl;
luaL_getmetatable(L, "stm32.COMMT"); luaL_getmetatable(L, "stm32.COMMT");
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
@ -194,6 +207,67 @@ static int l_con_new(lua_State *L)
#endif #endif
} }
#ifdef USE_LWIP
// __gc: 仅自建 UDP 句柄关闭 (上位机 provision 行 c=nil collectgarbage()
// 重绑旧句柄、telnet 断线重连 lua_init 重建 Lua 态时, 靠这里释放旧 UDP
// 链路/8007 端口, 否则二次 Rd_UdpCreate bind 失败);
// 串口/CON 借用句柄维持 robot 现状 (不关, 生命周期归宿主)
static int l_com_gc(lua_State *L)
{
stm32_com_t *u = (stm32_com_t*)luaL_testudata(L, 1, "stm32.COMMT");
if (u != NULL && u->m_iOwned && u->m_iType == COM_TYPE_UDP && u->m_ptComCtrl != NULL)
{
Rd_UdpClose(u->m_ptComCtrl);
u->m_ptComCtrl = NULL;
}
return 0;
}
// 构造函数: UDP(localport, "remoteip" [,remoteport [,txsrcport]]) — UDP 链路句柄
// 上位机连接后自动下发 c=UDP(8007,"<本端IP>",9007) 建 ymodem 文件链路,
// 之后 c:ry('.')/c:sy('f') 与串口句柄用法完全一致 (rym_* 按 TComCtrl 分派)
static int l_udp_new(lua_State *L)
{
if (lua_gettop(L) == 0)
{
lua_print("Usage: UDP(localport, \"remoteip\" [,remoteport [,txsrcport]])\n");
lua_print(" UDP(8007, \"192.168.1.100\", 9007) -- 固定直连: bind 8007 rx, 发往 9007\n");
return 0;
}
int localport = (int)luaL_checkinteger(L, 1);
const char *ip = luaL_checkstring(L, 2);
int remoteport = (int)luaL_optinteger(L, 3, 0);
int txsrcport = (int)luaL_optinteger(L, 4, 0);
TRdUdpCfg stUdpCfg = {
.m_psRemoteIp = ip,
.m_iRemotePort = remoteport,
.m_iLocalPort = localport,
.m_iTxSrcPort = txsrcport,
.m_uiBufSize = 4096,
.m_psName = "lua",
.m_pfCheck = NULL, /* 数据报全透传: ymodem 原始字节语义 */
.m_pfDecode = NULL,
};
TComCtrl *ptComCtrl = Rd_UdpCreate(&stUdpCfg);
if (ptComCtrl == NULL)
{
return luaL_error(L, "UDP(%d, %s, %d) create failed", localport, ip, remoteport);
}
stm32_com_t *u = (stm32_com_t*)lua_newuserdata(L, sizeof(stm32_com_t));
u->m_ptComCtrl = ptComCtrl;
u->m_iType = COM_TYPE_UDP;
u->m_iOwned = 1; // 自建句柄: GC 时关闭 (重连 provision 幂等的关键)
luaL_getmetatable(L, "stm32.COMMT");
lua_setmetatable(L, -2);
lua_print("UDP bind %d txsrc %d -> %s:%d\n", localport, txsrcport, ip, remoteport);
return 1;
}
#endif /* USE_LWIP */
// 模块注册 // 模块注册
int luaopen_stm32_com(lua_State *L) int luaopen_stm32_com(lua_State *L)
{ {
@ -209,6 +283,9 @@ int luaopen_stm32_com(lua_State *L)
{"read", l_com_read}, {"read", l_com_read},
{"ry", l_com_ry}, {"ry", l_com_ry},
{"sy", l_com_sy}, {"sy", l_com_sy},
#ifdef USE_LWIP
{"__gc", l_com_gc},
#endif
{NULL, NULL} {NULL, NULL}
}; };
const luaL_Reg *l; const luaL_Reg *l;
@ -231,6 +308,11 @@ int luaopen_stm32_com(lua_State *L)
lua_pushcfunction(L, l_con_new); lua_pushcfunction(L, l_con_new);
lua_setfield(L, -2, "console"); lua_setfield(L, -2, "console");
#ifdef USE_LWIP
lua_pushcfunction(L, l_udp_new);
lua_setfield(L, -2, "udp");
#endif
return 1; return 1;
} }

874
bspMCU/rd_udp.c

@ -0,0 +1,874 @@
/******************************************************************************
版权所有 (C), 2018-2099, Radkil
******************************************************************************
文 件 名 : rd_udp.c
版 本 号 : 初稿
作 者 : Radkil
生成日期 : 2026年11月3日
最近修改 :
功能描述 : 跨平台 UDP 传输实现 (TComCtrl 集成, 与 rd_serial 同构)
USE_LWIP → lwip/sockets.h (需 tcpip 栈已启动)
BUILD_PLATFORM_LINUX → POSIX socket + pthread
BUILD_PLATFORM_WIN32 → winsock2
与 Linux 标准 BSD socket 的差异收敛到本文件内:
- 头文件按平台宏分支
- IP 解析不依赖 inet_addr/inet_pton (sscanf + htonl)
- 接收超时不用 SO_RCVTIMEO (lwip 为 int 毫秒, Linux 为
struct timeval), 统一 fcntl(O_NONBLOCK) + 让步轮询
UDP 广播设备发现 (beacon, 见 rd_udp.h 宏):
- 独立发现 socket 绑 RD_UDP_DISCOVERY_PORT, 周期广播 beacon
- peer 表记录 (源IP, data_port), 数据帧扇出给全部活 peer
- m_psRemoteIp 非空时固定直连与发现扇出并存
修改历史 :
1.日 期 : 2026年11月3日
作 者 : Radkil
修改内容 : 创建文件
2.日 期 : 2026年11月4日
作 者 : Radkil
修改内容 : 新增 beacon 设备发现: 发现 socket+线程/peer 表/
发送扇出, m_psRemoteIp 可空 (空 = 纯发现模式)
******************************************************************************/
/* ─── 平台头文件 ─────────────────────────────────────────────────────── */
/* __ANDROID__ (NDK 自动定义) 并入 POSIX 分支: Bionic 的 socket/fcntl/errno
* 与 Linux 同名同义, 头文件一致 (与 rd_thread.h 的平台分支同模式);
* 不要用 BUILD_PLATFORM_LINUX 识别安卓 —— 会误激活 rd_serial 的 termios 路径 */
#include "common.h"
#ifdef USE_LWIP
#include "lwip/sockets.h"
#include "lwip/netdb.h"
/* robot 适配: 线程创建走固件既有 CMSIS-RTOS2 (与 My_freeRTOS.c talnet 任务同款),
* 不引入 Rd_ThreadCreate 跨平台抽象; 蹦床终止用 FreeRTOS vTaskDelete
* (头文件顺序与 My_freeRTOS.c 完全一致) */
#include "cmsis_os.h"
#include "cmsis_os2.h"
#include "FreeRTOS.h"
#include "task.h"
#endif /* USE_LWIP */
#if defined(BUILD_PLATFORM_LINUX) || defined(__ANDROID__)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif /* BUILD_PLATFORM_LINUX || __ANDROID__ */
#ifdef BUILD_PLATFORM_WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#endif /* BUILD_PLATFORM_WIN32 */
/* ─── 项目头文件 ─────────────────────────────────────────────────────── */
#include "rd_thread.h"
#include "rd_time.h"
#include "com.h"
#include "rd_udp.h"
/*==============================================*
* 常量/宏 *
*==============================================*/
#define RD_UDP_RECV_BUF_SIZE 2048 /* 接收线程单次读取缓冲(单报文上限) */
/* ── beacon 报文布局 (22B 二进制, 周期重发自愈 → 无需 CRC) ──
* [0..1] magic "RD" (0x52 0x44) 与 55 55 数据帧天然区分
* [2] ver = 1
* [3] type = 1 (beacon)
* [4..7] instance_id (u32, 网络序) 防自环: 同机广播自己也会收到一份
* [8..9] data_port (htons) 本端数据帧端口 (对端据此回发数据)
* [10..21] name[12] 设备名截断 (可选, 空补 0) */
#define RD_UDP_BEACON_SIZE 22
#define RD_UDP_BEACON_MAGIC_0 0x52 /* 'R' */
#define RD_UDP_BEACON_MAGIC_1 0x44 /* 'D' */
#define RD_UDP_BEACON_VER 1
#define RD_UDP_BEACON_TYPE 1 /* 1 = beacon */
#define RD_UDP_NAME_MAX 12 /* beacon name 字段宽度 */
/* 未识别平台: 不提供 socket 环境, 创建直接失败 */
#if !defined(USE_LWIP) && !defined(BUILD_PLATFORM_LINUX) && \
!defined(BUILD_PLATFORM_WIN32) && !defined(__ANDROID__)
#define RD_UDP_NO_PLATFORM 1
#endif
#ifndef RD_UDP_NO_PLATFORM
#if defined(BUILD_PLATFORM_WIN32)
#define RD_UDP_SOCK_ERR() WSAGetLastError()
/* Win32 错误码走 WSA* 命名空间, 与 errno 体系不同 */
#define RD_UDP_ERR_YIELD(e) ((e) == WSAEWOULDBLOCK || (e) == WSAEINTR || (e) == WSAECONNREFUSED)
#define RD_UDP_CLOSE(s) closesocket(s)
#else
#define RD_UDP_SOCK_ERR() errno
/* ECONNREFUSED: Linux 对端未监听时 ICMP 异步错误, 容忍不退出 */
#define RD_UDP_ERR_YIELD(e) ((e) == EAGAIN || (e) == EWOULDBLOCK || (e) == EINTR || (e) == ECONNREFUSED)
#define RD_UDP_CLOSE(s) close(s)
#endif
/* ═══════════════════════════════════════════════════════════════════════ *
* 平台特定 UserData 结构 *
* ═══════════════════════════════════════════════════════════════════════ */
/* beacon 发现的活 peer (design: 同 ip+port 刷新 last_seen, 超时剔除) */
typedef struct
{
uint32_t m_uiIp; /* 对端 IP (网络序, beacon 源地址) */
uint16_t m_uiDataPort; /* beacon 携带的对端数据帧端口 */
uint32_t m_uiLastSeen; /* 最近活跃时刻 Rd_GetTime() (ms) */
char m_sName[RD_UDP_NAME_MAX + 1];
} TRdUdpPeer;
typedef struct
{
int m_iRxSocket; /* 接收 socket (bind 本地端口) */
int m_iTxSocket; /* 发送 socket (可复用 m_iRxSocket) */
struct sockaddr_in m_stRemoteAddr; /* 固定对端地址 (发送目的, 可无效) */
volatile int m_iRunning; /* 接收/发现线程运行标志 */
/* ── beacon 设备发现 ── */
uint32_t m_uiInstanceId; /* 本实例标识 (create 时生成, 防自环) */
uint16_t m_uiDataPort; /* 本端数据帧端口 (beacon 携带, 0=只发不收) */
char m_sName[RD_UDP_NAME_MAX + 1]; /* 设备名 (beacon 携带, 可空) */
struct sockaddr_in m_stDiscAddr; /* beacon 广播目的 255.255.255.255:发现端口 */
int m_iDiscSocket; /* 发现接收 socket (-1 = 发现降级关闭) */
int m_iFixedValid; /* 固定对端有效 (m_psRemoteIp 非空) */
int m_iBcnWarned; /* beacon 广播失败已警告 (仅首次, 防刷屏) */
TRdUdpPeer m_astPeers[RD_UDP_PEER_MAX]; /* 无锁: 发现线程单写 +
发送线程读, u32/u16 对齐写在双平台均原子, demo 级容忍 */
} TUdpUserData;
/* ═══════════════════════════════════════════════════════════════════════ *
* 文件级全局 (cbk_ComSend 签名限制, 单链路约束) *
* ═══════════════════════════════════════════════════════════════════════ */
static TComCtrl *g_ptUdpComCtrl = NULL;
/* ═══════════════════════════════════════════════════════════════════════ *
* 内部辅助 *
* ═══════════════════════════════════════════════════════════════════════ */
/* 接收线程让步 (非阻塞轮询间隔) */
static void rd_udp_yield(void)
{
#if defined(BUILD_PLATFORM_LINUX) || defined(__ANDROID__)
usleep(1000);
#elif defined(BUILD_PLATFORM_WIN32)
Sleep(1);
#else
Rd_Delay(1);
#endif
}
/* 点分十进制 → sockaddr_in (不依赖 inet_addr/inet_pton, 全平台一致) */
static int rd_udp_fill_remote(struct sockaddr_in *_pstAddr,
const char *_psIp, int _iPort)
{
unsigned int a = 0, b = 0, c = 0, d = 0;
if (_pstAddr == NULL || _psIp == NULL ||
RD_STRLEN(_psIp) >= RD_UDP_IP_MAX - 1)
{
return RD_FAILURE;
}
if (sscanf(_psIp, "%u.%u.%u.%u", &a, &b, &c, &d) != 4 ||
a > 255 || b > 255 || c > 255 || d > 255)
{
return RD_FAILURE;
}
RD_MEMSET(_pstAddr, 0, sizeof(*_pstAddr));
_pstAddr->sin_family = AF_INET;
_pstAddr->sin_port = htons((uint16_t)_iPort);
_pstAddr->sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
return RD_SUCCESS;
}
/* 接收 socket 非阻塞 (lwip fcntl 支持 F_GETFL/F_SETFL/O_NONBLOCK) */
static int rd_udp_set_nonblock(int _iSocket)
{
#if defined(BUILD_PLATFORM_WIN32)
u_long ulNonBlock = 1;
return (ioctlsocket(_iSocket, FIONBIO, &ulNonBlock) == 0) ? RD_SUCCESS : RD_FAILURE;
#else
int iFlags = fcntl(_iSocket, F_GETFL, 0);
if (iFlags < 0) return RD_FAILURE;
return (fcntl(_iSocket, F_SETFL, iFlags | O_NONBLOCK) >= 0) ? RD_SUCCESS : RD_FAILURE;
#endif
}
/* ─────────────────── beacon 设备发现内部辅助 ─────────────────── */
/* 网络序 IP → "a.b.c.d" (静态缓冲, 仅日志用, 用完即弃) */
static const char *rd_udp_ip_str(uint32_t _uiIpNet)
{
static char s_szIp[16];
uint32_t ui = ntohl(_uiIpNet);
snprintf(s_szIp, sizeof(s_szIp), "%u.%u.%u.%u",
(unsigned)((ui >> 24) & 0xFF), (unsigned)((ui >> 16) & 0xFF),
(unsigned)((ui >> 8) & 0xFF), (unsigned)(ui & 0xFF));
return s_szIp;
}
/* 实例标识: 地址/时刻/本地数据端口混合散列 (不依赖平台 rand, lwip 无 rand);
* 并存实例必然本地端口不同 (否则数据 bind 冲突), 身份不依赖远端配置;
* 两活实例撞号只会互相当自环过滤掉 (发现失效), 无其他副作用 */
static uint32_t rd_udp_make_instance_id(const TUdpUserData *_ptUd, uint16_t _hwDataPort)
{
uint32_t uiId = (uint32_t)(uintptr_t)_ptUd;
uiId ^= Rd_GetTime();
uiId ^= (uint32_t)_hwDataPort << 16;
uiId *= 2654435761U; /* Knuth 乘法散列, 拉开低位 */
return (uiId != 0) ? uiId : 0x52440001U; /* 避开全 0 */
}
/* beacon 入账: 同 ip+port 静默刷新 last_seen, 新发现打 log_i;
* 表满时覆盖 last_seen 最旧的项 */
static void rd_udp_peer_update(TUdpUserData *_ptUd, uint32_t _uiIp,
uint16_t _hwDataPort, const char *_psName,
uint32_t _uiNow)
{
TRdUdpPeer *ptEmpty = NULL, *ptOldest = NULL, *ptSlot = NULL;
int i;
for (i = 0; i < RD_UDP_PEER_MAX; i++)
{
TRdUdpPeer *pt = &_ptUd->m_astPeers[i];
if (pt->m_uiIp == 0) /* 空位 (0.0.0.0 不会是合法对端) */
{
if (ptEmpty == NULL) ptEmpty = pt;
continue;
}
if (pt->m_uiIp == _uiIp && pt->m_uiDataPort == _hwDataPort)
{
pt->m_uiLastSeen = _uiNow; /* 已知 peer */
return;
}
if (ptOldest == NULL || pt->m_uiLastSeen < ptOldest->m_uiLastSeen)
{
ptOldest = pt;
}
}
ptSlot = (ptEmpty != NULL) ? ptEmpty : ptOldest;
if (ptSlot == NULL) return;
RD_MEMSET(ptSlot, 0, sizeof(*ptSlot));
ptSlot->m_uiIp = _uiIp;
ptSlot->m_uiDataPort = _hwDataPort;
ptSlot->m_uiLastSeen = _uiNow;
if (_psName != NULL)
{
uint32_t uiLen = (uint32_t)RD_STRLEN(_psName);
if (uiLen > RD_UDP_NAME_MAX) uiLen = RD_UDP_NAME_MAX;
RD_MEMCPY(ptSlot->m_sName, _psName, uiLen); /* 目标已清零, 天然 NUL 结尾 */
}
log_i("udp discovery: peer %s:%u (%s)", rd_udp_ip_str(_uiIp),
(unsigned)_hwDataPort, ptSlot->m_sName[0] ? ptSlot->m_sName : "?");
}
/* 超时剔除 (发现线程每轮顺手执行, 表仅 RD_UDP_PEER_MAX 项) */
static void rd_udp_peer_sweep(TUdpUserData *_ptUd, uint32_t _uiNow)
{
int i;
for (i = 0; i < RD_UDP_PEER_MAX; i++)
{
TRdUdpPeer *pt = &_ptUd->m_astPeers[i];
if (pt->m_uiIp == 0) continue;
/* uint32 回绕安全减法 */
if ((uint32_t)(_uiNow - pt->m_uiLastSeen) > RD_UDP_PEER_TIMEOUT_MS)
{
log_i("udp discovery: peer %s:%u (%s) timeout",
rd_udp_ip_str(pt->m_uiIp), (unsigned)pt->m_uiDataPort,
pt->m_sName[0] ? pt->m_sName : "?");
RD_MEMSET(pt, 0, sizeof(*pt));
}
}
}
/* 组装并广播 beacon (复用 tx socket, 已设 SO_BROADCAST;
* 周期重发自愈 → 失败仅首次警告防刷屏) */
static void rd_udp_beacon_send(TUdpUserData *_ptUd)
{
uint8_t ucBcn[RD_UDP_BEACON_SIZE];
uint32_t uiIdNet = htonl(_ptUd->m_uiInstanceId);
uint16_t hwPortNet = htons(_ptUd->m_uiDataPort);
int iSent;
if (_ptUd->m_iTxSocket < 0) return;
RD_MEMSET(ucBcn, 0, sizeof(ucBcn));
ucBcn[0] = RD_UDP_BEACON_MAGIC_0;
ucBcn[1] = RD_UDP_BEACON_MAGIC_1;
ucBcn[2] = RD_UDP_BEACON_VER;
ucBcn[3] = RD_UDP_BEACON_TYPE;
RD_MEMCPY(&ucBcn[4], &uiIdNet, sizeof(uiIdNet));
RD_MEMCPY(&ucBcn[8], &hwPortNet, sizeof(hwPortNet));
if (_ptUd->m_sName[0] != '\0')
{
/* name 字段定长 12B, 源串已保证 NUL 结尾, 不足补 0 (RD_MEMSET 已清) */
RD_MEMCPY(&ucBcn[10], _ptUd->m_sName,
RD_STRLEN(_ptUd->m_sName) < RD_UDP_NAME_MAX
? RD_STRLEN(_ptUd->m_sName) : RD_UDP_NAME_MAX);
}
iSent = (int)sendto(_ptUd->m_iTxSocket, (char *)ucBcn, sizeof(ucBcn), 0,
(struct sockaddr *)&_ptUd->m_stDiscAddr,
sizeof(_ptUd->m_stDiscAddr));
if (iSent < 0 && !_ptUd->m_iBcnWarned)
{
_ptUd->m_iBcnWarned = 1;
log_w("udp discovery: beacon broadcast failed, err=%d (discovery degraded)",
RD_UDP_SOCK_ERR());
}
}
/* 收到疑似 beacon: 校验 magic/ver/type → 自环过滤 → 入账;
* 数据地址取 beacon 源 IP, 数据端口取报文 data_port (非源端口) */
static void rd_udp_beacon_on_rx(TUdpUserData *_ptUd, const uint8_t *_pBuf,
uint32_t _iLen, const struct sockaddr_in *_pstFrom)
{
uint32_t uiId = 0;
uint16_t hwPortNet = 0;
char sName[RD_UDP_NAME_MAX + 1];
if (_iLen < RD_UDP_BEACON_SIZE ||
_pBuf[0] != RD_UDP_BEACON_MAGIC_0 || _pBuf[1] != RD_UDP_BEACON_MAGIC_1 ||
_pBuf[2] != RD_UDP_BEACON_VER || _pBuf[3] != RD_UDP_BEACON_TYPE)
{
return; /* 非 beacon, 发现通道上直接忽略 */
}
RD_MEMCPY(&uiId, &_pBuf[4], sizeof(uiId));
if (ntohl(uiId) == _ptUd->m_uiInstanceId) return; /* 防自环: 同机广播回环 */
RD_MEMCPY(&hwPortNet, &_pBuf[8], sizeof(hwPortNet));
RD_MEMSET(sName, 0, sizeof(sName));
RD_MEMCPY(sName, &_pBuf[10], RD_UDP_NAME_MAX); /* 定宽字段, 已清零保 NUL */
rd_udp_peer_update(_ptUd, _pstFrom->sin_addr.s_addr,
ntohs(hwPortNet), sName, Rd_GetTime());
}
/* ═══════════════════════════════════════════════════════════════════════ *
* 发送回调 (cbk_ComSend → 全局 TComCtrl → UserData → 扇出 sendto) *
* ═══════════════════════════════════════════════════════════════════════ */
/* 扇出策略: 固定对端 (remote ip 非空) + beacon 发现的全部活 peer;
* 任一路成功即返回字节数, 全失败返回 RD_FAILURE (cbk_ComSend 语义不变) */
static int udp_send_impl(char *_pBuffer, uint32_t _iSize)
{
if (g_ptUdpComCtrl == NULL) return RD_FAILURE;
TUdpUserData *ptUd = (TUdpUserData *)(g_ptUdpComCtrl->m_pUserData);
if (ptUd == NULL || ptUd->m_iTxSocket < 0) return RD_FAILURE;
int iBest = -1;
uint32_t uiNow = Rd_GetTime();
int i;
/* 1. 固定对端 (m_psRemoteIp 非空时保留现状) */
if (ptUd->m_iFixedValid)
{
int iSent = (int)sendto(ptUd->m_iTxSocket, _pBuffer, _iSize, 0,
(struct sockaddr *)&ptUd->m_stRemoteAddr,
sizeof(ptUd->m_stRemoteAddr));
if (iSent > 0)
{
iBest = iSent;
}
}
/* 2. 发现的活 peer 扇出 */
for (i = 0; i < RD_UDP_PEER_MAX; i++)
{
TRdUdpPeer *pt = &ptUd->m_astPeers[i];
struct sockaddr_in stTo;
int iSent;
if (pt->m_uiIp == 0 || pt->m_uiDataPort == 0) continue; /* 空位/无数据口 */
if ((uint32_t)(uiNow - pt->m_uiLastSeen) > RD_UDP_PEER_TIMEOUT_MS) continue;
/* 【去重】固定对端同时被 beacon 发现时 (ip+data_port 相同), 若不再跳过
* 会对同一地址双发每个报文 —— ymodem 协议字节双发直接错位/损坏文件.
* 字节序: m_uiIp 网络序直存, data_port 为入表 ntohs 后的主机序 */
if (ptUd->m_iFixedValid &&
pt->m_uiIp == ptUd->m_stRemoteAddr.sin_addr.s_addr &&
pt->m_uiDataPort == ntohs(ptUd->m_stRemoteAddr.sin_port))
{
continue; /* peer 即固定对端, 第 1 步固定发送已覆盖 */
}
RD_MEMSET(&stTo, 0, sizeof(stTo));
stTo.sin_family = AF_INET;
stTo.sin_port = htons(pt->m_uiDataPort);
stTo.sin_addr.s_addr = pt->m_uiIp;
iSent = (int)sendto(ptUd->m_iTxSocket, _pBuffer, _iSize, 0,
(struct sockaddr *)&stTo, sizeof(stTo));
if (iSent > 0 && iBest < 0)
{
iBest = iSent;
}
}
if (iBest < 0)
{
log_e("udp_send_impl: sendto all failed (fixed=%d), err=%d",
ptUd->m_iFixedValid, RD_UDP_SOCK_ERR());
return RD_FAILURE;
}
return iBest;
}
/* ═══════════════════════════════════════════════════════════════════════ *
* 接收线程 (非阻塞 recvfrom + 让步轮询) *
* ═══════════════════════════════════════════════════════════════════════ */
#if defined(BUILD_PLATFORM_WIN32)
static DWORD WINAPI udp_recv_thread(LPVOID _lpParam)
#else
static void *udp_recv_thread(void *_pArg)
#endif
{
#if defined(BUILD_PLATFORM_WIN32)
TComCtrl *ptComCtrl = (TComCtrl *)_lpParam;
#else
TComCtrl *ptComCtrl = (TComCtrl *)_pArg;
#endif
TUdpUserData *ptUd = (TUdpUserData *)(ptComCtrl->m_pUserData);
uint8_t ucBuf[RD_UDP_RECV_BUF_SIZE];
while (ptUd->m_iRunning)
{
struct sockaddr_in stFrom;
socklen_t iFromLen = sizeof(stFrom);
int iRd = (int)recvfrom(ptUd->m_iRxSocket, (char *)ucBuf,
sizeof(ucBuf), 0,
(struct sockaddr *)&stFrom, &iFromLen);
if (iRd > 0)
{
rd_ComRecvProc(ptComCtrl, (const char *)ucBuf, (uint32_t)iRd);
}
else if (iRd < 0)
{
int iErr = RD_UDP_SOCK_ERR();
if (RD_UDP_ERR_YIELD(iErr))
{
rd_udp_yield();
}
else
{
log_e("udp_recv_thread: recvfrom failed, err=%d", iErr);
break;
}
}
else
{
rd_udp_yield();
}
}
return 0;
}
/* ═══════════════════════════════════════════════════════════════════════ *
* 发现线程 (非阻塞收 beacon + 周期广播 + 超时剔除) *
* ═══════════════════════════════════════════════════════════════════════ */
#if defined(BUILD_PLATFORM_WIN32)
static DWORD WINAPI udp_disc_thread(LPVOID _lpParam)
#else
static void *udp_disc_thread(void *_pArg)
#endif
{
#if defined(BUILD_PLATFORM_WIN32)
TComCtrl *ptComCtrl = (TComCtrl *)_lpParam;
#else
TComCtrl *ptComCtrl = (TComCtrl *)_pArg;
#endif
TUdpUserData *ptUd = (TUdpUserData *)(ptComCtrl->m_pUserData);
uint8_t ucBuf[RD_UDP_BEACON_SIZE + 16]; /* 容忍 beacon 带少量尾部冗余 */
uint32_t uiLastBeacon = 0; /* 0 → 首轮立即广播, 尽快入对端表 */
/* 发现是增强通道: 收发失败只降级, 不退出 (m_iRunning 为唯一退出条件) */
while (ptUd->m_iRunning)
{
struct sockaddr_in stFrom;
socklen_t iFromLen = sizeof(stFrom);
uint32_t uiNow;
int iRd = (int)recvfrom(ptUd->m_iDiscSocket, (char *)ucBuf,
sizeof(ucBuf), 0,
(struct sockaddr *)&stFrom, &iFromLen);
if (iRd > 0)
{
rd_udp_beacon_on_rx(ptUd, ucBuf, (uint32_t)iRd, &stFrom);
}
uiNow = Rd_GetTime();
/* 周期广播 (uint32 回绕安全减法) */
if ((uint32_t)(uiNow - uiLastBeacon) >= RD_UDP_BEACON_INTERVAL_MS)
{
uiLastBeacon = uiNow;
rd_udp_beacon_send(ptUd);
}
rd_udp_peer_sweep(ptUd, uiNow);
rd_udp_yield();
}
return 0;
}
#if defined(USE_LWIP)
/* robot 适配: FreeRTOS 任务不允许从入口函数返回 (返回=未定义行为/断言),
* 蹦床包一层 "函数返回即 vTaskDelete(NULL) 自杀"; 真正逻辑仍在原线程函数 */
static void udp_recv_task_entry(void *_pArg)
{
udp_recv_thread(_pArg);
vTaskDelete(NULL);
}
static void udp_disc_task_entry(void *_pArg)
{
udp_disc_thread(_pArg);
vTaskDelete(NULL);
}
#endif /* USE_LWIP */
/* ═══════════════════════════════════════════════════════════════════════ *
* Rd_UdpCreate — 统一入口 *
* ═══════════════════════════════════════════════════════════════════════ */
/* 平台 socket 初始化 (Win32: WSAStartup; 其余平台空) */
static int rd_udp_sock_init(void)
{
#if defined(BUILD_PLATFORM_WIN32)
static int s_iInitDone = 0;
WSADATA stWsa;
if (!s_iInitDone)
{
if (WSAStartup(MAKEWORD(2, 2), &stWsa) != 0) return RD_FAILURE;
s_iInitDone = 1;
}
#endif
return RD_SUCCESS;
}
WEAK TComCtrl *Rd_UdpCreate(const TRdUdpCfg *_ptCfg)
{
if (_ptCfg == NULL)
{
log_e("Rd_UdpCreate: invalid config");
return NULL;
}
/* m_psRemoteIp 允许 NULL/"": 纯发现模式 (对端经 beacon 自动获知,
* m_iRemotePort 忽略可 0); 固定直连时远端端口才必填 */
if (_ptCfg->m_psRemoteIp != NULL && _ptCfg->m_psRemoteIp[0] != '\0' &&
(_ptCfg->m_iRemotePort <= 0 || _ptCfg->m_iRemotePort > 65535))
{
log_e("Rd_UdpCreate: invalid remote port %d (fixed-ip mode)",
_ptCfg->m_iRemotePort);
return NULL;
}
if (_ptCfg->m_iLocalPort < 0 || _ptCfg->m_iLocalPort > 65535 ||
_ptCfg->m_iTxSrcPort < 0 || _ptCfg->m_iTxSrcPort > 65535)
{
log_e("Rd_UdpCreate: invalid port config");
return NULL;
}
if (rd_udp_sock_init() != RD_SUCCESS)
{
log_e("Rd_UdpCreate: socket subsystem init failed");
return NULL;
}
/* 1. 分配 UserData */
TUdpUserData *ptUd = (TUdpUserData *)RD_CALLOC(1, sizeof(TUdpUserData));
if (ptUd == NULL) return NULL;
ptUd->m_iRxSocket = -1;
ptUd->m_iTxSocket = -1;
ptUd->m_iDiscSocket = -1;
/* 1.5 发现参数: 实例标识 / beacon 名 / 本端数据端口 / 广播目的 */
ptUd->m_uiDataPort = (uint16_t)(_ptCfg->m_iLocalPort > 0 ? _ptCfg->m_iLocalPort : 0);
ptUd->m_uiInstanceId = rd_udp_make_instance_id(ptUd, ptUd->m_uiDataPort);
if (_ptCfg->m_psName != NULL && _ptCfg->m_psName[0] != '\0')
{
uint32_t uiLen = (uint32_t)RD_STRLEN(_ptCfg->m_psName);
if (uiLen > RD_UDP_NAME_MAX) uiLen = RD_UDP_NAME_MAX;
RD_MEMCPY(ptUd->m_sName, _ptCfg->m_psName, uiLen); /* 目标已清零保 NUL */
}
RD_MEMSET(&ptUd->m_stDiscAddr, 0, sizeof(ptUd->m_stDiscAddr));
ptUd->m_stDiscAddr.sin_family = AF_INET;
ptUd->m_stDiscAddr.sin_port = htons(RD_UDP_DISCOVERY_PORT);
ptUd->m_stDiscAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
/* 2. 固定对端地址 (m_psRemoteIp NULL/"" = 纯发现模式, 不设固定直连) */
if (_ptCfg->m_psRemoteIp != NULL && _ptCfg->m_psRemoteIp[0] != '\0')
{
if (rd_udp_fill_remote(&ptUd->m_stRemoteAddr,
_ptCfg->m_psRemoteIp, _ptCfg->m_iRemotePort) != RD_SUCCESS)
{
log_e("Rd_UdpCreate: bad remote ip '%s'", _ptCfg->m_psRemoteIp);
RD_FREE(ptUd);
return NULL;
}
ptUd->m_iFixedValid = 1;
}
/* 3. 接收 socket: bind 本地端口 (0 = 只发不收, 不创建) */
if (_ptCfg->m_iLocalPort > 0)
{
struct sockaddr_in stLocal;
int iSock = (int)socket(AF_INET, SOCK_DGRAM, 0);
if (iSock < 0)
{
log_e("Rd_UdpCreate: rx socket failed, err=%d", RD_UDP_SOCK_ERR());
RD_FREE(ptUd);
return NULL;
}
RD_MEMSET(&stLocal, 0, sizeof(stLocal));
stLocal.sin_family = AF_INET;
stLocal.sin_port = htons((uint16_t)_ptCfg->m_iLocalPort);
stLocal.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(iSock, (struct sockaddr *)&stLocal, sizeof(stLocal)) < 0)
{
log_e("Rd_UdpCreate: bind(%d) failed, err=%d",
_ptCfg->m_iLocalPort, RD_UDP_SOCK_ERR());
RD_UDP_CLOSE(iSock);
RD_FREE(ptUd);
return NULL;
}
if (rd_udp_set_nonblock(iSock) != RD_SUCCESS)
{
log_e("Rd_UdpCreate: set nonblock failed");
RD_UDP_CLOSE(iSock);
RD_FREE(ptUd);
return NULL;
}
ptUd->m_iRxSocket = iSock;
}
/* 4. 发送 socket: 指定源端口且与接收端口不同时单独创建 (对齐固件
* 7006/8006 双 PCB 语义), 否则复用接收 socket (本地端口 0 时新建
* 匿名绑定 socket 专用于发送) */
if (_ptCfg->m_iTxSrcPort > 0 && _ptCfg->m_iTxSrcPort != _ptCfg->m_iLocalPort)
{
struct sockaddr_in stSrc;
int iSock = (int)socket(AF_INET, SOCK_DGRAM, 0);
if (iSock < 0)
{
log_e("Rd_UdpCreate: tx socket failed, err=%d", RD_UDP_SOCK_ERR());
if (ptUd->m_iRxSocket >= 0) RD_UDP_CLOSE(ptUd->m_iRxSocket);
RD_FREE(ptUd);
return NULL;
}
RD_MEMSET(&stSrc, 0, sizeof(stSrc));
stSrc.sin_family = AF_INET;
stSrc.sin_port = htons((uint16_t)_ptCfg->m_iTxSrcPort);
stSrc.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(iSock, (struct sockaddr *)&stSrc, sizeof(stSrc)) < 0)
{
log_e("Rd_UdpCreate: bind tx src(%d) failed, err=%d",
_ptCfg->m_iTxSrcPort, RD_UDP_SOCK_ERR());
RD_UDP_CLOSE(iSock);
if (ptUd->m_iRxSocket >= 0) RD_UDP_CLOSE(ptUd->m_iRxSocket);
RD_FREE(ptUd);
return NULL;
}
ptUd->m_iTxSocket = iSock;
}
else
{
ptUd->m_iTxSocket = ptUd->m_iRxSocket;
if (ptUd->m_iTxSocket < 0)
{
/* 只发不收且未指定源端口: 新建匿名 socket */
ptUd->m_iTxSocket = (int)socket(AF_INET, SOCK_DGRAM, 0);
if (ptUd->m_iTxSocket < 0)
{
log_e("Rd_UdpCreate: tx-only socket failed, err=%d", RD_UDP_SOCK_ERR());
RD_FREE(ptUd);
return NULL;
}
}
}
/* 5. tx socket 开广播 (beacon 发 255.255.255.255; 三平台同名宏同义,
* 失败不致命: 固定直连仍可用, beacon 首次发送失败再警告) */
{
int iOn = 1;
if (setsockopt(ptUd->m_iTxSocket, SOL_SOCKET, SO_BROADCAST,
(const char *)&iOn, sizeof(iOn)) < 0)
{
log_w("Rd_UdpCreate: SO_BROADCAST failed, err=%d", RD_UDP_SOCK_ERR());
}
}
/* 6. 发现 socket: SO_REUSEADDR + bind 发现端口 + 非阻塞
* (SO_REUSEADDR: 同机多实例同端口共存, 广播各收一份;
* 绑定失败仅降级关闭发现, 链路创建继续) */
{
int iOn = 1;
int iSock = (int)socket(AF_INET, SOCK_DGRAM, 0);
if (iSock < 0)
{
log_w("Rd_UdpCreate: disc socket failed, err=%d (discovery off)",
RD_UDP_SOCK_ERR());
}
else
{
struct sockaddr_in stLocal;
setsockopt(iSock, SOL_SOCKET, SO_REUSEADDR,
(const char *)&iOn, sizeof(iOn));
RD_MEMSET(&stLocal, 0, sizeof(stLocal));
stLocal.sin_family = AF_INET;
stLocal.sin_port = htons(RD_UDP_DISCOVERY_PORT);
stLocal.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(iSock, (struct sockaddr *)&stLocal, sizeof(stLocal)) < 0)
{
log_w("Rd_UdpCreate: bind disc(%d) failed, err=%d (discovery off)",
RD_UDP_DISCOVERY_PORT, RD_UDP_SOCK_ERR());
RD_UDP_CLOSE(iSock);
}
else if (rd_udp_set_nonblock(iSock) != RD_SUCCESS)
{
log_w("Rd_UdpCreate: disc nonblock failed (discovery off)");
RD_UDP_CLOSE(iSock);
}
else
{
ptUd->m_iDiscSocket = iSock;
}
}
}
/* 7. 创建 TComCtrl (m_pfSend = udp_send_impl) */
uint32_t uiBufSize = _ptCfg->m_uiBufSize > 0 ? _ptCfg->m_uiBufSize : 4096;
TComCtrl *ptComCtrl = rd_ComCreate(_ptCfg->m_pfCheck, _ptCfg->m_pfDecode,
udp_send_impl, (int)uiBufSize, ptUd);
if (ptComCtrl == NULL)
{
log_e("Rd_UdpCreate: rd_ComCreate failed");
if (ptUd->m_iDiscSocket >= 0) RD_UDP_CLOSE(ptUd->m_iDiscSocket);
if (ptUd->m_iRxSocket >= 0 && ptUd->m_iTxSocket != ptUd->m_iRxSocket)
RD_UDP_CLOSE(ptUd->m_iTxSocket);
if (ptUd->m_iRxSocket >= 0) RD_UDP_CLOSE(ptUd->m_iRxSocket);
RD_FREE(ptUd);
return NULL;
}
/* 8. 记录全局引用 (供发送回调使用) */
g_ptUdpComCtrl = ptComCtrl;
/* 9. 启动接收线程 (local_port = 0 只发不收, 无接收线程) */
ptUd->m_iRunning = 1; /* 接收/发现线程共用运行标志 */
if (ptUd->m_iRxSocket >= 0)
{
/* robot 适配: 固件既有 CMSIS-RTOS2 直接创建 (栈字节/优先级语义与
* 主仓库 FreeRTOS 封装默认值一致: 4KB / osPriorityNormal) */
const osThreadAttr_t attr = { .name = "udp_recv", .stack_size = 4 * 1024,
.priority = (osPriority_t)osPriorityNormal };
if (osThreadNew(udp_recv_task_entry, ptComCtrl, &attr) == NULL)
{
log_e("Rd_UdpCreate: osThreadNew failed");
ptUd->m_iRunning = 0;
g_ptUdpComCtrl = NULL;
rd_ComDelete(ptComCtrl);
if (ptUd->m_iDiscSocket >= 0) RD_UDP_CLOSE(ptUd->m_iDiscSocket);
if (ptUd->m_iRxSocket >= 0 && ptUd->m_iTxSocket != ptUd->m_iRxSocket)
RD_UDP_CLOSE(ptUd->m_iTxSocket);
RD_UDP_CLOSE(ptUd->m_iRxSocket);
RD_FREE(ptUd);
return NULL;
}
}
/* 10. 启动发现线程 (发现 socket 就绪才有; 增强功能, 失败只关发现) */
if (ptUd->m_iDiscSocket >= 0)
{
const osThreadAttr_t attr = { .name = "udp_disc", .stack_size = 4 * 1024,
.priority = (osPriority_t)osPriorityNormal };
if (osThreadNew(udp_disc_task_entry, ptComCtrl, &attr) == NULL)
{
log_w("Rd_UdpCreate: disc thread failed (discovery off)");
RD_UDP_CLOSE(ptUd->m_iDiscSocket);
ptUd->m_iDiscSocket = -1;
}
}
if (ptUd->m_iFixedValid)
{
log_i("Rd_UdpCreate: local=%d txsrc=%d -> %s:%d (discovery %s)",
_ptCfg->m_iLocalPort, _ptCfg->m_iTxSrcPort,
_ptCfg->m_psRemoteIp, _ptCfg->m_iRemotePort,
ptUd->m_iDiscSocket >= 0 ? "on" : "off");
}
else
{
log_i("Rd_UdpCreate: local=%d txsrc=%d, pure discovery mode (disc %s)",
_ptCfg->m_iLocalPort, _ptCfg->m_iTxSrcPort,
ptUd->m_iDiscSocket >= 0 ? "on" : "off");
}
return ptComCtrl;
}
/* ═══════════════════════════════════════════════════════════════════════ *
* Rd_UdpClose — 统一销毁 *
* ═══════════════════════════════════════════════════════════════════════ */
WEAK void Rd_UdpClose(TComCtrl *_ptComCtrl)
{
if (_ptComCtrl == NULL) return;
TUdpUserData *ptUd = (TUdpUserData *)(_ptComCtrl->m_pUserData);
if (ptUd != NULL)
{
/* 1. 通知接收/发现线程停止 (detached 线程无法 join, 发信号后 sleep;
* 两个线程均为 1ms 级轮询, 多让步几拍确保脱离共享数据) */
ptUd->m_iRunning = 0;
rd_udp_yield();
rd_udp_yield();
rd_udp_yield();
rd_udp_yield();
/* 2. 关闭 socket (唤醒轮询中的 recvfrom 并使其退出) */
if (ptUd->m_iDiscSocket >= 0)
{
RD_UDP_CLOSE(ptUd->m_iDiscSocket);
ptUd->m_iDiscSocket = -1;
}
if (ptUd->m_iTxSocket >= 0 && ptUd->m_iTxSocket != ptUd->m_iRxSocket)
{
RD_UDP_CLOSE(ptUd->m_iTxSocket);
ptUd->m_iTxSocket = -1;
}
if (ptUd->m_iRxSocket >= 0)
{
RD_UDP_CLOSE(ptUd->m_iRxSocket);
ptUd->m_iRxSocket = -1;
}
RD_FREE(ptUd);
}
/* 3. 销毁 TComCtrl */
rd_ComDelete(_ptComCtrl);
if (g_ptUdpComCtrl == _ptComCtrl)
g_ptUdpComCtrl = NULL;
log_i("Rd_UdpClose: done");
}
#else
TComCtrl *Rd_UdpCreate(const TRdUdpCfg *_ptCfg)
{
return NULL;
}
void Rd_UdpClose(TComCtrl *_ptComCtrl)
{
return;
}
#endif /* RD_UDP_NO_PLATFORM */

102
peripheral/include/rd_udp.h

@ -0,0 +1,102 @@
/******************************************************************************
版权所有 (C), 2018-2099, Radkil
******************************************************************************
文 件 名 : rd_udp.h
版 本 号 : 初稿
作 者 : Radkil
生成日期 : 2026年11月3日
最近修改 :
功能描述 : 跨平台 UDP 传输抽象层公共头文件 (TComCtrl 集成)
与 rd_serial 同构: 创建 TComCtrl + 内部接收线程,
平台差异在 peripheral/rd_udp.c 内通过宏处理:
USE_LWIP → lwip/sockets.h (STM32 固件, 需 tcpip 栈已启动)
BUILD_PLATFORM_LINUX → POSIX socket
BUILD_PLATFORM_WIN32 → winsock2
修改历史 :
1.日 期 : 2026年11月3日
作 者 : Radkil
修改内容 : 创建文件
******************************************************************************/
#ifndef __RD_UDP_H__
#define __RD_UDP_H__
#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus */
/*==============================================*
* include header files *
*----------------------------------------------*/
#include <stdint.h>
#include "common.h"
#include "com.h"
/*==============================================*
* constants or macros define *
*----------------------------------------------*/
#define RD_UDP_IP_MAX 16 /* 点分十进制 IP 最大长度("255.255.255.255"+0) */
/* ── UDP 广播设备发现 (beacon) 参数 ──
* 机制: 各实例按 RD_UDP_BEACON_INTERVAL_MS 周期向
* 255.255.255.255:RD_UDP_DISCOVERY_PORT 广播 22B beacon,
* 收到方校验 magic 后记录 (源IP, data_port) 进 peer 表,
* 数据帧扇出给所有 RD_UDP_PEER_TIMEOUT_MS 内活跃的 peer */
#define RD_UDP_DISCOVERY_PORT 9005 /* 发现通道端口 (beacon 收发, 两端一致) */
#define RD_UDP_PEER_MAX 8 /* peer 表容量 (满时覆盖最旧) */
#define RD_UDP_PEER_TIMEOUT_MS 5000 /* peer 存活超时 (beacon 周期整数倍, 防误剔) */
#define RD_UDP_BEACON_INTERVAL_MS 1000 /* beacon 广播周期 */
/* UDP 链路配置 */
typedef struct
{
const char *m_psRemoteIp; /* 对端 IP (发送目的), 点分十进制;
* NULL/"" = 纯发现模式 (数据帧扇出给 beacon 发现的活 peer) */
int m_iRemotePort; /* 对端端口 (仅固定 IP 模式使用; 纯发现模式忽略, 可 0) */
int m_iLocalPort; /* 本地绑定端口 (接收), 0 = 只发不收 (beacon data_port 同源) */
int m_iTxSrcPort; /* 发送源端口 (对齐固件 7006 双 PCB 语义),
* 0 = 复用接收 socket (源端口 = 本地端口) */
uint32_t m_uiBufSize; /* TComCtrl 环形缓冲区大小 */
const char *m_psName; /* 设备名 (beacon 携带, 对端日志可见), NULL = 不填 */
cbk_ComCheck m_pfCheck;
cbk_ComDecode m_pfDecode;
} TRdUdpCfg;
/*==============================================*
* routines' or functions' implementations *
*----------------------------------------------*/
/**
* @brief 创建 UDP 链路 TComCtrl (内部启动接收线程 + beacon 发现线程)
* @param _ptCfg UDP 配置 (不可为空; m_psRemoteIp 可为 NULL/"")
* @return TComCtrl* 成功, NULL 失败
* @note m_psRemoteIp 为 NULL/"" 时进入纯发现模式: 不再要求配置对端,
* 数据帧自动扇出给 beacon 发现的活 peer (rd_udp.h 宏可调);
* 非空时固定直连与发现并存 (扇出 = 固定对端 + 全部活 peer)。
* 发现 socket 绑定失败仅降级关闭发现 (log_w), 链路创建不受影响。
* USE_LWIP 平台需先完成 tcpip 栈/netif 初始化再调用
* (SO_REUSEADDR 需 LWIP_SO_REUSE);
* 发送回调经模块级静态上下文解析, 每进程仅支持一条 UDP 链路
* (与 rd_serial 的单实例约束同模式, 不改 com.c)
*/
TComCtrl *Rd_UdpCreate(const TRdUdpCfg *_ptCfg);
/**
* @brief 关闭并销毁 UDP 链路 TComCtrl
* @param _ptComCtrl TComCtrl 控制块
*/
void Rd_UdpClose(TComCtrl *_ptComCtrl);
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
#endif /* __RD_UDP_H__ */
Loading…
Cancel
Save