# ESP-NOW

[English]

## 概述

ESP-NOW 是一种由乐鑫公司定义的无连接 Wi-Fi 通信协议。在 ESP-NOW 中，应用程序数据被封装在各个供应商的动作帧中，然后在无连接的情况下，从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。

CTR 与 CBC-MAC 协议 (CCMP) 可用来保护动作帧的安全。ESP-NOW 广泛应用于智能照明、远程控制、传感器等领域。

## 帧格式

ESP-NOW 使用供应商的动作帧传输数据，默认比特率为 1 Mbps。

目前 ESP-NOW 支持两个版本：v1.0 和 v2.0。v2.0 的设备支持的最大数据包长度为 1470 (`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`) 字节，或是直接丢弃数据包。

具体行为请参考对应 IDF 版本中的文档。

供应商的动作帧格式为：

    -----------------------------------------------------------------------------
    |   MAC 报头   |  分类代码  |  组织标识符  |  随机值  |  供应商特定内容  |   FCS   |
    -----------------------------------------------------------------------------
       24 字节        1 字节        3 字节      4 字节      7-x 字节       4 字节

- 分类代码：分类代码字段可用于指示各个供应商的类别（比如 127）。

- 组织标识符：组织标识符包含一个唯一标识符（比如 0x18fe34），为乐鑫指定的 MAC 地址的前三个字节。

- 随机值：防止重放攻击。

- 供应商特定内容：供应商特定内容包含若干个（大于等于1）特定供应商元素字段，对于 v2.0 版本，x = 1512(1470+6*7)；对于 v1.0 版本，x = 257(250+7)。

特定供应商元素的帧格式为：

    ESP-NOW v1.0：
    ---------------------------------------------------------------------------
    |  元素 ID  |  长度  |  组织标识符  |  类型  |  保留  |  版本    |     正文     |
    ---------------------------------------------------------------------------
                                              7~4 比特| 3~0 比特
       1 字节     1 字节     3 字节      1 字节       1 字节           0-250 字节

    ESP-NOW v2.0：
    -------------------------------------------------------------------------------------
    |  元素 ID  |  长度  |  组织标识符  |  类型    |  保留  |更多数据 | 版本    |     正文     |
    -------------------------------------------------------------------------------------
                                                7~5 比特 | 1 比特 | 3~0 比特
       1 字节     1 字节     3 字节      1 字节            1 字节               0-250 字节

- 元素 ID：元素 ID 字段可用于指示特定于供应商的元素。

- 长度：长度是组织标识符、类型、版本和正文的总长度，最大值为 255。

- 组织标识符：组织标识符包含一个唯一标识符（比如 0x18fe34），为乐鑫指定的 MAC 地址的前三个字节。

- 类型：类型字段设置为 4，代表 ESP-NOW。

- 版本：版本字段设置为 ESP-NOW 的版本。

- 正文：正文包含实际要发送的 ESP-NOW 数据。

由于 ESP-NOW 是无连接的，因此 MAC 报头与标准帧略有不同。FrameControl 字段的 FromDS 和 ToDS 位均为 0。第一个地址字段用于配置目标地址。第二个地址字段用于配置源地址。第三个地址字段用于配置广播地址 (0xff:0xff:0xff:0xff:0xff:0xff)。

## 安全

ESP-NOW 采用 CCMP 方法保护供应商特定动作帧的安全，具体可参考 IEEE Std. 802.11-2012。Wi-Fi 设备维护一个初始主密钥 (PMK) 和若干本地主密钥 (LMKs，每个配对设备拥有一个 LMK)，长度均为 16 个字节。

> - PMK 可使用 AES-128 算法加密 LMK。请调用 `esp_now_set_pmk()` 设置 PMK。如果未设置 PMK，将使用默认 PMK。

> - LMK 可通过 CCMP 方法对供应商特定的动作帧进行加密。如果未设置配对设备的 LMK，则动作帧不进行加密。

目前，不支持加密组播供应商特定的动作帧。

## 初始化和反初始化

调用 `esp_now_init()` 初始化 ESP-NOW，调用 `esp_now_deinit()` 反初始化 ESP-NOW。ESP-NOW 数据必须在 Wi-Fi 启动后传输，因此建议在初始化 ESP-NOW 之前启动 Wi-Fi，并在反初始化 ESP-NOW 之后停止 Wi-Fi。

