当前位置:网站首页>ESP32学习笔记(49)——ESP-WIFI-MESH接口使用
ESP32学习笔记(49)——ESP-WIFI-MESH接口使用
2022-06-11 06:44:00 【Leung_ManWah】
一、ESP-WIFI-MESH简介
1.1 概述
ESP-WIFI-MESH是建立在Wi-Fi协议之上的网络协议。ESP-WIFI-MESH允许分布在大范围物理区域内(室内和室外)的许多设备(以下称为节点)在同一个WLAN(无线局域网)下互连。ESP-WIFI-MESH是自组织和自修复的,这意味着网络可以自主构建和维护。
ESP-IDF 编程指南——ESP-WIFI-MESH
ESP-WIFI-MESH 编程指南
1.2 网络结构区别

传统Wi-Fi网络是“一点对多点”网络,其中称为接入点(AP)的单个中心节点直接连接到所有其他称为站点(Station)的节点。AP负责仲裁和转发站之间的传输。有些接入点还通过路由器中继与外部IP网络之间的传输。传统Wi-Fi网络的缺点是覆盖范围有限,因为要求每个站点都必须在范围内才能直接与AP连接。此外,传统Wi-Fi网络容易过载,因为网络中允许的最大站点数量受到AP容量的限制。

ESP-WIFI-MESH不同于传统Wi-Fi网络,因为节点不需要连接到中心节点。相反,允许节点与相邻节点连接。节点相互负责转发彼此的传输。ESP-WIFI-MESH网络的覆盖区域更广,因为节点仍然可以实现互连,而不需要在中心节点的范围内。同样,ESP-WIFI-MESH也不容易过载,因为网络上允许的节点数量不再受单个中心节点的限制。
1.3 术语
| 术语 | 描述 |
|---|---|
| 节点 | 任何 属于 或 可以成为 ESP-WIFI-MESH 网络一部分的设备 |
| 根节点 | 网络顶部的节点 |
| 子节点 | 如节点 X 连接至节点 Y,且 X 相较 Y 与根节点的距离更远(跨越的连接数量更多),则称 X 为 Y 的子节点。 |
| 父节点 | 子节点的相反概念 |
| 后代节点 | 任何可以从根节点追溯到的节点 |
| 兄弟节点 | 连接至同一个父节点的所有节点 |
| 连接 | AP 和 Station 之间的传统 Wi-Fi 关联。ESP-WIFI-MESH 中的节点使用 Station 接口与另一个节点的 SoftAP 接口产生关联,进而形成连接。连接包括 Wi-Fi 网络中的身份验证和关联过程。 |
| 上行连接 | 从节点到其父节点的连接 |
| 下行连接 | 从父节点到其一个子节点的连接 |
| 无线 hop | 源节点和目标节点间无线连接路径中的一部分。单跳指遍历单个连接的数据包,多跳指遍历多个连接的数据包。 |
| 子网 | 指 ESP-WIFI-MESH 网络的一部分,包括一个节点及其所有后代节点。因此,根节点的子网包括 ESP-WIFI-MESH 网络中的所有节点。 |
| MAC 地址 | 在 ESP-WIFI-MESH 网络中用于区别每个节点或路由器的唯一地址 |
| DS | 分布式系统(外部 IP 网络) |
1.4 树型拓扑
ESP-WIFI-MESH建立在基础设施Wi-Fi协议之上,可以被认为是一种将许多单独的Wi-Fi网络组合成一个WLAN的网络协议。在Wi-Fi中,Station在任何时候都只能与一个AP建立一个连接(上行连接),而一个AP可以同时连接到多个Station(下行连接)。然而,ESP-WIFI-MESH允许节点同时充当Station和AP。因此,ESP-WIFI-MESH中的节点可以使用其SoftAP接口建立多个下行连接,同时使用其Station接口建立一个上行连接。这将自然产生一个由多层父子结构组成的树型网络拓扑结构。

ESP-WIFI-MESH是多跳(multi-hop)网络,这意味着节点可以通过单跳或多跳向网络中的其他节点传输数据包。因此,ESP-WIFI-MESH中的节点不仅传输自己的数据包,还同时充当其他节点的中继。假设物理层上的任意两个节点之间存在路径(通过单跳或多跳),则ESP-WIFI-MESH网络内的任意一对节点都可以通信。
注意: ESP-WIFI-MESH网络中的大小(节点总数)取决于网络中允许的
最大层数,以及每个节点可以拥有的最大下游连接数。这两个变量都可以配置为限制网络的规模。
1.5 节点类型

