一、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 日

• 参考:ESP32 学习日志(3)——WIFI-MESH简介

ESP32学习笔记(50)——ESP-WIFI-MESH接口使用相关推荐

  1. ESP32学习笔记(7)——SmartConfig接口使用(ESP-Touch和AirKiss)

    一.概述 SmartConfig是TI开发的一种配置技术,用于将新的Wi-Fi设备连接到Wi-Fi网络.它使用移动应用程序将网络凭据从智能手机或平板电脑广播到未配置的Wi-Fi设备. 该技术的优点是设 ...

  2. ESP32学习笔记(22)——ADC接口使用

    一.概述 ESP32 集成了 2 个 12 位逐次逼近模数转换器 (SARADC),支持 18 个测量通道(模拟使能引脚). 支持以下通道: ADC1: 8通道:GPIO32 - GPIO39 ADC ...

  3. ESP32学习笔记(45)——DAC接口使用

    一.概述 ESP32 有两个 8 位 DAC(数模转换器) 通道,分别连接 GPIO25(通道 1) 和 GPIO26(通道 2). DAC 驱动器允许将这些通道设置为任意电压. ESP-IDF 编程 ...

  4. ESP32学习笔记(41)——SNTP接口使用

    一.SNTP简介 简单网络时间协议(Simple Network Time Protocol),由 NTP 改编而来,主要用来同步因特网中的计算机时钟. SNTP 协议是用来同步本地的时间到 unix ...

  5. ESP32学习笔记(20)——SPI(从机)接口使用

    一.SPI简介 SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线.它被广泛地使用在 ADC.LCD ...

  6. ESP32学习笔记(19)——SPI(主机)接口使用

    一.SPI简介 SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线.它被广泛地使用在 ADC.LCD ...

  7. ESP32学习笔记(1)——搭建环境、编译烧写(Windows+VS Code)

    Espressif-IDE 环境搭建参看 ESP32学习笔记(50)--搭建环境.编译烧写(Windows+Espressif-IDE) 一.搭建环境 1.1 官方资料 ESP-IDF 编程指南 1. ...

  8. ESP32学习笔记(14)——HTTP服务器

    一.HTTP简介 HTTP(Hyper Text Transfer Protocol) 超文本传输协议,是一种建立在 TCP 上的无状态连接,整个基本的工作流程是客户端发送一个 HTTP 请求,说明客 ...

  9. ESP32学习笔记(27)——BLE GAP主机端扫描

    一.背景 1.1 低功耗蓝牙(BLE)协议栈 链路层(LL) 控制设备的射频状态,有五个设备状态:待机.广播.扫描.初始化和连接. 广播 为广播数据包,而 扫描 则是监听广播. GAP通信中角色,中心 ...

  10. ESP32学习笔记(七) 复位和时钟

    ESP32学习笔记(七) 复位和时钟 目录: ESP32学习笔记(一) 芯片型号介绍 ESP32学习笔记(二) 开发环境搭建 VSCode+platformio ESP32学习笔记(三) 硬件资源介绍 ...

最新文章

  1. Ubuntu 14.04 64bit上安装Scrapy
  2. Oracle大的存储层次体系,Oracle 数据库中的逻辑存储层次体系
  3. 【笔记】mybatis的sqlSession和Mapper详解
  4. linuxliveu盘怎么用_U盘数据如何恢复?U盘打不开怎么办?
  5. [pytorch、学习] - 5.6 深度卷积神经网络(AlexNet)
  6. GDAL根据Shape文件切图(java)
  7. 在自动驾驶技术上,一向自信满满的马斯克也承认了特斯拉的不足
  8. kali 克隆网页_Web侦察工具HTTrack (网站克隆)
  9. python123 测验6:组合数据类型
  10. ANSYS APDL 绘制云图时出现错误“The Requested S data is not available. The PLNSOL command is ignored“的解决方法
  11. macbook pro 2017版电池问题,八九十的电,用着忽然关机,再开机提示充电才行。
  12. 家用洗地机有什么优缺点?入门级家用洗地机
  13. js统一社会信用代码正则验证
  14. nginx正向代理https
  15. 《万里长江图》告诉我们:金沙江是长江的正源
  16. c语言内存越界例子,内存越界的可能情况分析,C语言内存越界详解
  17. LinuxI/O多路复用转接服务器——epoll模型实现
  18. 如何快速实现文章AI伪原创?
  19. Stack和Queue:后进先出和先进先出
  20. 《Photoshop+Lightroom数码摄影后期处理经典教程》目录—导读

热门文章

  1. 史上最全的常用中英文学术网站(二)
  2. 【已解决】使用pip安装包提示TLS证书错误解决办法
  3. Windows API Watch Dog----看门狗(相互监视的两个进程(真源代码))
  4. 状态模式,靠着这份190页的面试资料
  5. 小米13系列配置曝光 骁龙8 Gen2+2K大屏
  6. C# (初入江湖)-猫狗大战(面向对象的三大特征)
  7. js系列二十三:函数柯理化
  8. 启动nvcpldll时出现问题找不到指定的模块
  9. 大数据与精准营销研究
  10. 根据爬虫原理收集所有网站的邮箱及手机联系方式然后建立行业email数据库