当调用 `esp_now_deinit()` 时，配对设备的所有信息都将被删除。

## 添加配对设备

在将数据发送到其他设备之前，请先调用 `esp_now_add_peer()` 将其添加到配对设备列表中。如果启用了加密，则必须设置 LMK。

ESP-NOW 数据可以从 Station 或 SoftAP 接口发送。确保在发送 ESP-NOW 数据之前已启用该接口。

配对设备的信道范围是从 0 ~ 14。如果信道设置为 0，数据将在当前信道上发送。否则，必须使用本地设备所在的通道。

对于接收设备，调用 `esp_now_add_peer()` 不是必需的。如果没有添加配对设备，只能接收广播包和不加密的单播包。如果需要接收加密的单播包，则必须添加配对设备并设置相同的 LMK。

配对设备的最大数量是 20，其中加密设备的数量不超过 17，默认值是 7。如果想要修改加密设备的数量，在 Wi-Fi menuconfig 设置 CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM。

## 发送 ESP-NOW 数据

调用 `esp_now_send()` 发送 ESP-NOW 数据，调用 `esp_now_register_send_cb()` 注册发送回调函数。如果 MAC 层成功接收到数据，则该函数将返回 ESP_NOW_SEND_SUCCESS 事件。否则，它将返回 ESP_NOW_SEND_FAIL。ESP-NOW 数据发送失败可能有几种原因，比如目标设备不存在、设备的信道不相同、动作帧在传输过程中丢失等。应用层并不一定可以总能接收到数据。如果需要，应用层可在接收 ESP-NOW 数据时发回一个应答 (ACK) 数据。如果接收 ACK 数据超时，则将重新传输 ESP-NOW 数据。可以为 ESP-NOW 数据设置序列号，从而删除重复的数据。

如果有大量 ESP-NOW 数据要发送，调用 `esp_now_send()` 时需注意单次发送的数据不能超过最大数据包长（v1.0 是 250 bytes， v2.0 是 1470 字节）。请注意，两个 ESP-NOW 数据包的发送间隔太短可能导致回调函数返回混乱。因此，建议在等到上一次回调函数返回 ACK 后再发送下一个 ESP-NOW 数据。发送回调函数从高优先级的 Wi-Fi 任务中运行。因此，不要在回调函数中执行冗长的操作。相反，将必要的数据发布到队列，并交给优先级较低的任务处理。

## 接收 ESP-NOW 数据

调用 `esp_now_register_recv_cb()` 注册接收回调函数。当接收 ESP-NOW 数据时，需要调用接收回调函数。接收回调函数也在 Wi-Fi 任务任务中运行。因此，不要在回调函数中执行冗长的操作。 相反，将必要的数据发布到队列，并交给优先级较低的任务处理。

## 配置 ESP-NOW 速率

调用 `esp_now_set_peer_rate_config()` 配置每个 peer 的 ESP-NOW 速率。请确保在配置速率之前添加 peer。此 API 应在 `esp_wifi_start()` 和 `esp_now_add_peer()` 之后调用。

## 配置 ESP-NOW 功耗参数

当且仅当 ESP32-S3 配置为 STA 模式时，允许其进行休眠。

进行休眠时，调用 `esp_now_set_wake_window()` 为 ESP-NOW 收包配置 Window。默认情况下 Window 为最大值，将允许一直收包。

如果对 ESP-NOW 进功耗管理，也需要调用 `esp_wifi_connectionless_module_set_wake_interval()`。

请参考 非连接模块功耗管理 获取更多信息。

## 应用示例

- wifi/espnow 演示了如何使用 ESP32-S3 的 Wi-Fi 的 ESPNOW 功能，包括启动 Wi-Fi、初始化 ESP-NOW、注册 ESP-NOW 发送或接收回调函数、添加 ESP-NOW 对等信息，以及在两台设备之间发送和接收 ESP-NOW 数据。

## API 参考

### Header File

- components/esp_wifi/include/esp_now.h

- This header file can be included with:

  >     #include "esp_now.h"

- This header file is a part of the API provided by the `esp_wifi` component. To declare that your component depends on `esp_wifi`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_wifi

  > or

  >     PRIV_REQUIRES esp_wifi