根节点: 是指网络中的顶级节点,并且充当ESP-WIFI-MESH网络和外部IP网络之间的唯一接口。根节点连接到传统的Wi-Fi路由器,并将来往外部IP网络的数据包中继到ESP-WIFI-MESH网络内的节点。ESP-WIFI-MESH网络中只能有一个根节点,根节点的上行连接只能是路由器。如上图,节点A是ESP-WIFI-MESH网络的根节点。
叶节点: 是指不允许有任何子节点的节点(即没有下行连接)。因此,叶节点只能发送或接收自己的数据包,而不能转发其他节点的数据包。如果一个节点位于网络中的最大允许层,它将被指定为叶节点。这将防止节点形成任何下行连接,从而确保网络不会增加额外的层数。由于建立任何下行连接都需要SoftAP接口,一些没有SoftAP接口的节点(仅Station)也将被指定为叶节点。如上图,节点L/M/N位于网络最大允许层,因此被指定为叶节点。
中间父节点: 是指既不是根节点也不是叶节点的连接节点。中间父节点必须有一个上行连接(即一个父节点),但可以有零到多个下行连接(零到多个子节点)。因此,中间父节点可以发送和接收数据包,还可以转发从其上行和下行连接发送的数据包。如上图,节点B到J是中间父节点。**注意,没有下行连接的中间父节点(如节点E/F/G/I/J)不等同于叶节点,因为它们仍然被允许在将来形成下行连接。
空闲节点: 是指尚未加入网络的节点。空闲节点将尝试与中间父节点形成上行连接,或者在有条件的情况下尝试成为根节点(参见自动根节点选择)。如上图,节点K和O是空闲节点。
二、API说明
以下 Mesh 接口位于 components/esp_wifi/include/esp_mesh.h。
2.1 esp_mesh_init

2.2 esp_mesh_start

2.3 esp_mesh_send


2.4 esp_mesh_recv

2.5 esp_mesh_set_config

2.6 esp_mesh_set_max_layer

2.7 esp_mesh_get_layer

2.8 esp_mesh_is_root

2.9 esp_mesh_get_routing_table

2.10 esp_mesh_set_topology

三、编程流程
3.1 初始化LwIP和WIFI
ESP-WIFI-MESH软件栈构建在Wi-Fi驱动程序/FreeRTOS之上,在某些情况下可能会使用LwIP栈(即根节点)。下图说明了ESP-WIFI-MESH软件栈。

应用程序通过ESP-WIFI-MESH事件与ESP-WIFI-MESH交互。由于ESP-WIFI-MESH构建在Wi-Fi栈之上,因此应用程序也可以通过Wi-Fi事件任务与Wi-Fi驱动交互。下图说明了ESP-WIFI-MESH应用程序中各种系统事件的接口。

