P2P 远程访问与设备互联
本文档介绍 AC79NN AIoT SDK 中实现 P2P 远程访问与设备互联能力的应用层网络架构:以 wifi_app_task 网络任务为核心,通过 lwIP socket、流媒体服务器(Fenice)、网络服务器(net_server)与配网/静态 IP 机制,支撑设备间点对点互联与远程访问。
Purpose and Scope
本页覆盖以下内容:
- 网络应用任务模型:
wifi_app_task.c中的wifi_app_hdl状态标志与net_app_init/net_app_uninit生命周期管理; - 局域网互联基础:SoftAP 热点配置(
192.168.0.1)、静态 IP 恢复(VM_STA_IPADDR_INDEX)、net_set_lan_info_ext下发的 LAN 信息; - Socket 通信层:基于 lwIP 的 UDP/TCP 收发(
sock_recvfrom、sock_api),这是设备发现与 P2P 数据通道的基础; - 远程服务面:
server_core、net_server与streaming_media_server(Fenice)提供的媒体流/命令服务入口; - 网络使能与测试模式:
CONFIG_NET_ENABLE等编译开关与 RF/STA/AP 测试模式。
边界说明:仓库源码中未发现独立的第三方 P2P SDK(如 TUTK)或专门的 p2p_*.c 模块;本 SDK 的"P2P 远程访问"由应用层自研 socket + 流媒体服务 + 云/局域网服务器组合实现。配网流程(Airkiss、SmartConfig)、BT 低功耗、音乐业务等属于其他页面范畴,本页仅在与网络互联相关的边界处引用。
Overview
AC79NN 是杰理(Jieli)面向 AIoT 场景的 WiFi SoC SDK。在 apps/wifi_story_machine(WiFi 故事机)与 apps/demo/demo_audio 等应用中,网络能力统一收敛到一个网络应用任务(wifi_app_task)中。该任务负责:
- 接入网络:以 STA 模式连接路由器(或 SoftAP 模式供手机配网),超时窗口为
CONNECT_TIMEOUT_SEC 60秒; - 配置网络栈:通过
net_set_lan_info_ext下发 IP/网关/掩码,静态 IP 场景从syscfg(VM_STA_IPADDR_INDEX)恢复上次保存的地址; - 开放服务端口:启动 UDP 监听(用于设备发现/透传测试),并集成
net_server/streaming_media_server(Fenice)提供远程访问入口; - 维持互联状态:以位域标志(
net_app_init_flag、request_connect_flag等)管理初始化与重连状态机,防止重复初始化。
P2P 语义在本 SDK 中体现为"设备主动暴露服务端(socket + 流媒体),对端(App/另一台设备)通过局域网或公网直连/中转访问",而非专门的打洞协议实现。
Architecture
flowchart TD
subgraph sg_App["应用层 apps/wifi_story_machine"]
WAT["wifi_app_task.c<br/>网络应用任务"]
HDL["wifi_app_hdl<br/>位域状态标志"]
LAN["soft_ap_set_lan_setting_info<br/>wifi_set_lan_setting_info"]
UDP["UDP 监听<br/>sock_recvfrom"]
end
subgraph sg_Srv["远程服务面"]
NS["net_server<br/>server/net_server.h"]
SC["server_core<br/>server/server_core.h"]
FEN["streaming_media_server<br/>fenice_config.h"]
end
subgraph sg_Stack["网络协议栈"]
LWIP["lwIP<br/>sockets / netdb / dns / ip"]
SOCK["sock_api"]
end
subgraph sg_Cfg["配置与持久化"]
SYSCFG["syscfg<br/>VM_STA_IPADDR_INDEX"]
EVT["net_event"]
JSON["json_tokener<br/>协议消息"]
end
WAT --> HDL
WAT --> LAN
WAT --> UDP
WAT -->|"初始化/使能"| NS
NS --> SC
NS --> FEN
UDP --> LWIP
NS --> SOCK
SOCK --> LWIP
LAN -->|"net_set_lan_info_ext"| LWIP
WAT -->|"syscfg_read"| SYSCFG
NS -->|"事件通知"| EVT
NS -->|"JSON 编解码"| JSON
架构说明:
wifi_app_task是唯一入口:wifi_on()后调用net_app_init()完成网络栈初始化,退出时调用net_app_uninit()释放资源;所有互联行为都挂接在这个任务上。- 局域网配置是互联的前提:SoftAP 模式固定为
192.168.0.1网段,保证手机/App 可稳定发现设备;STA 静态 IP 从syscfg恢复,使设备在路由器下的地址可预期,便于对端直连。 - socket 层承担"互联通道"角色:UDP 用于发现与透传,TCP(经
sock_api)用于命令/流媒体;lwip提供协议栈。 - 远程服务面把设备变成"服务器":
net_server+streaming_media_server(Fenice)让 App 可远程拉取媒体流/下发命令,这正是"设备互联/远程访问"的产品化形态。 - 协议与事件解耦:JSON(
json_tokener)做消息编解码,net_event向应用层广播网络事件,syscfg做持久化。
网络应用任务模型
wifi_app_task.c 顶部定义了一个全局单例句柄 wifi_app_hdl,所有网络状态都压缩在位域标志中。这种设计在资源受限的 SoC 上非常典型:用一个 u32 承载全部状态位,避免为每个标志单独占一个变量,同时让整个状态集合可被原子地读取/清空。
static struct {
int udp_recv_pid;
u32 use_static_ipaddr_flag : 1;
u32 net_app_init_flag : 1;
u32 request_connect_flag : 1;
u32 save_ssid_flag : 1;
u32 mac_addr_succ_flag : 1;
u32 udp_recv_test_exit_flag : 1;
u32 rf_test_mode : 3;
u32 reserved : 23;
} wifi_app_hdl;
#define __this (&wifi_app_hdl)
Source: wifi_app_task.c
各标志位的职责:
| 标志位 | 宽度 | 含义 |
|---|---|---|
use_static_ipaddr_flag | 1 bit | 是否使用静态 IP(对应 CONFIG_STATIC_IPADDR_ENABLE) |
net_app_init_flag | 1 bit | 网络应用是否已初始化,防止重复初始化 |
request_connect_flag | 1 bit | 是否有连接请求待处理 |
save_ssid_flag | 1 bit | 是否保存过 SSID |
mac_addr_succ_flag | 1 bit | MAC 地址获取是否成功(关联 CONFIG_ASSIGN_MACADDR_ENABLE) |
udp_recv_test_exit_flag | 1 bit | UDP 接收测试是否退出 |
rf_test_mode | 3 bits | RF 测试模式(见下方枚举) |
rf_test_mode 的取值来自文件顶部的测试模式枚举,覆盖生产测试与认证场景:
enum {
RF_TEST_CLOSE = 0,
WIFI_MP_TEST_MODE,
WIFI_STA_TEST_MODE,
WIFI_AP_TEST_MODE,
BT_DUT_TEST_MODE,
BT_FCC_TEST_MODE,
BT_BQB_TEST_MODE,
RF_MULTI_TEST_MODE,
};
Source: wifi_app_task.c
生命周期:net_app_init / net_app_uninit
任务启动与退出遵循严格的"初始化一次、退出一次"模式。net_app_init 用标志位做幂等保护——即使 wifi_on 被多次调用或事件重复触发,也不会重复初始化网络栈:
static void net_app_init(void)
{
if (__this->net_app_init_flag) {
return;
}
__this->net_app_init_flag = 1;
}
Source: wifi_app_task.c
对应的启动/停止序列(Grep 证据):
wifi_on();
net_app_init();
// ...
net_app_uninit();
Source: wifi_app_task.c
设计意图:wifi_on() 负责底层射频与协议栈上电,net_app_init() 负责应用层(socket 服务、流媒体服务、事件注册)初始化。两者分离使得网络底层重启(如断线重连)不需要重建应用层服务,而应用层重建(如进入测试模式)也不会触碰射频状态。
局域网互联配置
SoftAP 模式:固定网段保证可发现性
设备以 SoftAP 形态出现时(配网/直连场景),LAN 信息被硬编码为 192.168.0.1 网段。固定网段是 P2P 互联的关键设计:对端(手机 App)连上该热点后无需 DHCP 协商即可使用既定地址访问设备,避免了 IP 漂移导致的发现失败:
static void soft_ap_set_lan_setting_info(void)
{
struct lan_setting lan_setting_info = {
.WIRELESS_IP_ADDR0 = 192,
.WIRELESS_IP_ADDR1 = 168,
.WIRELESS_IP_ADDR2 = 0,
.WIRELESS_IP_ADDR3 = 1,
.WIRELESS_NETMASK0 = 255, .WIRELESS_NETMASK1 = 255,
.WIRELESS_NETMASK2 = 255, .WIRELESS_NETMASK3 = 0,
.WIRELESS_GATEWAY0 = 192, .WIRELESS_GATEWAY1 = 168,
.WIRELESS_GATEWAY2 = 0, .WIRELESS_GATEWAY3 = 1,
.SERVER_IPADDR1 = 192, .SERVER_IPADDR2 = 168,
.SERVER_IPADDR3 = 0, .SERVER_IPADDR4 = 1,
.CLIENT_IPADDR1 = 192, .CLIENT_IPADDR2 = 168,
.CLIENT_IPADDR3 = 0, .CLIENT_IPADDR4 = 2,
.SUB_NET_MASK1 = 255, .SUB_NET_MASK2 = 255,
.SUB_NET_MASK3 = 255, .SUB_NET_MASK4 = 0,
};
net_set_lan_info_ext(&lan_setting_info, WIFI_NETIF);
}
Source: wifi_app_task.c
注意 SERVER_IPADDR 与 CLIENT_IPADDR 的区分:设备自身固定为 .0.1(服务器角色),对端设备预留 .0.2(客户端角色)——这直接对应"设备互联"中主从设备的地址规划。
STA 静态 IP:从 syscfg 恢复上次配置
当 CONFIG_STATIC_IPADDR_ENABLE 使能时,设备以 STA 模式接入路由器后使用上次保存的静态地址。sta_ip_info 结构体完整保存了 SSID、IP、网关、掩码、DNS、网关 MAC、本地 MAC 与信道,通过 syscfg_read(VM_STA_IPADDR_INDEX, ...) 从 VM 持久化区读出:
struct sta_ip_info {
char ssid[33];
u32 ip;
u32 gw;
u32 netmask;
u32 dns;
u8 gw_mac[6];
u8 local_mac[6];
u8 chanel;
};
static void wifi_set_lan_setting_info(void)
{
struct sta_ip_info sta_ip_info;
syscfg_read(VM_STA_IPADDR_INDEX, (char *) &sta_ip_info, sizeof(struct sta_ip_info));
struct lan_setting lan_setting_info = {
.WIRELESS_IP_ADDR0 = (u8)(sta_ip_info.ip >> 0),
.WIRELESS_IP_ADDR1 = (u8)(sta_ip_info.ip >> 8),
.WIRELESS_IP_ADDR2 = (u8)(sta_ip_info.ip >> 16),
.WIRELESS_IP_ADDR3 = (u8)(sta_ip_info.ip >> 24),
// ... netmask / gateway / dns 同理按字节拆分
};
}
Source: wifi_app_task.c
设计意图:
- 持久化 vs 运行时:网络参数存于
syscfg(VM 区),重启不掉电丢失,保证"上次可用配置"可直接恢复,缩短重连时间; - 字节序处理:
u32的 IP 按字节右移拆分填入lan_setting,与 SoC 小端序匹配,必须逐字节转换而非直接 memcpy; - 弱函数扩展点:
wifi_force_set_lan_setting_info被声明为__attribute__((weak)),默认返回 0——产品方可以在不改动 SDK 代码的前提下覆盖它,强制注入自定义 LAN 配置,这是本模块预留的扩展点:
int __attribute__((weak)) wifi_force_set_lan_setting_info(void)
{
return 0;
}
Source: wifi_app_task.c
Socket 通信层:设备互联的数据通道
设备的"互联"本质是 socket 通信。wifi_app_task.c 的依赖头文件揭示了完整通信栈:lwip/sockets.h(BSD socket API)、lwip/netdb.h(域名解析)、lwip/dns.h(DNS)、lwip/ip.h(IP 层),以及 sock_api/sock_api.h(SDK 封装的异步 socket 接口)。
在 apps/wifi_story_machine/wifi_app_task.c 中,UDP 服务采用典型的"绑定端口 + 循环接收"模式,用 remote_addr(struct sockaddr_in)记录对端地址——保存对端地址是 P2P 回复的前提,收到数据后可按 remote_addr 定向回包:
static u32 sock_rcv_cnt;
struct sockaddr_in remote_addr;
socklen_t len = sizeof(remote_addr);
recv_len = sock_recvfrom(socket_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&remote_addr, &len);
Source: wifi_app_task.c
该 UDP 通道的用途包括:
- 设备发现:App 广播探测包,设备在固定端口应答自身信息;
- 透传/调试:
udp_recv_pid字段表明接收逻辑运行在独立线程中,udp_recv_test_exit_flag控制测试模式的退出,避免接收线程泄漏; - 轻量命令:不需建立连接的命令走 UDP,降低开销。
为何使用 UDP 而非只依赖 TCP? 设备发现阶段双方 IP 未知,必须借助 UDP 广播/组播完成"寻址-应答",之后才切换到 TCP 建立可靠通道承载命令与媒体流。这是 P2P 互联的标准两步走策略。
远程服务面:让设备成为"服务器"
设备互联的另一半是服务端能力。头文件依赖表明设备集成了三层远程服务:
| 服务 | 头文件 | 角色 |
|---|---|---|
server_core | server/server_core.h | 服务框架核心:任务/事件循环、连接管理 |
net_server | server/net_server.h | 网络服务入口:TCP 监听、命令分发 |
| Fenice 流媒体服务器 | streaming_media_server/fenice_config.h | 媒体流服务(RTSP/HTTP),远程拉流 |
#include "server/server_core.h"
#include "server/net_server.h"
#include "streaming_media_server/fenice_config.h"
#include "event/net_event.h"
Source: wifi_app_task.c
Fenice 集成是"远程访问"落地的关键:设备本地播放/采集的音频流通过 Fenice 暴露为流媒体服务,App 端可像访问普通流媒体服务器一样远程拉流,实现"设备互联"中最常见的场景——远程听音/远程控制。
协议与事件
- JSON 协议:
json_c/json_tokener.h提供 JSON 解析,设备与对端(App/云)之间的命令消息以 JSON 承载,保证协议可扩展、可调试; - 事件通知:
event/net_event.h定义网络事件,net_server层通过事件机制把"连接建立/断开/数据到达"通知到应用任务,应用无需轮询; - 系统服务:
system/timer.h提供定时器(配合CONNECT_TIMEOUT_SEC做连接超时管理)。
Core Flow:从开机到远程访问
sequenceDiagram
participant App as 设备应用
participant WAT as wifi_app_task
participant LWIP as lwIP 协议栈
participant NS as net_server / Fenice
participant Peer as 对端(App/设备)
App->>WAT: wifi_on()
WAT->>WAT: 初始化 wifi_app_hdl 标志
WAT->>WAT: net_app_init()(幂等保护)
WAT->>LWIP: net_set_lan_info_ext<br/>(SoftAP 192.168.0.1 / 静态 IP)
WAT->>NS: 启动 UDP/TCP 服务<br/>+ Fenice 流媒体服务
NS-->>WAT: net_event 通知就绪
WAT-->>App: 网络就绪事件
Peer->>NS: UDP 发现请求
NS->>Peer: 应答设备信息(remote_addr 定向回包)
Peer->>NS: TCP 连接 / 命令请求
NS->>NS: 分发命令(JSON 解析)
Peer->>NS: RTSP/HTTP 拉流
NS-->>Peer: 媒体流
NS-->>WAT: 断开事件 -> 重连/清理
关键步骤解读:
- 上电:
wifi_on()使能射频与协议栈,随后net_app_init()完成应用层服务注册——标志位保证无论事件如何触发都不会重复初始化; - 地址配置:SoftAP 或静态 IP 场景通过
net_set_lan_info_ext注入 LAN 信息,设备获得稳定地址; - 服务就绪:UDP(发现)+ TCP(命令)+ Fenice(流媒体)全部挂起,设备成为"可被访问的服务器";
- 发现-连接:对端先用 UDP 发现设备(
remote_addr记录来源),再升级为 TCP 可靠通道; - 业务流转:命令走 JSON,媒体走流媒体协议,事件通过
net_event回灌应用层; - 退出/重连:断线触发事件,任务按
net_app_uninit清理或按需重连,CONNECT_TIMEOUT_SEC 60约束连接等待时间。
Configuration Options
以下配置项来自 wifi_app_task.c 源码与编译条件,均需在应用工程(如 app_config.h / 板级配置)中定义:
| 选项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
CONFIG_NET_ENABLE | 编译宏 | 未定义 | 网络功能总开关;wifi_app_task.c 全部代码包裹在其内 |
CONNECT_TIMEOUT_SEC | 宏常量 | 60 | 连接超时窗口(秒),控制 wifi_on 后等待网络的时长 |
CONFIG_STATIC_IPADDR_ENABLE | 编译宏 | 未定义 | 使能 STA 静态 IP 恢复(VM_STA_IPADDR_INDEX) |
CONFIG_ASSIGN_MACADDR_ENABLE | 编译宏 | 未定义 | 使能 MAC 地址分配(assign_macaddr.h),关联 mac_addr_succ_flag |
CONFIG_WIFIBOX_ENABLE | 编译宏 | 未定义 | 使能 Wi-Fi Box 客户端(client.h) |
VM_STA_IPADDR_INDEX | syscfg 索引 | 由 syscfg_id.h 定义 | 静态 IP 信息在 VM 区的存储索引 |
use_static_ipaddr_flag | 位域 | 0 | 运行时标志,指示使用静态 IP 配置路径 |
编译开关的叠加关系:CONFIG_NET_ENABLE 是总闸;CONFIG_STATIC_IPADDR_ENABLE 决定走 wifi_set_lan_setting_info(读 syscfg)还是 SoftAP 固定配置路径;CONFIG_WIFIBOX_ENABLE 额外引入 Wi-Fi Box 云对接能力(对应 client.h,属于公网远程访问的另一通道)。
Failure Modes、边界与并发
连接超时
CONNECT_TIMEOUT_SEC 60 定义了从发起连接到放弃的最长等待时间。在弱信号或错误 SSID 场景下,任务不会无限阻塞,超时后进入失败处理路径。设计意图:IoT 设备对功耗与响应延迟敏感,必须用硬超时兜底,避免任务挂死导致系统无响应。
重复初始化防护
net_app_init 的 net_app_init_flag 幂等检查直接防御了并发/重入问题:网络事件(如连接成功回调、重连定时器)可能在同一时刻被多次触发,若没有标志位保护,socket 会重复 bind、服务会重复注册,最终导致资源泄漏或 EADDRINUSE。同样地,net_app_uninit 与 net_app_init 配对调用,保证退出路径对称。
测试模式下的状态隔离
rf_test_mode(3 bits)承载 8 种测试模式(MP/STA/AP/BT DUT/FCC/BQB/Multi),切换模式时通过 net_app_uninit 释放网络应用资源,防止 RF 测试期间残留的网络任务干扰测试结果。
边界情况
- UDP 接收线程:
udp_recv_pid表明接收运行于独立任务;udp_recv_test_exit_flag用于优雅退出接收循环——若未正确处理,切换测试模式时可能残留阻塞中的sock_recvfrom; - 静态 IP 冲突:从
syscfg恢复的地址可能与路由器 DHCP 池冲突;sta_ip_info保存了gw_mac(网关 MAC),可用于连通性校验; - 字节序:
u32IP 必须逐字节拆分填入lan_setting,直接 memcpy 会导致地址错位。
Extension Points
| 扩展点 | 机制 | 用途 |
|---|---|---|
wifi_force_set_lan_setting_info | __attribute__((weak)) 弱函数 | 产品方强制注入自定义 LAN 配置,无需改动 SDK |
net_event | 事件订阅 | 应用层监听连接/断开/数据事件,挂接自定义业务 |
net_server 命令分发 | 服务注册 | 新增自定义 JSON 命令,扩展远程控制能力 |
| Fenice 配置 | fenice_config.h | 定制流媒体端口/协议,适配不同拉流端 |
sock_api | 封装 socket | 复用异步收发能力实现自定义 P2P 通道 |
Related Links
- net_server.h(网络服务入口) — 远程命令服务定义
- server_core.h(服务框架) — 服务任务与连接管理
- fenice_config.h(流媒体服务器) — 远程媒体流服务配置
- wifi_app_task.c(网络应用任务) — 本页核心实现文件
- demo_audio 版 wifi_app_task.c — 同名网络任务在 demo 应用中的变体
- 配网(Airkiss/SmartConfig)、BT 低功耗、音乐业务等详见对应章节页面