diff --git a/components/esp32/component.mk b/components/esp32/component.mk index 2ed898052..e66cae3bc 100644 --- a/components/esp32/component.mk +++ b/components/esp32/component.mk @@ -5,7 +5,7 @@ COMPONENT_SRCDIRS := . hwcrypto LIBS ?= ifndef CONFIG_NO_BLOBS -LIBS += core rtc net80211 pp wpa smartconfig coexist wps wpa2 phy +LIBS += core rtc net80211 pp wpa smartconfig coexist wps wpa2 espnow phy endif #Linker scripts used to link the final application. diff --git a/components/esp32/include/esp_now.h b/components/esp32/include/esp_now.h new file mode 100644 index 000000000..497d73776 --- /dev/null +++ b/components/esp32/include/esp_now.h @@ -0,0 +1,315 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_NOW_H__ +#define __ESP_NOW_H__ + +#include +#include "esp_err.h" +#include "esp_wifi_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup WiFi_APIs WiFi Related APIs + * @brief WiFi APIs + */ + +/** @addtogroup WiFi_APIs + * @{ + */ + +/** \defgroup ESPNOW_APIs ESPNOW APIs + * @brief ESP32 ESPNOW APIs + * + */ + +/** @addtogroup ESPNOW_APIs + * @{ + */ + +#define ESP_ERR_ESPNOW_BASE (ESP_ERR_WIFI_BASE + 101) /*!< ESPNOW error number base. */ +#define ESP_ERR_ESPNOW_NOT_INIT (ESP_ERR_ESPNOW_BASE) /*!< ESPNOW is not initialized. */ +#define ESP_ERR_ESPNOW_ARG (ESP_ERR_ESPNOW_BASE + 1) /*!< Invalid argument */ +#define ESP_ERR_ESPNOW_NO_MEM (ESP_ERR_ESPNOW_BASE + 2) /*!< Out of memory */ +#define ESP_ERR_ESPNOW_FULL (ESP_ERR_ESPNOW_BASE + 3) /*!< ESPNOW peer list is full */ +#define ESP_ERR_ESPNOW_NOT_FOUND (ESP_ERR_ESPNOW_BASE + 4) /*!< ESPNOW peer is not found */ +#define ESP_ERR_ESPNOW_INTERNAL (ESP_ERR_ESPNOW_BASE + 5) /*!< Internal error */ +#define ESP_ERR_ESPNOW_EXIST (ESP_ERR_ESPNOW_BASE + 6) /*!< ESPNOW peer has existed */ + +#define ESP_NOW_ETH_ALEN 6 /*!< Length of ESPNOW peer MAC address */ +#define ESP_NOW_KEY_LEN 16 /*!< Length of ESPNOW peer local master key */ + +#define ESP_NOW_MAX_TOTAL_PEER_NUM 20 /*!< Maximum number of ESPNOW total peers */ +#define ESP_NOW_MAX_ENCRYPT_PEER_NUM 6 /*!< Maximum number of ESPNOW encrypted peers */ + +#define ESP_NOW_MAX_DATA_LEN 250 /*!< Maximum length of ESPNOW data which is sent very time */ + +/** + * @brief Status of sending ESPNOW data . + */ +typedef enum { + ESP_NOW_SEND_SUCCESS = 0, /**< Send ESPNOW data successfully */ + ESP_NOW_SEND_FAIL, /**< Send ESPNOW data fail */ +} esp_now_send_status_t; + +/** + * @brief ESPNOW peer information parameters. + */ +typedef struct esp_now_peer_info { + 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 */ +} esp_now_peer_info_t; + +/** + * @brief Number of ESPNOW peers which exist currently. + */ +typedef struct esp_now_peer_num { + 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 */ +} esp_now_peer_num_t; + +/** + * @brief Callback function of receiving ESPNOW data + * @param mac_addr peer MAC address + * @param data received data + * @param data_len length of received data + */ +typedef void (*esp_now_recv_cb_t)(const uint8_t *mac_addr, const uint8_t *data, int data_len); + +/** + * @brief Callback function of sending ESPNOW data + * @param mac_addr peer MAC address + * @param status status of sending ESPNOW data (succeed or fail) + */ +typedef void (*esp_now_send_cb_t)(const uint8_t *mac_addr, esp_now_send_status_t status); + +/** + * @brief Initialize ESPNOW function + * + * @return + * - ESP_OK : succeed + * - ESP_ERR_ESPNOW_INTERNAL : Internal error + */ +esp_err_t esp_now_init(void); + +/** + * @brief De-initialize ESPNOW function + * + * @return + * - ESP_OK : succeed + */ +esp_err_t esp_now_deinit(void); + +/** + * @brief Get the version of ESPNOW + * + * @param version ESPNOW version + * + * @return + * - ESP_OK : succeed + * - ESP_ERR_ESPNOW_ARG : invalid argument + */ +esp_err_t esp_now_get_version(uint32_t *version); + +/** + * @brief Register callback function of receiving ESPNOW data + * + * @param cb callback function of receiving ESPNOW data + * + * @return + * - ESP_OK : succeed + * - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized + * - ESP_ERR_ESPNOW_INTERNAL : internal error + */ +esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t cb); + +/** + * @brief Unregister callback function of receiving ESPNOW data + * + * @return + * - ESP_OK : succeed + * - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized + */ +esp_err_t esp_now_unregister_recv_cb(void); + +/** + * @brief Register callback function of sending ESPNOW data + * + * @param cb callback function of sending ESPNOW data + * + * @return + * - ESP_OK : succeed + * - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized + * - ESP_ERR_ESPNOW_INTERNAL : internal error + */ +esp_err_t esp_now_register_send_cb(esp_now_send_cb_t cb); + +/** + * @brief Unregister callback function of sending ESPNOW data + * + * @return + * - ESP_OK : succeed + * - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized + */ +esp_err_t esp_now_unregister_send_cb(void); + +/** + * @brief 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 + * + * @param peer_addr peer MAC address + * @param data data to send + * @param len length of data + * + * @return + * - 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 + * - ESP_ERR_ESPNOW_NOT_FOUND : peer is not found + */ +esp_err_t esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len); + +/** + * @brief Add a peer to peer list + * + * @param peer peer information + * + * @return + * - 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_add_peer(const esp_now_peer_info_t *peer); + +/** + * @brief Delete a peer from peer list + * + * @param peer_addr peer MAC address + * + * @return + * - 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_del_peer(const uint8_t *peer_addr); + +/** + * @brief Modify a peer + * + * @param peer peer information + * + * @return + * - 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_mod_peer(const esp_now_peer_info_t *peer); + +/** + * @brief Get a peer whose MAC address matches peer_addr from peer list + * + * @param peer_addr peer MAC address + * @param peer peer information + * + * @return + * - 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_get_peer(const uint8_t *peer_addr, esp_now_peer_info_t *peer); + +/** + * @brief Fetch a peer from peer list + * + * @param from_head fetch from head of list or not + * @param peer peer information + * + * @return + * - 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); + +/** + * @brief Peer exists or not + * + * @param peer_addr peer MAC address + * + * @return + * - true : peer exists + * - false : peer not exists + */ +bool esp_now_is_peer_exist(const uint8_t *peer_addr); + +/** + * @brief Get the number of peers + * + * @param num number of peers + * + * @return + * - ESP_OK : succeed + * - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized + * - ESP_ERR_ESPNOW_ARG : invalid argument + */ +esp_err_t esp_now_get_peer_num(esp_now_peer_num_t *num); + +/** + * @brief Set the primary master key + * + * @param pmk primary master key + * + * @attention 1. primary master key is used to encrypt local master key + * + * @return + * - 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); + +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __ESP_NOW_H__ */ diff --git a/components/esp32/lib b/components/esp32/lib index d8119f2d8..3fe7f3a0a 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit d8119f2d8b121f7bca072b3cc8e2e3a26b0b7d44 +Subproject commit 3fe7f3a0a5f570b22182063fcc6d7dad6875dfbd diff --git a/docs/Doxyfile b/docs/Doxyfile index 628db925a..dee628ef4 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -27,6 +27,7 @@ INPUT = \ ../components/esp32/include/esp_wifi.h \ ../components/esp32/include/esp_wifi_crypto_types.h \ ../components/esp32/include/esp_smartconfig.h \ + ../components/esp32/include/esp_now.h \ ## Bluetooth - API Reference ## Controller && VHCI ../components/bt/include/bt.h \ diff --git a/docs/api-reference/wifi/esp_now.rst b/docs/api-reference/wifi/esp_now.rst new file mode 100644 index 000000000..207bbf954 --- /dev/null +++ b/docs/api-reference/wifi/esp_now.rst @@ -0,0 +1,103 @@ +ESP-NOW +====== + +Overview +-------- + +ESP-NOW is a kind of connectionless WiFi communication protocol which is defined by Espressif. In ESP-NOW, application data is +encapsulated in vendor-specific action frame and then transmitted from one WiFi device to another without connection. +CTR with CBC-MAC Protocol(CCMP) is used to protect the action frame for security. ESP-NOW is widely used in smart light, remote +controlling, sensor, etc. + +Frame Format +------------ + +ESP-NOW uses vendor-specific action frame to transmit ESP-NOW data. The format of vendor-specific action frame is as follows: + +.. highlight:: none + +:: + +---------------------------------------------------------------------------------------- +| MAC Header | Category Code | Organization Identifier | Vendor Specific Content | FCS | +---------------------------------------------------------------------------------------- + 1 byte 3 bytes 7~255 bytes + +- Category Code: The Category field is set to the value(127) indicating the vendor-specific category. +- Organization Identifier: The Organization Identifier contains a unique identifier(0x18fe34) which is the first three bytes + of MAC address applied by Espressif. +- Vendor Specific Content: The Vendor Specific Content contains vendor-specific field as follows: + +.. highlight:: none + +:: + +------------------------------------------------------------------------------- +| Element ID | Length | Organization Identifier | Type | Version | Body | +------------------------------------------------------------------------------- + 1 byte 1 byte 3 bytes 1 byte 1 byte 0~250 bytes + +- Element ID: The Element ID field is set to the value(221) indicating the vendor-specific element. +- Length: The length is the total length of Organization Identifier, Type, Version and Body. +- Organization Identifier: The Organization Identifier contains a unique identifier(0x18fe34) which is the first three bytes + of MAC address applied by Espressif. +- Type: The Type field is set to the value(4) indicating ESP-NOW. +- Version: The Version field is set to the version of ESP-NOW. +- Body: The Body contains the ESP-NOW data. + +As ESP-NOW is connectionless, the MAC header is a little different from that of standard frames. The FromDS and ToDS bits of +FrameControl field are both 0. The first address field is set to the destination address. The second address field is set to +the source address. The third address field is set to broadcast address(0xff:0xff:0xff:0xff:0xff:0xff). + +Security +-------- + +ESP-NOW use CCMP method which can be referenced in IEEE Std. 802.11-2012 to protect the vendor-specific action frame. The WiFi +device maintains a Primary Master Key(PMK) and several Local Master Keys(LMK). The lengths of them are 16 bytes. PMK is used +to encrypt LMK with AES-128 algorithm. Call ``esp_now_set_pmk()`` to set PMK. If PMK is not set, a default PMK will be used. +If LMK of the paired device is set, it will be used to encrypt the vendor-specific action frame with CCMP method. The maximum +number of different LMKs is six. Do not support encrypting multicast vendor-specific action frame. + +Initialization and De-initialization +------------------------------------ + +Call ``esp_now_init()`` to initialize ESP-NOW and ``esp_now_deinit()`` to de-initialize ESP-NOW. ESP-NOW data must be transmitted +after WiFi is started, so it is recommended to start WiFi before initializing ESP-NOW and stop WiFi after de-initializing ESP-NOW. +When ``esp_now_deinit()`` is called, all of the information of paired devices will be deleted. + +Add Paired Device +----------------- + +Before sending data to other device, call ``esp_now_add_peer()`` to add it to the paired device list first. The maximum number of +paired devices is twenty. If security is enabled, the LMK must be set. ESP-NOW data can be sent from station or softap interface. +Make sure that the interface is enabled before sending ESP-NOW data. A device with broadcast MAC address must be added before +sending broadcast data. The range of the channel of paired device is from 0 to 14. If the channel is set to 0, data will be sent +on the current channel. Otherwise, the channel must be set as the channel that the local device is on. + +Send ESP-NOW Data +----------------- + +Call ``esp_now_send()`` to send ESP-NOW data and ``esp_now_register_send_cb`` to register sending callback function. It will return +`ESP_NOW_SEND_SUCCESS` in sending callback function if the data is received successfully on MAC layer. Otherwise, it will return +`ESP_NOW_SEND_FAIL`. There are several reasons failing to send ESP-NOW data, for example, the destination device doesn't exist, the +channels of the devices are not the same, the action frame is lost when transmiting on the air, etc. It is not guaranteed that +application layer can receive the data. If necessary, send back ack data when receiving ESP-NOW data. If receiving ack data timeout +happens, retransmit the ESP-NOW data. A sequence number can also be assigned to ESP-NOW data to drop the duplicated data. + +If there is a lot of ESP-NOW data to send, call ``esp_now_send()`` to send less than or equal to 250 bytes of data once a time. +Note that too short interval between sending two ESP-NOW datas may lead to disorder of sending callback function. So, it is +recommended that sending the next ESP-NOW data after the sending callback function of previous sending has returned. The sending +callback function runs from a high-priority WiFi task. So, do not do lengthy operations in the callback function. Instead, post +necessary data to a queue and handle it from a lower priority task. + +Receiving ESP-NOW Data +---------------------- + +Call ``esp_now_register_recv_cb`` to register receiving callback function. When receiving ESP-NOW data, receiving callback function +is called. The receiving callback function also runs from WiFi task. So, do not do lengthy operations in the callback function. +Instead, post necessary data to a queue and handle it from a lower priority task. + +API Reference +------------- + +.. include:: /_build/inc/esp_now.inc diff --git a/docs/api-reference/wifi/index.rst b/docs/api-reference/wifi/index.rst index 97165df6b..1553d3597 100644 --- a/docs/api-reference/wifi/index.rst +++ b/docs/api-reference/wifi/index.rst @@ -6,6 +6,7 @@ Wi-Fi API Wi-Fi Smart Config + ESPNOW Example code for this API section is provided in :example:`wifi` directory of ESP-IDF examples.