启动ESP-WIFI-MESH的先决条件是初始化LwIP和Wi-Fi。
void app_main(void)
{
/* 初始化NVS */
ESP_ERROR_CHECK(nvs_flash_init());
/* 初始化底层TCP/IP堆栈 */
ESP_ERROR_CHECK(esp_netif_init());
/* 初始化事件 */
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* 为Mesh创建STA和AP模式网络接口(仅保存STA接口以供进一步操作),并关闭DHCP服务器和客户端(DHCP客户端只有设备成为根节点才启用) */
ESP_ERROR_CHECK(esp_netif_create_default_wifi_mesh_netifs(&netif_sta, NULL));
/* 初始化WIFI */
wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&config));
/* 注册IP事件处理程序 */
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_FLASH));
ESP_ERROR_CHECK(esp_wifi_start());
···
···
}
应用程序可以直接访问ESP-WIFI-MESH栈,而无需通过LwIP栈。仅根节点需要LwIP栈向/从外部IP网络发送/接收数据。 但是,由于每个节点都可能成为根节点(由于自动根节点选择),所以每个节点仍必须初始化LwIP栈,通过esp_netif_init()。为了防止非根节点访问LwIP栈,应用程序不应该使用esp _ netif API创建或注册任何网络接口。
ESP-WIFI-MESH需要根节点与路由器连接。因此,如果一个节点成为根节点,相应的处理程序必须启动DHCP客户端服务,并立即获取IP地址。这样做将允许其他节点开始向/从外部IP网络发送/接收数据包。但是,如果使用静态IP设置,则此步骤是不必要的。
在可以使用ESP-WIFI-MESH系统事件之前,应用程序必须通过esp_event_handler_register()注册Mesh事件处理回调函数。 注册的Mesh事件处理回调函数包含与应用程序相关的每个ESP-WIFI-MESH事件的处理程序。
3.2 初始化Mesh
void app_main(void)
{
···
···
/* 初始化Mesh */
ESP_ERROR_CHECK(esp_mesh_init());
/* 注册Mesh事件处理程序 */
ESP_ERROR_CHECK(esp_event_handler_register(MESH_EVENT, ESP_EVENT_ANY_ID, &mesh_event_handler, NULL));
···
···
}
3.3 配置Mesh拓扑结构
void app_main(void)
{
···
···
/* 设置Mesh拓扑结构 */
ESP_ERROR_CHECK(esp_mesh_set_topology(MESH_TOPO_TREE));
/* 根据拓扑结构设置Mesh最大层数 */
ESP_ERROR_CHECK(esp_mesh_set_max_layer(6));
/* 设置Mesh根节点推举百分比(只有达到此阈值才可成为根节点) */
ESP_ERROR_CHECK(esp_mesh_set_vote_percentage(1));
/* 设置RX队列大小 */
ESP_ERROR_CHECK(esp_mesh_set_xon_qsize(128));
···
···
}
3.4 配置Mesh低功耗功能
void app_main(void)
{
···
···
#ifdef CONFIG_MESH_ENABLE_PS
/* 启用Mesh低功耗功能 */
ESP_ERROR_CHECK(esp_mesh_enable_ps());
/* 设置 Mesh AP模式关联过期时间(在AP模式下此时间内未收到某个子节点的任何数据,Mesh将子节点置为不活跃的并分离它) */
/* 如果设置了较小的占空比,最好增加关联过期时间 */
ESP_ERROR_CHECK(esp_mesh_set_ap_assoc_expire(60));
/* 如果设置了较小的占空比,最好增加广播间隔以避免过多的管理流量 */
ESP_ERROR_CHECK(esp_mesh_set_announce_interval(600, 3300));
#else
/* 禁用Mesh低功耗功能 */
ESP_ERROR_CHECK(esp_mesh_disable_ps());
ESP_ERROR_CHECK(esp_mesh_set_ap_assoc_expire(10));
#endif
···
#ifdef CONFIG_MESH_ENABLE_PS
/* set the device active duty cycle. (default:12, MESH_PS_DEVICE_DUTY_REQUEST) */
ESP_ERROR_CHECK(esp_mesh_set_active_duty_cycle(CONFIG_MESH_PS_DEV_DUTY, CONFIG_MESH_PS_DEV_DUTY_TYPE));
/* set the network active duty cycle. (default:12, -1, MESH_PS_NETWORK_DUTY_APPLIED_ENTIRE) */
ESP_ERROR_CHECK(esp_mesh_set_network_duty_cycle(CONFIG_MESH_PS_NWK_DUTY, CONFIG_MESH_PS_NWK_DUTY_DURATION, CONFIG_MESH_PS_NWK_DUTY_RULE));
#endif
···
}
3.5 配置Mesh网络
ESP-WIFI-MESH通过esp_mesh_set_config()配置,它使用mesh_cfg_t结构接收其参数。 该结构包含用于配置ESP-WIFI-MESH的以下参数:
| 参数 | 描述 |
|---|---|
| Channel | 通道,范围从 1 到 14 |
| Mesh ID | ESP-WIFI-MESH网络的ID,请参见mesh_addr_t |
| Router | 路由器配置,请参见mesh_router_t |
| Mesh AP | Mesh 接入点配置,请参见mesh_ap_cfg_t |
| Crypto Functions | Mesh IE的加密功能,请参见mesh_crypto_funcs_t |
在ESP-WIFI-MESH网络构建过程开始之前,配置的某些部分必须在网络中的每个节点上保持一致(请参阅
mesh_cfg_t)。每个节点必须配置相同的Mesh网络ID,路由器配置和SoftAP配置。
void app_main(void)
{
···
···
/* 默认启用Mesh IE加密 */
mesh_cfg_t cfg = MESH_INIT_CONFIG_DEFAULT();
/* Mesh ID */
memcpy((uint8_t *) &cfg.mesh_id, MESH_ID, 6);
/* 路由器 */
/* 信道(需与路由器信道匹配)*/
cfg.channel = CONFIG_MESH_CHANNEL;
cfg.router.ssid_len = strlen(CONFIG_MESH_ROUTER_SSID);
memcpy((uint8_t *) &cfg.router.ssid, CONFIG_MESH_ROUTER_SSID, cfg.router.ssid_len);
memcpy((uint8_t *) &cfg.router.password, CONFIG_MESH_ROUTER_PASSWD,
strlen(CONFIG_MESH_ROUTER_PASSWD));
/* Mesh softAP */
ESP_ERROR_CHECK(esp_mesh_set_ap_authmode(CONFIG_MESH_AP_AUTHMODE));
cfg.mesh_ap.max_connection = CONFIG_MESH_AP_CONNECTIONS;
memcpy((uint8_t *) &cfg.mesh_ap.password, CONFIG_MESH_AP_PASSWD,
strlen(CONFIG_MESH_AP_PASSWD));
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
···
···
}
3.6 启动Mesh
void app_main(void)
{
···
···
/* 启动Mesh */
ESP_ERROR_CHECK(esp_mesh_start());
···
ESP_LOGI(MESH_TAG, "mesh starts successfully, heap:%d, %s<%d>%s, ps:%d\n", esp_get_minimum_free_heap_size(),
esp_mesh_is_root_fixed() ? "root fixed" : "root not fixed",
esp_mesh_get_topology(), esp_mesh_get_topology() ? "(chain)":"(tree)", esp_mesh_is_ps_enabled());
}
启动ESP-WIFI-MESH后,应用程序应检查ESP-WIFI-MESH事件,以确定它何时连接到网络。连接后,应用程序可以使用esp_mesh_send()和esp_mesh_recv()发送和接收数据包。
3.7 Mesh事件处理程序
void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
mesh_addr_t id = {
0,};
static uint16_t last_layer = 0;
switch (event_id) {
/* Mesh启动完成事件 */
case MESH_EVENT_STARTED: {
/* 获取Mesh网络ID*/
esp_mesh_get_id(&id);
ESP_LOGI(MESH_TAG, "<MESH_EVENT_MESH_STARTED>ID:"MACSTR"", MAC2STR(id.addr));
is_mesh_connected = false;
/* 获取Mesh网络上的当前图层值*/
mesh_layer = esp_mesh_get_layer();
}
break;
/* Mesh停止完成事件 */
case MESH_EVENT_STOPPED: {
ESP_LOGI(MESH_TAG, "<MESH_EVENT_STOPPED>");
is_mesh_connected = false;
mesh_layer = esp_mesh_get_layer();
}
break;
/* 子节点连接事件 */
case MESH_EVENT_CHILD_CONNECTED: {
mesh_event_child_connected_t *child_connected = (mesh_event_child_connected_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_CHILD_CONNECTED>aid:%d, "MACSTR"",
child_connected->aid,
MAC2STR(child_connected->mac));
}
break;
/* 子节点断开连接事件 */
case MESH_EVENT_CHILD_DISCONNECTED: {
mesh_event_child_disconnected_t *child_disconnected = (mesh_event_child_disconnected_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_CHILD_DISCONNECTED>aid:%d, "MACSTR"",
child_disconnected->aid,
MAC2STR(child_disconnected->mac));
}
break;
/* 子节点加入路由表事件 */
case MESH_EVENT_ROUTING_TABLE_ADD: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(MESH_TAG, "<MESH_EVENT_ROUTING_TABLE_ADD>add %d, new:%d, layer:%d",
routing_table->rt_size_change,
routing_table->rt_size_new, mesh_layer);
}
break;
/* 子节点移出路由表事件 */
case MESH_EVENT_ROUTING_TABLE_REMOVE: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(MESH_TAG, "<MESH_EVENT_ROUTING_TABLE_REMOVE>remove %d, new:%d, layer:%d",
routing_table->rt_size_change,
routing_table->rt_size_new, mesh_layer);
}
break;
/* 找不到父节点事件 */
case MESH_EVENT_NO_PARENT_FOUND: {
mesh_event_no_parent_found_t *no_parent = (mesh_event_no_parent_found_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_NO_PARENT_FOUND>scan times:%d",
no_parent->scan_times);
}
/* TODO handler for the failure */
break;
/* 已连接到父节点事件 */
case MESH_EVENT_PARENT_CONNECTED: {
mesh_event_connected_t *connected = (mesh_event_connected_t *)event_data;
esp_mesh_get_id(&id);
mesh_layer = connected->self_layer;
memcpy(&mesh_parent_addr.addr, connected->connected.bssid, 6);
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_PARENT_CONNECTED>layer:%d-->%d, parent:"MACSTR"%s, ID:"MACSTR", duty:%d",
last_layer, mesh_layer, MAC2STR(mesh_parent_addr.addr),
esp_mesh_is_root() ? "<ROOT>" :
(mesh_layer == 2) ? "<layer2>" : "", MAC2STR(id.addr), connected->duty);
last_layer = mesh_layer;
/* 已连接到父节点状态指示 */
mesh_connected_indicator(mesh_layer);
is_mesh_connected = true;
/* 判断该设备是否为Mesh网络中的根节点 */
if (esp_mesh_is_root()) {
/* 开启DHCP客户端获取IP */
esp_netif_dhcpc_start(netif_sta);
}
/* 创建Mesh网络点对点发送和接收两个任务 */
esp_mesh_comm_p2p_start();
}
break;
/* 与父节点断开连接事件 */
case MESH_EVENT_PARENT_DISCONNECTED: {
mesh_event_disconnected_t *disconnected = (mesh_event_disconnected_t *)event_data;
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_PARENT_DISCONNECTED>reason:%d",
disconnected->reason);
is_mesh_connected = false;
mesh_disconnected_indicator();
mesh_layer = esp_mesh_get_layer();
}
break;
/* Mesh网络上图层变更事件 */
case MESH_EVENT_LAYER_CHANGE: {
mesh_event_layer_change_t *layer_change = (mesh_event_layer_change_t *)event_data;
mesh_layer = layer_change->new_layer;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_LAYER_CHANGE>layer:%d-->%d%s",
last_layer, mesh_layer,
esp_mesh_is_root() ? "<ROOT>" :
(mesh_layer == 2) ? "<layer2>" : "");
last_layer = mesh_layer;
mesh_connected_indicator(mesh_layer);
}
break;
/* 获取根节点地址事件 */
case MESH_EVENT_ROOT_ADDRESS: {
mesh_event_root_address_t *root_addr = (mesh_event_root_address_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_ROOT_ADDRESS>root address:"MACSTR"",
MAC2STR(root_addr->addr));
}
break;
/* 选举新的根节点开始事件 */
case MESH_EVENT_VOTE_STARTED: {
mesh_event_vote_started_t *vote_started = (mesh_event_vote_started_t *)event_data;
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_VOTE_STARTED>attempts:%d, reason:%d, rc_addr:"MACSTR"",
vote_started->attempts,
vote_started->reason,
MAC2STR(vote_started->rc_addr.addr));
}
break;
/* 选举新的根节点结束事件 */
case MESH_EVENT_VOTE_STOPPED: {
ESP_LOGI(MESH_TAG, "<MESH_EVENT_VOTE_STOPPED>");
break;
}
/* 选举新的根节点请求事件 */
case MESH_EVENT_ROOT_SWITCH_REQ: {
mesh_event_root_switch_req_t *switch_req = (mesh_event_root_switch_req_t *)event_data;
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_ROOT_SWITCH_REQ>reason:%d, rc_addr:"MACSTR"",
switch_req->reason,
MAC2STR( switch_req->rc_addr.addr));
}
break;
/* 根节点交换响应确认事件 */
case MESH_EVENT_ROOT_SWITCH_ACK: {
/* new root */
mesh_layer = esp_mesh_get_layer();
esp_mesh_get_parent_bssid(&mesh_parent_addr);
ESP_LOGI(MESH_TAG, "<MESH_EVENT_ROOT_SWITCH_ACK>layer:%d, parent:"MACSTR"", mesh_layer, MAC2STR(mesh_parent_addr.addr));
}
break;
/* 根节点能够访问外网事件 */
case MESH_EVENT_TODS_STATE: {
mesh_event_toDS_state_t *toDs_state = (mesh_event_toDS_state_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_TODS_REACHABLE>state:%d", *toDs_state);
}
break;
/* 根节点能够访问外网事件 */
case MESH_EVENT_ROOT_FIXED: {
mesh_event_root_fixed_t *root_fixed = (mesh_event_root_fixed_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_ROOT_FIXED>%s",
root_fixed->is_fixed ? "fixed" : "not fixed");
}
break;
/* 设备替换成与父节点相同的根节点事件 */
case MESH_EVENT_ROOT_ASKED_YIELD: {
mesh_event_root_conflict_t *root_conflict = (mesh_event_root_conflict_t *)event_data;
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_ROOT_ASKED_YIELD>"MACSTR", rssi:%d, capacity:%d",
MAC2STR(root_conflict->addr),
root_conflict->rssi,
root_conflict->capacity);
}
break;
/* 信道切换事件 */
case MESH_EVENT_CHANNEL_SWITCH: {
mesh_event_channel_switch_t *channel_switch = (mesh_event_channel_switch_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_CHANNEL_SWITCH>new channel:%d", channel_switch->channel);
}
break;
/* 扫描完成事件 */
case MESH_EVENT_SCAN_DONE: {
mesh_event_scan_done_t *scan_done = (mesh_event_scan_done_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_SCAN_DONE>number:%d",
scan_done->number);
}
break;
/* Mesh网络状态事件 */
case MESH_EVENT_NETWORK_STATE: {
mesh_event_network_state_t *network_state = (mesh_event_network_state_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_NETWORK_STATE>is_rootless:%d",
network_state->is_rootless);
}
break;
/* 根节点停止重连路由器,非根节点停止重连父节点事件 */
case MESH_EVENT_STOP_RECONNECTION: {
ESP_LOGI(MESH_TAG, "<MESH_EVENT_STOP_RECONNECTION>");
}
break;
/* 找到网络事件 */
case MESH_EVENT_FIND_NETWORK: {
mesh_event_find_network_t *find_network = (mesh_event_find_network_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_FIND_NETWORK>new channel:%d, router BSSID:"MACSTR"",
find_network->channel, MAC2STR(find_network->router_bssid));
}
break;
/* 加入到相同SSID的路由器事件 */
case MESH_EVENT_ROUTER_SWITCH: {
mesh_event_router_switch_t *router_switch = (mesh_event_router_switch_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_ROUTER_SWITCH>new router:%s, channel:%d, "MACSTR"",
router_switch->ssid, router_switch->channel, MAC2STR(router_switch->bssid));
}
break;
/* 低功耗父节点占空比事件 */
case MESH_EVENT_PS_PARENT_DUTY: {
mesh_event_ps_duty_t *ps_duty = (mesh_event_ps_duty_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_PS_PARENT_DUTY>duty:%d", ps_duty->duty);
}
break;
/* 低功耗子节点占空比事件 */
case MESH_EVENT_PS_CHILD_DUTY: {
mesh_event_ps_duty_t *ps_duty = (mesh_event_ps_duty_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_PS_CHILD_DUTY>cidx:%d, "MACSTR", duty:%d", ps_duty->child_connected.aid-1,
MAC2STR(ps_duty->child_connected.mac), ps_duty->duty);
}
break;
default:
ESP_LOGI(MESH_TAG, "unknown id:%d", event_id);
break;
}
}
3.7.1 Mesh启动完成事件
- MESH_EVENT_STARTED
void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
mesh_addr_t id = {
0,};
static uint16_t last_layer = 0;
switch (event_id) {
/* Mesh启动完成事件 */
case MESH_EVENT_STARTED: {
/* 获取Mesh网络ID*/
esp_mesh_get_id(&id);
ESP_LOGI(MESH_TAG, "<MESH_EVENT_MESH_STARTED>ID:"MACSTR"", MAC2STR(id.addr));
is_mesh_connected = false;
/* 获取Mesh网络上的当前图层值*/
mesh_layer = esp_mesh_get_layer();
}
break;
···
···
}
查看打印:

3.7.2 找到网络事件
- MESH_EVENT_FIND_NETWORK
void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
···
···
/* 找到网络事件 */
case MESH_EVENT_FIND_NETWORK: {
mesh_event_find_network_t *find_network = (mesh_event_find_network_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_FIND_NETWORK>new channel:%d, router BSSID:"MACSTR"",
find_network->channel, MAC2STR(find_network->router_bssid));
}
break;
···
···
}
按照上面3.5配置Mesh网络中写入的路由器信道、SSID和密码进行寻找并连接。

3.7.3 与父节点事件
- MESH_EVENT_NO_PARENT_FOUND
- MESH_EVENT_PARENT_CONNECTED
- MESH_EVENT_PARENT_DISCONNECTED
void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
···
···
/* 找不到父节点事件 */
case MESH_EVENT_NO_PARENT_FOUND: {
mesh_event_no_parent_found_t *no_parent = (mesh_event_no_parent_found_t *)event_data;
ESP_LOGI(MESH_TAG, "<MESH_EVENT_NO_PARENT_FOUND>scan times:%d",
no_parent->scan_times);
}
/* TODO handler for the failure */
break;
/* 已连接到父节点事件 */
case MESH_EVENT_PARENT_CONNECTED: {
mesh_event_connected_t *connected = (mesh_event_connected_t *)event_data;
esp_mesh_get_id(&id);
mesh_layer = connected->self_layer;
memcpy(&mesh_parent_addr.addr, connected->connected.bssid, 6);
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_PARENT_CONNECTED>layer:%d-->%d, parent:"MACSTR"%s, ID:"MACSTR", duty:%d",
last_layer, mesh_layer, MAC2STR(mesh_parent_addr.addr),
esp_mesh_is_root() ? "<ROOT>" :
(mesh_layer == 2) ? "<layer2>" : "", MAC2STR(id.addr), connected->duty);
last_layer = mesh_layer;
/* 已连接到父节点状态指示 */
mesh_connected_indicator(mesh_layer);
is_mesh_connected = true;
/* 判断该设备是否为Mesh网络中的根节点 */
if (esp_mesh_is_root()) {
/* 开启DHCP客户端获取IP */
esp_netif_dhcpc_start(netif_sta);
}
/* 创建Mesh网络点对点发送和接收两个任务 */
esp_mesh_comm_p2p_start();
}
break;
/* 与父节点断开连接事件 */
case MESH_EVENT_PARENT_DISCONNECTED: {
mesh_event_disconnected_t *disconnected = (mesh_event_disconnected_t *)event_data;
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_PARENT_DISCONNECTED>reason:%d",
disconnected->reason);
is_mesh_connected = false;
mesh_disconnected_indicator();
mesh_layer = esp_mesh_get_layer();
}
break;
···
···
}