### Functions

esp_err_t esp_now_init(void)

  Initialize ESPNOW function.

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_INTERNAL : Internal error

esp_err_t esp_now_deinit(void)

  De-initialize ESPNOW function.

  返回:

    - ESP_OK : succeed

esp_err_t esp_now_get_version(uint32_t *version)

  Get the version of ESPNOW. Currently, ESPNOW supports two versions: v1.0 and v2.0.

             The v2.0 devices are capable of receiving packets from both v2.0 and v1.0 devices. In contrast, v1.0 devices can only receive packets from other v1.0 devices.
             However, v1.0 devices can receive v2.0 packets if the packet length is less than or equal to ESP_NOW_MAX_IE_DATA_LEN.
             For packets exceeding this length, the v1.0 devices will either truncate the data to the first ESP_NOW_MAX_IE_DATA_LEN bytes or discard the packet entirely.
             For detailed behavior, please refer to the documentation corresponding to the specific IDF version.

  参数:

    version -- ESPNOW version

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_ARG : invalid argument

esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t cb)

  Register callback function of receiving ESPNOW data.

  参数:

    cb -- callback function of receiving ESPNOW data

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_INTERNAL : internal error

esp_err_t esp_now_unregister_recv_cb(void)

  Unregister callback function of receiving ESPNOW data.

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

esp_err_t esp_now_register_send_cb(esp_now_send_cb_t cb)

  Register callback function of sending ESPNOW data.

  参数:

    cb -- callback function of sending ESPNOW data

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_INTERNAL : internal error

esp_err_t esp_now_unregister_send_cb(void)

  Unregister callback function of sending ESPNOW data.

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

esp_err_t esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len)

  Send ESPNOW data.

  Attention

    1. If peer_addr is not NULL, send data to the peer whose MAC address matches peer_addr

  Attention

    2. If peer_addr is NULL, send data to all of the peers that are added to the peer list

  Attention

    3. The maximum length of data must be less than ESP_NOW_MAX_DATA_LEN

  Attention

    4. The buffer pointed to by data argument does not need to be valid after esp_now_send returns

  参数:

    - peer_addr -- peer MAC address

    - data -- data to send

    - len -- length of data

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

    - ESP_ERR_ESPNOW_INTERNAL : internal error

    - ESP_ERR_ESPNOW_NO_MEM : out of memory, when this happens, you can delay a while before sending the next data

    - ESP_ERR_ESPNOW_NOT_FOUND : peer is not found

    - ESP_ERR_ESPNOW_IF : current Wi-Fi interface doesn't match that of peer

    - ESP_ERR_ESPNOW_CHAN: current Wi-Fi channel doesn't match that of peer

esp_err_t esp_now_add_peer(const esp_now_peer_info_t *peer)

  Add a peer to peer list.

  参数:

    peer -- peer information

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

    - ESP_ERR_ESPNOW_FULL : peer list is full

    - ESP_ERR_ESPNOW_NO_MEM : out of memory

    - ESP_ERR_ESPNOW_EXIST : peer has existed

esp_err_t esp_now_del_peer(const uint8_t *peer_addr)

  Delete a peer from peer list.

  参数:

    peer_addr -- peer MAC address

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

    - ESP_ERR_ESPNOW_NOT_FOUND : peer is not found

esp_err_t esp_now_mod_peer(const esp_now_peer_info_t *peer)

  Modify a peer.

  参数:

    peer -- peer information

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

    - ESP_ERR_ESPNOW_FULL : peer list is full

esp_err_t esp_now_set_peer_rate_config(const uint8_t *peer_addr, esp_now_rate_config_t *config)

  Set ESPNOW rate config for each peer.

  Attention

    1. This API should be called after esp_wifi_start() and esp_now_init().

  参数:

    - peer_addr -- peer MAC address

    - config -- rate config to be configured.

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

    - ESP_ERR_ESPNOW_INTERNAL : internal error

esp_err_t esp_now_get_peer(const uint8_t *peer_addr, esp_now_peer_info_t *peer)

  Get a peer whose MAC address matches peer_addr from peer list.

  参数:

    - peer_addr -- peer MAC address

    - peer -- peer information

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

    - ESP_ERR_ESPNOW_NOT_FOUND : peer is not found

