ESP-NOW 介绍 ESP-NOW 是乐鑫定义的一种无线通信协议,能够在无路由器的情况下直接、快速、低功耗地控制智能设备。它能够与 Wi-Fi 和 Bluetooth LE 共存,支持乐鑫 ESP8266、ESP32、ESP32-S 和 ESP32-C 等多系列 SoC。ESP-NOW 广泛应用于智能家电、远程控制和传感器等领域。
关于 ESP-NOW FAQ ,最大传输速率、应用场景、信道选择、配对限制等。
1、ESP-NOW 配对限制最多 20 个设备,是否有办法控制更多的设备?
使用广播包进行控制即可,目的地址包含在 payload 中,不受配对数量限制。仅需配置正确的广播地址即可。
2、ESP-NOW 最多可以控制多少个设备?
这取决于具体的通信方式:
如使用单播包,支持同时最多配对并控制 20 个设备。
如使用 ESP-NOW 加密模式,支持同时最多配对并控制 6 个设备。
如使用广播包,仅需配置正确的广播地址即可。控制设备的数量理论上没有上限,但需考虑设备过多时的干扰问题。
3、为什么将 ESP-NOW 用户数据包的最大数据长度限制为 250 字节,这个数值可以修改吗?
最长长度目前不能修改 。因为 ESP-NOW 使用动作帧中的供应商特定元素字段传输数据,802.11 协议规定一个供应商特定元素中的长度
字段只有 1 个字节 (0xff = 255),因此限制了正文部分 ESP-NOW 数据长度,最长为 250 字节。
或者您可以使用 API esp_wifi_80211_tx()
发送数据,使用 sniffer 模式接收数据。这样同样可以实现只工作在 Wi-Fi 层并且不使用 TCP/IP 协议栈。
4、ESP-NOW 是否可以与 Wi-Fi 一起使用?
可以,但需要注意的是 ESP-NOW 的信道要和所连接的 AP 的信道相同。
5、ESP-NOW 设备间通信需要连接路由器吗?
ESP-NOW 的交互方式为直接从 设备到设备 进行通信,不需要通过路由器来转发数据。
6、使用 ESP-NOW 应用时,需要注意什么?
连接 Wi-Fi 以后不能再切换信道,只能在当前 Wi-Fi 信道收发数据。
如果设备进入 Modem-sleep 模式,将无法接受来自 ESP-NOW 的数据。
7、如果保证数据安全性?
ESP-NOW 可以通过 ECDH 和 AES128-CCM 来保护数据安全。
使用 ECDH 和所有权证明 (PoP) 字符串授权会话、生成共享密钥
使用 AES256-CTR 模式加密配置数据
使用 AES128-CCM 模式加密 ESP-NOW 数据
ESP-NOW 数据帧 ESP-IDF 编程指南:ESP-NOW
ESP-NOW 是一种由乐鑫公司定义的 无连接 Wi-Fi 通信协议。在 ESP-NOW 中,应用程序数据被封装在各个供应商的动作帧 中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。
目前 ESP-NOW 支持两个版本:v1.0 和 v2.0:
v2.0 的设备支持的最大数据包长度为 1490 (ESP_NOW_MAX_DATA_LEN_V2
) 字节;
v1.0 的设备支持的最大数据包长度为 250 (ESP_NOW_MAX_DATA_LEN
) 字节。
v2.0 设备可以接收来自 v2.0 和 v1.0 设备的数据包。v1.0 设备只能接收来自 v1.0 设备的数据包。
当然,v1.0 设备也可以接收长度不超过 250 (ESP_NOW_MAX_IE_DATA_LEN
) 的 v2.0 数据包,只是如果长度超过此值,就只接收前 250 (ESP_NOW_MAX_IE_DATA_LEN
) 字节,或是直接丢弃数据包。
ESP-NOW 帧格式:
1 2 3 4 ----------------------------------------------------------------------------- | MAC 报头 | 分类代码 | 组织标识符 | 随机值 | 供应商特定内容 | FCS | ----------------------------------------------------------------------------- 24 字节 1 字节 3 字节 4 字节 7-x 字节 4 字节
帧数据包大小范围:43 字节 ~ 293 字节
供应商特定内容 帧格式:
1 2 3 4 5 -------------------------------------------------------------------------------------- | 元素 ID | 长度 | 组织标识符 | 类型 | 保留 | 版本 | 正文 | -------------------------------------------------------------------------------------- 1 字节 1 字节 3 字节 1 字节 7~4 比特| 3~0 比特 1 字节 0-250 字节
最大有效载荷:250 字节
配对设备:即将对方设备的MAC地址通过 esp_now_add_peer()
将其添加到配对设备列表中。配对设备的最大数量是 20,其中加密设备的数量不超过17,默认值是 7。
由于ESPNOW帧数据最大有效载荷:250 字节,乐鑫将帧数据有效载荷封装了应用层,其中应用层报头占用:20 字节,剩余 230 字节用于 body 部分。
应用层数据报:
1 2 3 4 ----------------------------------------------------------------------------- | 类型 | 版本 | 保留 | 数据大小 | 自定义帧头 | 目标MAC地址 | 源MAC地址 | 数据 | ----------------------------------------------------------------------------- 4 bit 2 bit 2 bit 1 Byte 6 Byte 6 字节 6 Byte 0~230 Byte
其中自定义帧头:
1 2 3 4 ----------------------------------------------------------------------------------------------------------- | 魔术数 | 信道 | 过滤相邻信道干扰位 | 过滤弱信号位 | 安全模式 | 保留 | 广播位 | 群组 | ACK位 | 重传次数 | TTL | RSSI | ----------------------------------------------------------------------------------------------------------- 2 byte 4 bit 1bit 1 bit 1 bit 4 bit 1 bit 1bit 1 bit 5 bit 5 bit 8 bit
ESP-NOW Control 对于 ESP-NOW Control Example 的解读。
ESP-NOW 根据数据流定义了两个角色:发起者(initiator)和响应者(responder)。同一个设备即可单独作为发起者,也可以单独作为响应者,也可以即作为发起者又作为响应者。
在物联网系统中,通常开关、传感器、液晶屏,而灯、插座作为响应者。
在 ESP-NOW Control Example 中,乐鑫又在 esp_now.h 的基础上封住了如下几个库:
1 2 3 4 5 6 7 - control - debug - espnow - ota - provisioning - security - utils
用户可以基于上述几个库开发上层应用,当然如果用户希望不想使用这几个库,也可以直接使用 esp_now.h 更加原始的库,然后用户自定义esp_now 应用层数据格式。
在 espnow/include/espnow.h 中封装了esp_now 应用层数据结构:
自定义类型帧头的数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct espnow_frame_head_s { uint16_t magic; uint8_t channel : 4 ; bool filter_adjacent_channel : 1 ; bool filter_weak_signal : 1 ; bool security : 1 ; uint16_t : 4 ; bool broadcast : 1 ; bool group : 1 ; bool ack : 1 ; uint16_t retransmit_count : 5 ; uint8_t forward_ttl : 5 ; int8_t forward_rssi : 8 ; } __attribute__ ((packed)) espnow_frame_head_t ;
1 2 3 4 5 6 7 8 9 10 typedef struct { uint8_t type : 4 ; uint8_t version : 2 ; uint8_t : 2 ; uint8_t size; espnow_frame_head_t frame_head; uint8_t dest_addr[6 ]; uint8_t src_addr[6 ]; uint8_t payload[0 ]; } __attribute__((packed)) espnow_data_t ;
__attribute__((packed))
: 这是一个GCC编译器属性,告诉编译器不要对结构体成员进行默认的对齐优化,即按照每个成员声明的顺序紧密排列,以保证结构体在内存中的布局与协议要求一致,这对于网络通信非常重要。
那么对于 espnow_frame_head_t
结构体大小是 6 Byte,espnow_data_t
的结构体大小是 20 Bype。
注意:uint8_t payload[0] 在结构体中不占用,通过sizeof(espnow_data_t) 得出只有 20 Byte
数据包类型定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 typedef enum { ESPNOW_DATA_TYPE_ACK, ESPNOW_DATA_TYPE_FORWARD, ESPNOW_DATA_TYPE_GROUP, ESPNOW_DATA_TYPE_PROV, ESPNOW_DATA_TYPE_CONTROL_BIND, ESPNOW_DATA_TYPE_CONTROL_DATA, ESPNOW_DATA_TYPE_OTA_STATUS, ESPNOW_DATA_TYPE_OTA_DATA, ESPNOW_DATA_TYPE_DEBUG_LOG, ESPNOW_DATA_TYPE_DEBUG_COMMAND, ESPNOW_DATA_TYPE_DATA, ESPNOW_DATA_TYPE_SECURITY_STATUS, ESPNOW_DATA_TYPE_SECURITY, ESPNOW_DATA_TYPE_SECURITY_DATA, ESPNOW_DATA_TYPE_RESERVED, ESPNOW_DATA_TYPE_MAX, } espnow_data_type_t ;
在 control/include/espnow_ctrl.h 中对于 initiator 和 responder 的控制数据封装了数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 typedef enum { ESPNOW_ATTRIBUTE_BASE = 0x0000 , ESPNOW_ATTRIBUTE_POWER = 0x0001 , ESPNOW_ATTRIBUTE_POWER_ADD = 0x0002 , ESPNOW_ATTRIBUTE_ATTRIBUTE = 0x0003 , ESPNOW_ATTRIBUTE_LIGHT_BASE = 0x0100 , ESPNOW_ATTRIBUTE_BRIGHTNESS = 0x0101 , ESPNOW_ATTRIBUTE_BRIGHTNESS_ADD = 0x0102 , ESPNOW_ATTRIBUTE_HUE = 0x0103 , ESPNOW_ATTRIBUTE_HUE_ADD = 0x0104 , ESPNOW_ATTRIBUTE_SATURATION = 0x0105 , ESPNOW_ATTRIBUTE_SATURATION_ADD = 0x0106 , ESPNOW_ATTRIBUTE_WARM = 0x0107 , ESPNOW_ATTRIBUTE_WARM_ADD = 0x0108 , ESPNOW_ATTRIBUTE_COLD = 0x0109 , ESPNOW_ATTRIBUTE_COLD_ADD = 0x010a , ESPNOW_ATTRIBUTE_RED = 0x010b , ESPNOW_ATTRIBUTE_RED_ADD = 0x010c , ESPNOW_ATTRIBUTE_GREEN = 0x010d , ESPNOW_ATTRIBUTE_GREEN_ADD = 0x010e , ESPNOW_ATTRIBUTE_BLUE = 0x010f , ESPNOW_ATTRIBUTE_BLUE_ADD = 0x0110 , ESPNOW_ATTRIBUTE_MODE = 0x0111 , ESPNOW_ATTRIBUTE_MODE_ADD = 0x0112 , ESPNOW_ATTRIBUTE_BUTTON_BASE = 0x0200 , ESPNOW_ATTRIBUTE_KEY_1 = 0x0201 , ESPNOW_ATTRIBUTE_KEY_2 = 0x0202 , ESPNOW_ATTRIBUTE_KEY_3 = 0x0203 , ESPNOW_ATTRIBUTE_KEY_4 = 0x0204 , ESPNOW_ATTRIBUTE_KEY_5 = 0x0205 , ESPNOW_ATTRIBUTE_KEY_6 = 0x0206 , ESPNOW_ATTRIBUTE_KEY_7 = 0x0207 , ESPNOW_ATTRIBUTE_KEY_8 = 0x0208 , ESPNOW_ATTRIBUTE_KEY_9 = 0x0209 , ESPNOW_ATTRIBUTE_KEY_10 = 0x0210 , ESPNOW_ATTRIBUTE_BATTERY_BASE = 0x0300 , ESPNOW_ATTRIBUTE_STATUS_LOW_BATTERY = 0x0301 , ESPNOW_ATTRIBUTE_BATTERY_LEVEL = 0x0302 , ESPNOW_ATTRIBUTE_CHARGING_STATE = 0x0303 , } espnow_attribute_t ; typedef struct espnow_ctrl_data_s {#ifdef CONFIG_ESPNOW_CONTROL_AUTO_CHANNEL_SENDING espnow_frame_head_t frame_head; #endif espnow_attribute_t initiator_attribute; espnow_attribute_t responder_attribute; union { bool responder_value_b; int responder_value_i; float responder_value_f; struct { uint32_t responder_value_s_flag : 24 ; uint8_t responder_value_s_size; }; }; char responder_value_s[0 ]; } espnow_ctrl_data_t ;
在 espnow/include/espnow.h 中对于ESP_NOW 事件也进行了定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define ESP_EVENT_ESPNOW_CTRL_BIND (ESP_EVENT_ESPNOW_CTRL_BASE + 0) #define ESP_EVENT_ESPNOW_CTRL_UNBIND (ESP_EVENT_ESPNOW_CTRL_BASE + 1) #define ESP_EVENT_ESPNOW_CTRL_BIND_ERROR (ESP_EVENT_ESPNOW_CTRL_BASE + 2) #define ESP_EVENT_DECLARE_BASE(id) extern esp_event_base_t const id ESP_EVENT_DECLARE_BASE(ESP_EVENT_ESPNOW); #define ESP_EVENT_ESPNOW_PROV_BASE 0x100 #define ESP_EVENT_ESPNOW_CTRL_BASE 0x200 #define ESP_EVENT_ESPNOW_OTA_BASE 0x300 #define ESP_EVENT_ESPNOW_DEBUG_BASE 0x400 #define ESP_EVENT_ESPNOW_RESERVED_BASE 0x500
ESP-NOW Provisioning 在配网中也要对ESP32设备分为两种角色:发起者(initiator)和 响应者(responder)
发起者
1、持续扫描响应者发出 ESP-NOW 的 provision beacon(信标)
2、当接收响应者发出的 provision beacon后,发送 “设备类型配置帧”,用于向响应者请求 Wi-Fi凭证(即AP的SSID和密码)
3、如果接收到响应者发出的 provision 帧后,则从数据帧中获取 Wi-Fi 凭证,并连接到目标AP
响应者
1、在 30 秒内,每 100 毫秒广播一次 provision beacon
2、如果接收到发起者发出的 “设备类型配置帧” 后,则发出包含 Wi-Fi 凭证的 Wi-Fi 类型配置帧进行响应
在 provisioning/include/espnow_prov.h 定义了 esp_now 配网数据包类型:
1 2 3 4 5 6 typedef enum { ESPNOW_PROV_TYPE_BEACON, ESPNOW_PROV_TYPE_DEVICE, ESPNOW_PROV_TYPE_WIFI, } espnow_prov_type_t ;
当然配网数据包结构也要有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #define ESPNOW_PACKED_STRUCT __attribute__ ((packed)) typedef enum { ESPNOW_PROV_AUTH_INVALID, ESPNOW_PROV_AUTH_PRODUCT, ESPNOW_PROV_AUTH_DEVICE, ESPNOW_PROV_AUTH_CERT, } espnow_prov_auth_mode_t ; typedef struct espnow_prov_initiator_s { char product_id[16 ]; char device_name[16 ]; espnow_prov_auth_mode_t auth_mode; union { char device_secret[32 ]; char product_secret[32 ]; char cert_secret[32 ]; }; uint8_t custom_size; uint8_t custom_data[0 ]; } ESPNOW_PACKED_STRUCT espnow_prov_initiator_t ; typedef struct espnow_prov_responder_s { char product_id[16 ]; char device_name[16 ]; } ESPNOW_PACKED_STRUCT espnow_prov_responder_t ; typedef enum { WIFI_MODE_NULL = 0 , WIFI_MODE_STA, WIFI_MODE_AP, WIFI_MODE_APSTA, WIFI_MODE_NAN, WIFI_MODE_MAX } wifi_mode_t ; typedef struct espnow_prov_wifi_s { wifi_mode_t mode; union { wifi_ap_config_t ap; wifi_sta_config_t sta; }; char token[32 ]; uint8_t custom_size; uint8_t custom_data[0 ]; } ESPNOW_PACKED_STRUCT espnow_prov_wifi_t ; typedef struct { uint8_t type; union { espnow_prov_initiator_t initiator_info; espnow_prov_responder_t responder_info; espnow_prov_wifi_t wifi_config; }; } __attribute__((packed)) espnow_prov_data_t ;
responder (dc:da:0c:d2:4f:74)创建定时器,每100ms发送 ESPNOW_DATA_TYPE_PROV
广播数据,并注册回调函数用来接收 ESPNOW_DATA_TYPE_PROV
类型的数据。
1 2 data(33): 00 72 65 73 70 6f 6e 64 65 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
initiator(f0:9e:9e:99:12:60) 持续扫描 ESPNOW_DATA_TYPE_PROV
类型的数据,接收到 ESPNOW_DATA_TYPE_PROV
类型数据后,接着发送 ESPNOW_DATA_TYPE_PROV
的单播数据,并注册回调函数接收 ESPNOW_DATA_TYPE_PROV
类型数据:
1 2 data(33): 00 72 65 73 70 6f 6e 64 65 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
1 2 data(218): 01 69 6e 69 74 69 61 74 6f 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 60 00 06 00 23 21 8a 55 11 88 02 00 ff ff ff ff ff ff dc da 0c d2 4f 74 00 72 65 73 70 6f 6e 64 65 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc ab ca 3f eb 00 00 00 00 72 65 73 70 6f 6e 64 65 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ff 02 00 00 00 00 00 00 00 00 00 00 00 00 00 02 85 00 70 ec 00 00 00 00 01 01 42 d6 10 ac ca 3f 43 53 01 00 14 8d c9 3f e0 ab
responder 接收到 ESPNOW_DATA_TYPE_PROV
类型的数据后,接着发送 ESPNOW_DATA_TYPE_PROV
类型的单播数据:
1 2 data(218): 01 69 6e 69 74 69 61 74 6f 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 60 00 06 00 23 21 8a 55 11 88 02 00 ff ff ff ff ff ff dc da 0c d2 4f 74 00 72 65 73 70 6f 6e 64 65 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc ab ca 3f eb 00 00 00 00 72 65 73 70 6f 6e 64 65 72 5f 74 65 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ff 02 00 00 00 00 00 00 00 00 00 00 00 00 00 02 85 00 70 ec 00 00 00 00 01 01 42 d6 10 ac ca 3f 43 53 01 00 14 8d c9 3f e0 ab
1 2 data(218): 02 00 00 00 00 43 55 42 45 43 5f 41 50 5f 32 2e 34 47 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 63 75 62 65 63 32 30 31 35 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
initiator 接收到 ESPNOW_DATA_TYPE_PROV
类型数据后,接着去连接目标AP
1 data(218): 02 00 00 00 00 43 55 42 45 43 5f 41 50 5f 32 2e 34 47 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 63 75 62 65 63 32 30 31 35 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00