连接到父节点后,打印当前层级的切换情况,父节点MAC地址,和设备在Mesh网络中ID。如果是Mesh网络中的根节点,则开启DHCP客户端获取IP与外网通信。然后创建Mesh网络点对点发送和接收两个任务。

3.7.4 子节点路由表事件
- MESH_EVENT_ROUTING_TABLE_ADD
- MESH_EVENT_ROUTING_TABLE_REMOVE
void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
···
···
/* 子节点加入路由表事件 */
case MESH_EVENT_ROUTING_TABLE_ADD: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(MESH_TAG, "<MESH_EVENT_ROUTING_TABLE_ADD>add %d, new:%d, layer:%d",
routing_table->rt_size_change,
routing_table->rt_size_new, mesh_layer);
}
break;
/* 子节点移出路由表事件 */
case MESH_EVENT_ROUTING_TABLE_REMOVE: {
mesh_event_routing_table_change_t *routing_table = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGW(MESH_TAG, "<MESH_EVENT_ROUTING_TABLE_REMOVE>remove %d, new:%d, layer:%d",
routing_table->rt_size_change,
routing_table->rt_size_new, mesh_layer);
}
break;
···
···
}
ESP-WIFI-MESH网络中的每个节点将单独维护自己的路由表,用于将ESP-WIFI-MESH数据包(请参见ESP-WIFI-MESH数据包)正确路由到正确目标节点。特定节点的路由表将包括特定节点的子网内所有节点的MAC地址(包括特定节点本身的MAC地址)。每个路由表在内部划分为多个子表,每个子表对应于每个子节点的子网。