esp_err_t esp_now_fetch_peer(bool from_head, esp_now_peer_info_t *peer)

  Fetch a peer from peer list. Only return the peer which address is unicast, for the multicast/broadcast address, the function will ignore and try to find the next in the peer list.

  参数:

    - from_head -- fetch from head of list or not

    - peer -- peer information

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

    - ESP_ERR_ESPNOW_NOT_FOUND : peer is not found

bool esp_now_is_peer_exist(const uint8_t *peer_addr)

  Peer exists or not.

  参数:

    peer_addr -- peer MAC address

  返回:

    - true : peer exists

    - false : peer not exists

esp_err_t esp_now_get_peer_num(esp_now_peer_num_t *num)

  Get the number of peers.

  参数:

    num -- number of peers

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

esp_err_t esp_now_set_pmk(const uint8_t *pmk)

  Set the primary master key.

  Attention

    1. primary master key is used to encrypt local master key

  参数:

    pmk -- primary master key

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

    - ESP_ERR_ESPNOW_ARG : invalid argument

esp_err_t esp_now_set_wake_window(uint16_t window)

  Set wake window for esp_now to wake up in interval unit.

  Attention

    1. This configuration could work at connected status. When ESP_WIFI_STA_DISCONNECTED_PM_ENABLE is enabled, this configuration could work at disconnected status.

  Attention

    2. Default value is the maximum.

  参数:

    window -- Milliseconds would the chip keep waked each interval, from 0 to 65535.

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized

esp_err_t esp_now_set_user_oui(uint8_t *oui)

  Set the OUI (Organization Identifier) in the vendor-specific element for ESPNOW.

  参数:

    oui -- The oui should occupy 3 bytes. If the oui is NULL, then use the default value (0x18fe34).

  返回:

    - ESP_OK : succeed

esp_err_t esp_now_get_user_oui(uint8_t *oui)

  Get the OUI (Organization Identifier) in the vendor-specific element for ESPNOW.

  参数:

    oui -- user configured OUI.

  返回:

    - ESP_OK : succeed

    - ESP_ERR_ESPNOW_ARG : invalid argument

esp_err_t esp_now_switch_channel_tx(esp_now_switch_channel_t *config)

  ESPNOW switch to a specific channel for a required duration, and send one ESPNOW data.

  参数:

    config -- ESPNOW switch channel relevant information

  返回:

    - ESP_OK : succeed

    - ESP_ERR_NO_MEM: failed to allocate memory

    - ESP_ERR_INVALID_ARG: the <channel, sec_channel> pair is invalid

    - ESP_FAIL: failed to send frame

esp_err_t esp_now_remain_on_channel(esp_now_remain_on_channel_t *config)

  ESPNOW remain on the target channel for required duration.

  参数:

    config -- ESPNOW remain on channel relevant information

  返回:

    - ESP_OK : succeed

    - ESP_ERR_NO_MEM: failed to allocate memory

    - ESP_ERR_INVALID_ARG: the <channel, sec_channel> pair is invalid

    - ESP_FAIL: failed to perform roc operation

### Structures

struct esp_now_peer_info

  ESPNOW peer information parameters.

  Public Members

  uint8_t peer_addr[ESP_NOW_ETH_ALEN]

    ESPNOW peer MAC address that is also the MAC address of station or softap

  uint8_t lmk[ESP_NOW_KEY_LEN]

    ESPNOW peer local master key that is used to encrypt data

  uint8_t channel

    Wi-Fi channel that peer uses to send/receive ESPNOW data. If the value is 0, use the current channel which station or softap is on. Otherwise, it must be set as the channel that station or softap is on.

  wifi_interface_t ifidx

    Wi-Fi interface that peer uses to send/receive ESPNOW data

  bool encrypt

    ESPNOW data that this peer sends/receives is encrypted or not

  void *priv

    ESPNOW peer private data

struct esp_now_peer_num

  Number of ESPNOW peers which exist currently.

  Public Members

  int total_num

    Total number of ESPNOW peers, maximum value is ESP_NOW_MAX_TOTAL_PEER_NUM

  int encrypt_num

    Number of encrypted ESPNOW peers, maximum value is ESP_NOW_MAX_ENCRYPT_PEER_NUM