使用上面的图作为示例,节点B的路由表将包括节点B到I的MAC地址(即等同于节点B的子网)。节点B的路由表在内部划分为两个子表,包含节点C到F和节点G到I(即分别等同于节点C和G的子网)。
3.7.5 选举新的根节点事件
- MESH_EVENT_VOTE_STARTED
- MESH_EVENT_VOTE_STOPPED
- MESH_EVENT_ROOT_SWITCH_REQ
- MESH_EVENT_ROOT_SWITCH_ACK
void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
···
···
/* 选举新的根节点开始事件 */
case MESH_EVENT_VOTE_STARTED: {
mesh_event_vote_started_t *vote_started = (mesh_event_vote_started_t *)event_data;
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_VOTE_STARTED>attempts:%d, reason:%d, rc_addr:"MACSTR"",
vote_started->attempts,
vote_started->reason,
MAC2STR(vote_started->rc_addr.addr));
}
break;
/* 选举新的根节点结束事件 */
case MESH_EVENT_VOTE_STOPPED: {
ESP_LOGI(MESH_TAG, "<MESH_EVENT_VOTE_STOPPED>");
break;
}
/* 选举新的根节点请求事件 */
case MESH_EVENT_ROOT_SWITCH_REQ: {
mesh_event_root_switch_req_t *switch_req = (mesh_event_root_switch_req_t *)event_data;
ESP_LOGI(MESH_TAG,
"<MESH_EVENT_ROOT_SWITCH_REQ>reason:%d, rc_addr:"MACSTR"",
switch_req->reason,
MAC2STR( switch_req->rc_addr.addr));
}
break;
/* 根节点交换响应确认事件 */
case MESH_EVENT_ROOT_SWITCH_ACK: {
/* new root */
mesh_layer = esp_mesh_get_layer();
esp_mesh_get_parent_bssid(&mesh_parent_addr);
ESP_LOGI(MESH_TAG, "<MESH_EVENT_ROOT_SWITCH_ACK>layer:%d, parent:"MACSTR"", mesh_layer, MAC2STR(mesh_parent_addr.addr));
}
break;
···
···
}

3.8 获取IP事件处理程序
void ip_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
ESP_LOGI(MESH_TAG, "<IP_EVENT_STA_GOT_IP>IP:" IPSTR, IP2STR(&event->ip_info.ip));
}
查看打印:

3.9 数据发送和接收
void esp_mesh_p2p_tx_main(void *arg)
{
int i;
esp_err_t err;
int send_count = 0;
mesh_addr_t route_table[CONFIG_MESH_ROUTE_TABLE_SIZE];
int route_table_size = 0;
mesh_data_t data;
data.data = tx_buf;
data.size = sizeof(tx_buf);
data.proto = MESH_PROTO_BIN;
data.tos = MESH_TOS_P2P;
is_running = true;
while (is_running) {
/* non-root do nothing but print */
// 非根节点只进行打印
if (!esp_mesh_is_root()) {
ESP_LOGI(MESH_TAG, "layer:%d, rtableSize:%d, %s", mesh_layer,
esp_mesh_get_routing_table_size(),
(is_mesh_connected && esp_mesh_is_root()) ? "ROOT" : is_mesh_connected ? "NODE" : "DISCONNECT");
vTaskDelay(10 * 1000 / portTICK_RATE_MS);
continue;
}
// 节点查询自身路由表
esp_mesh_get_routing_table((mesh_addr_t *) &route_table,
CONFIG_MESH_ROUTE_TABLE_SIZE * 6, &route_table_size);
if (send_count && !(send_count % 100)) {
ESP_LOGI(MESH_TAG, "size:%d/%d,send_count:%d", route_table_size,
esp_mesh_get_routing_table_size(), send_count);
}
send_count++;
tx_buf[25] = (send_count >> 24) & 0xff;
tx_buf[24] = (send_count >> 16) & 0xff;
tx_buf[23] = (send_count >> 8) & 0xff;
tx_buf[22] = (send_count >> 0) & 0xff;
if (send_count % 2) {
memcpy(tx_buf, (uint8_t *)&light_on, sizeof(light_on));
} else {
memcpy(tx_buf, (uint8_t *)&light_off, sizeof(light_off));
}
for (i = 0; i < route_table_size; i++) {
// 给每个节点发送数据,地址为NULL就是给根节点发。
// 自身节点在自身路由表[0]中,如是根节点则生成根节点的地址和路由表中[0]记录的不一致,路由表中最后一个字节小1
err = esp_mesh_send(&route_table[i], &data, MESH_DATA_P2P, NULL, 0);
if (err) {
ESP_LOGE(MESH_TAG,
"[ROOT-2-UNICAST:%d][L:%d]parent:"MACSTR" to "MACSTR", heap:%d[err:0x%x, proto:%d, tos:%d]",
send_count, mesh_layer, MAC2STR(mesh_parent_addr.addr),
MAC2STR(route_table[i].addr), esp_get_minimum_free_heap_size(),
err, data.proto, data.tos);
} else if (!(send_count % 100)) {
ESP_LOGW(MESH_TAG,
"[ROOT-2-UNICAST:%d][L:%d][rtableSize:%d]parent:"MACSTR" to "MACSTR", heap:%d[err:0x%x, proto:%d, tos:%d]",
send_count, mesh_layer,
esp_mesh_get_routing_table_size(),
MAC2STR(mesh_parent_addr.addr),
MAC2STR(route_table[i].addr), esp_get_minimum_free_heap_size(),
err, data.proto, data.tos);
}
}
/* if route_table_size is less than 10, add delay to avoid watchdog in this task. */
if (route_table_size < 10) {
vTaskDelay(1 * 1000 / portTICK_RATE_MS);
}
}
vTaskDelete(NULL);
}
void esp_mesh_p2p_rx_main(void *arg)
{
int recv_count = 0;
esp_err_t err;
mesh_addr_t from;
int send_count = 0;
mesh_data_t data;
int flag = 0;
data.data = rx_buf;
data.size = RX_SIZE;
is_running = true;
while (is_running) {
data.size = RX_SIZE;
err = esp_mesh_recv(&from, &data, portMAX_DELAY, &flag, NULL, 0);
if (err != ESP_OK || !data.size) {
ESP_LOGE(MESH_TAG, "err:0x%x, size:%d", err, data.size);
continue;
}
/* extract send count */
if (data.size >= sizeof(send_count)) {
send_count = (data.data[25] << 24) | (data.data[24] << 16)
| (data.data[23] << 8) | data.data[22];
}
if(mesh_layer == 1)
{
// 根节点解析处理
//RootMeshDataAnalysis((char *)data.data, data.size);
}
else
{
// 叶节点解析处理
//LeafMeshDataAnalysis((char *)data.data, data.size);
}
recv_count++;
if (!(recv_count % 1)) {
ESP_LOGW(MESH_TAG,
"[#RX:%d/%d][L:%d] parent:"MACSTR", receive from "MACSTR", size:%d, heap:%d, flag:%d[err:0x%x, proto:%d, tos:%d]",
recv_count, send_count, mesh_layer,
MAC2STR(mesh_parent_addr.addr), MAC2STR(from.addr),
data.size, esp_get_minimum_free_heap_size(), flag, err, data.proto,
data.tos);
}
}
vTaskDelete(NULL);
}
• 由 Leung 写于 2022 年 6 月 8 日
边栏推荐
- Why is it that the live video of the devices connected to easygbs suddenly cannot be played? Insufficient database read / write
- Fix the problem that the right mouse button does not have a vscode shortcut
- Wechat applet (authorized login of TP5)
- 100. same tree
- Learn C language well from keywords
- Multimedia框架解析之MediaExtractor源码分析(一)
- Exchange two values without introducing the third variable
- QT socket设置连接超时时间
- Training and testing of super score model in mmediting
- Biweekly investment and financial report: capital rush yuan universe game
猜你喜欢