struct esp_now_recv_info

  ESPNOW receive packet information.

  Public Members

  uint8_t *src_addr

    Source address of ESPNOW packet

  uint8_t *des_addr

    Destination address of ESPNOW packet

  wifi_pkt_rx_ctrl_t *rx_ctrl

    Rx control info of ESPNOW packet

struct esp_now_switch_channel_t

  ESPNOW switch channel information.

  Public Members

  wifi_action_tx_t type

    ACTION TX operation type

  uint8_t channel

    Channel on which to perform ESPNOW TX Operation

  wifi_second_chan_t sec_channel

    Secondary channel

  uint32_t wait_time_ms

    Duration to wait for on target channel

  uint8_t op_id

    Unique Identifier for operation provided by wifi driver

  uint8_t dest_mac[6]

    Destination MAC address

  uint16_t data_len

    Length of the appended Data

  uint8_t data[0]

    Appended Data payload

struct esp_now_remain_on_channel_t

  ESPNOW remain on channel information.

  Public Members

  wifi_roc_t type

    ROC operation type

  uint8_t channel

    Channel on which to perform ESPNOW ROC Operation

  wifi_second_chan_t sec_channel

    Secondary channel

  uint32_t wait_time_ms

    Duration to wait for on target channel

  uint8_t op_id

    ID of this specific ROC operation provided by wifi driver

### Macros

ESP_ERR_ESPNOW_BASE

  ESPNOW error number base.

ESP_ERR_ESPNOW_NOT_INIT

  ESPNOW is not initialized.

ESP_ERR_ESPNOW_ARG

  Invalid argument

ESP_ERR_ESPNOW_NO_MEM

  Out of memory

ESP_ERR_ESPNOW_FULL

  ESPNOW peer list is full

ESP_ERR_ESPNOW_NOT_FOUND

  ESPNOW peer is not found

ESP_ERR_ESPNOW_INTERNAL

  Internal error

ESP_ERR_ESPNOW_EXIST

  ESPNOW peer has existed

ESP_ERR_ESPNOW_IF

  Interface error

ESP_ERR_ESPNOW_CHAN

  Channel error

ESP_NOW_ETH_ALEN

  Length of ESPNOW peer MAC address

ESP_NOW_KEY_LEN

  Length of ESPNOW peer local master key

ESP_NOW_MAX_TOTAL_PEER_NUM

  Maximum number of ESPNOW total peers

ESP_NOW_MAX_ENCRYPT_PEER_NUM

  Maximum number of ESPNOW encrypted peers

ESP_NOW_MAX_IE_DATA_LEN

  Maximum data length in a vendor-specific element

ESP_NOW_MAX_DATA_LEN

  Maximum length of data sent in each ESPNOW transmission for v1.0

ESP_NOW_MAX_DATA_LEN_V2

  Maximum length of data sent in each ESPNOW transmission for v2.0

### Type Definitions

typedef struct esp_now_peer_info esp_now_peer_info_t

  ESPNOW peer information parameters.

typedef struct esp_now_peer_num esp_now_peer_num_t

  Number of ESPNOW peers which exist currently.

typedef struct esp_now_recv_info esp_now_recv_info_t

  ESPNOW receive packet information.

typedef wifi_tx_info_t esp_now_send_info_t

  ESPNOW sending packet information.

typedef wifi_tx_rate_config_t esp_now_rate_config_t

  ESPNOW rate config.

typedef void (*esp_now_recv_cb_t)(const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len)

  Callback function of receiving ESPNOW data.

  Attention

    esp_now_info is a local variable，it can only be used in the callback.

  Param esp_now_info:

    received ESPNOW packet information

  Param data:

    received data

  Param data_len:

    length of received data

typedef void (*esp_now_send_cb_t)(const esp_now_send_info_t *tx_info, esp_now_send_status_t status)

  Callback function of sending ESPNOW data.

  Param tx_info:

    Sending information for ESPNOW data

  Param status:

    status of sending ESPNOW data (succeed or fail). This is will be removed later, since the tx_info->tx_status also works.

### Enumerations

enum esp_now_send_status_t

  Status of sending ESPNOW data .

  Values:

  enumerator ESP_NOW_SEND_SUCCESS

    Send ESPNOW data successfully

  enumerator ESP_NOW_SEND_FAIL

    Send ESPNOW data fail

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。