通过两种方式手写一个消息队列

Unity 全景漫游过程中使用AWSD控制镜头移动,EQ控制镜头升降,鼠标右键控制镜头旋转。

JS two methods to determine whether there are duplicate values in the array

The nearest common ancestor of 235 binary search tree

Why is it that the live video of the devices connected to easygbs suddenly cannot be played? Insufficient database read / write

100. same tree
![Handwritten promise [05] - exception capture of promise method and optional parameters of then method implementation](/img/e7/87069f921ae003511e32b23653703c.jpg)
Handwritten promise [05] - exception capture of promise method and optional parameters of then method implementation

Résoudre le problème de la durée inexacte du fichier audio AAC obtenu par ffmpeg

347. top k high frequency elements

Why don't we have our own programming language?
随机推荐
Redux learning (III) -- using Redux saga, writing middleware functions, and splitting reducer files
instanceof到底是怎样判断引用数据类型的!
争议很大的问题
无心剑汉英双语诗001.《爱》
Use of qscriptengine class
On cursor in MySQL
022-Redis数据库0基础入门
UEFI查找PCI设备
What is sentinel produced by Ali?
网狐游戏服务器房间配置向导服务定制功能页实现
UEFI finding PCI devices
JS judge whether the year is a leap year and the number of days in the month
Zabbix 监控主机是否在线
235-二叉搜索树的最近公共祖先
572. 另一个树的子树
CCS安装编译器的方法
[]==! []
开源漫画服务器Mango
Lazy load
fatal: refusing to merge unrelated histories