完全指南:Peer 管理、加密傳輸與多設(shè)備實(shí)戰(zhàn))
Arduino-ESP32 ESP-NOW 通信庫(kù)完全指南Peer 管理、加密傳輸與多設(shè)備實(shí)戰(zhàn)【免費(fèi)下載鏈接】arduino-esp32Arduino core for the ESP32 family of SoCs項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32導(dǎo)讀ESP-NOW 是樂(lè)鑫Espressif為 ESP32 系列芯片設(shè)計(jì)的一種無(wú)連接connectionless通信協(xié)議它允許設(shè)備之間在**不需要接入點(diǎn)AP**的情況下直接進(jìn)行低功耗、低延遲、高吞吐的數(shù)據(jù)交換。本指南基于 Arduino-ESP32 倉(cāng)庫(kù)中內(nèi)置的ESP_NOW庫(kù)文檔原文系統(tǒng)講解ESP_NOW_Class與ESP_NOW_Peer兩大核心類(lèi)的全部 API并結(jié)合倉(cāng)庫(kù)源碼、示例工程與多設(shè)備驗(yàn)證測(cè)試給出可直接復(fù)制運(yùn)行的廣播、單播、加密與串口流式傳輸方案。讀完本文你將掌握如何在 ESP32 設(shè)備間搭建去中心化的點(diǎn)對(duì)點(diǎn)通信網(wǎng)絡(luò)并理解其底層實(shí)現(xiàn)原理。一、ESP-NOW 協(xié)議概述與適用場(chǎng)景ESP-NOW 是一種專(zhuān)為 ESP32 設(shè)備間通信設(shè)計(jì)的協(xié)議核心特點(diǎn)是無(wú)需 AP 中轉(zhuǎn)設(shè)備直接點(diǎn)對(duì)點(diǎn)通信適合局域網(wǎng)內(nèi)的直連場(chǎng)景低功耗、低延遲、高吞吐相比傳統(tǒng) Wi-Fi 連接開(kāi)銷(xiāo)更小、響應(yīng)更快應(yīng)用廣泛可用于智能燈、遙控設(shè)備、傳感器等物聯(lián)網(wǎng)場(chǎng)景。該庫(kù)為 ESP-NOW 通信提供了一套易用的接口涵蓋初始化、添加/移除 Peer、發(fā)送/接收數(shù)據(jù)包等完整流程。需要特別注意的是文檔原文 中的明確說(shuō)明本庫(kù)僅在 ESP32 系列芯片上測(cè)試過(guò)與 ESP8266 或其他非 ESP32 平臺(tái)的互操作性不作保證。從源碼看這一約束也有硬性實(shí)現(xiàn)依據(jù)ESP32_NOW.h 與 ESP32_NOW.cpp 均以CONFIG_ESP_HOSTED_ENABLED宏做編譯期防護(hù)——當(dāng)芯片只有 Hosted外掛 Wi-Fi 協(xié)處理器模式而缺乏原生 Wi-Fi 時(shí)編譯會(huì)直接發(fā)出ESP-NOW is only supported in SoCs with native Wi-Fi support警告。庫(kù)元數(shù)據(jù)library.properties也聲明architecturesesp32即僅面向 ESP32 平臺(tái)。二、ESP_NOW_Class全局管理類(lèi)ESP_NOW_Class是管理 ESP-NOW 通信的主類(lèi)倉(cāng)庫(kù)在 ESP32_NOW.h 中定義并默認(rèn)提供全局實(shí)例ESP_NOW見(jiàn) ESP32_NOW.h 與 ESP32_NOW.cpp。它繼承自 ArduinoPrint因此還支持流式寫(xiě)接口。2.1 begin —— 初始化 ESP-NOWbool begin(const uint8_t *pmk NULL);pmk可選參數(shù)若啟用加密傳入成對(duì)主密鑰Pairwise Master KeyPMK長(zhǎng)度為 16 字節(jié)返回值初始化成功返回true失敗返回false必須在調(diào)用其他任何 ESP-NOW 功能之前調(diào)用。源碼實(shí)現(xiàn)揭示了 begin 內(nèi)部的完整初始化鏈路ESP32_NOW.cpp若已初始化_esp_now_has_begun直接返回true冪等調(diào)用esp_wifi_start()啟動(dòng) Wi-Fi調(diào)用esp_now_get_version()獲取 ESP-NOW 版本號(hào)據(jù)此設(shè)置max_data_lenv1.0 為 250 字節(jié)ESP_NOW_MAX_DATA_LENv2.0 為 1470 字節(jié)ESP_NOW_MAX_DATA_LEN_V2調(diào)用esp_now_init()初始化協(xié)議棧為已登記的 Peer 統(tǒng)一設(shè)置 PHY 速率_esp_now_set_all_peers_rate()若傳入pmk調(diào)用esp_now_set_pmk(pmk)設(shè)置主密鑰注冊(cè)接收回調(diào)_esp_now_rx_cb與發(fā)送完成回調(diào)_esp_now_tx_cb。2.2 end —— 結(jié)束通信并釋放資源bool end();結(jié)束 ESP-NOW 通信并釋放庫(kù)占用的全部資源。從實(shí)現(xiàn)看ESP32_NOW.cppend()會(huì)先遍歷并移除所有已注冊(cè)的 Peer再調(diào)用esp_now_deinit()反初始化協(xié)議棧最后清空內(nèi)部 Peer 列表。2.3 查詢當(dāng)前 Peer 數(shù)量int getTotalPeerCount(); // 當(dāng)前已添加的 Peer 總數(shù) int getEncryptedPeerCount(); // 使用加密的 Peer 數(shù)量?jī)烧呔祷?Peer 數(shù)量出錯(cuò)時(shí)返回 -1。例如在 ESP-NOW 尚未初始化時(shí)實(shí)現(xiàn)會(huì)打印ESP-NOW not initialized并返回 -1ESP32_NOW.cpp。它們底層分別讀取esp_now_get_peer_num()返回結(jié)構(gòu)體中的total_num與encrypt_num字段。2.4 版本與最大數(shù)據(jù)長(zhǎng)度源碼補(bǔ)充 API文檔之外源碼還提供了兩個(gè)極具實(shí)用價(jià)值的查詢方法ESP32_NOW.hint getMaxDataLen() const; // 當(dāng)前固件支持的最大單包數(shù)據(jù)長(zhǎng)度 int getVersion() const; // 當(dāng)前 ESP-NOW 協(xié)議版本這兩個(gè)方法在未初始化時(shí)返回 -1并在初始化后分別返回 250/1470 與 1/2ESP32_NOW.cpp。所有官方示例在begin()之后都會(huì)打印Serial.printf(ESP-NOW version: %d, max data length: %d\n, ESP_NOW.getVersion(), ESP_NOW.getMaxDataLen());2.5 onNewPeer —— 新 Peer 數(shù)據(jù)回調(diào)當(dāng)收到來(lái)自未注冊(cè) Peer的數(shù)據(jù)時(shí)可通過(guò)onNewPeer注冊(cè)回調(diào)函數(shù)來(lái)處理void onNewPeer(void (*cb)(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg), void *arg);cb回調(diào)函數(shù)指針arg可選傳遞給回調(diào)的用戶自定義參數(shù)。回調(diào)函數(shù)簽名void cb(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg);info接收數(shù)據(jù)包的信息含源/目的 MAC 地址等data接收到的數(shù)據(jù)指針len接收數(shù)據(jù)的長(zhǎng)度arg用戶自定義參數(shù)。源碼層面onNewPeer只是將回調(diào)保存在靜態(tài)變量new_cb/new_arg中ESP32_NOW.cpp真正的分發(fā)邏輯在接收回調(diào)_esp_now_rx_cb中ESP32_NOW.cpp先判斷目的地址是否為廣播地址0xFF:FF:FF:FF:FF:FF若源地址不是已注冊(cè) Peer 且new_cb非空則調(diào)用new_cb并返回——這正是動(dòng)態(tài)發(fā)現(xiàn)新設(shè)備的機(jī)制否則在內(nèi)部 Peer 列表中查找源 MAC 對(duì)應(yīng)的ESP_NOW_Peer對(duì)象調(diào)用其onReceive(data, len, broadcast)虛方法。2.6 write —— 向所有 Peer 直接發(fā)送源碼補(bǔ)充 API由于ESP_NOW_Class繼承自Print它還提供了流式寫(xiě)接口ESP32_NOW.hsize_t write(const uint8_t *data, size_t len); // 向所有已注冊(cè) Peer 發(fā)送無(wú)需指定 MAC size_t write(uint8_t data); // 單字節(jié)重載write底層調(diào)用esp_now_send(nullptr, data, len)nullptr表示廣播給所有已登記 Peer超長(zhǎng)數(shù)據(jù)會(huì)被截?cái)嗟絤ax_data_lenESP32_NOW.cpp。配套的availableForWrite()返回當(dāng)前可寫(xiě)字節(jié)數(shù)。三、ESP_NOW_PeerPeer 設(shè)備類(lèi)ESP_NOW_Peer表示 ESP-NOW 網(wǎng)絡(luò)中的一個(gè)對(duì)端設(shè)備ESP32_NOW.h。它是一個(gè)抽象類(lèi)必須由子類(lèi)繼承并在子類(lèi)中實(shí)現(xiàn)onReceive與onSent兩個(gè)虛方法以正確處理連接與收發(fā)事件。3.1 構(gòu)造函數(shù)ESP_NOW_Peer(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk);mac_addr對(duì)端設(shè)備的 MAC 地址6 字節(jié)channel通信信道iface使用的 Wi-Fi 接口WIFI_IF_STA或WIFI_IF_APlmk可選若啟用加密傳入本地主密鑰Local Master KeyLMK。從 ESP32_NOW.cpp 可以看到構(gòu)造函數(shù)會(huì)保存 MAC、信道、接口并根據(jù)lmk是否為空設(shè)置encrypt標(biāo)志位同時(shí)設(shè)置默認(rèn) PHY 速率配置#define DEFAULT_ESPNOW_RATE_CONFIG { \ .phymode WIFI_PHY_MODE_11G, \ .rate WIFI_PHY_RATE_1M_L, \ .ersu false, \ .dcm false \ }默認(rèn)以 802.11g、1 Mbps 長(zhǎng)前導(dǎo)Long preamble速率通信可通過(guò)setRate()覆蓋。3.2 add / remove —— 注冊(cè)與注銷(xiāo) Peerbool add(); // 將對(duì)端添加到 ESP-NOW 網(wǎng)絡(luò)成功返回 true bool remove(); // 將對(duì)端從網(wǎng)絡(luò)移除成功返回 trueadd()內(nèi)部調(diào)用esp_now_add_peer()完成注冊(cè)ESP32_NOW.cpp。底層輔助函數(shù)_esp_now_add_peerESP32_NOW.cpp展示了關(guān)鍵細(xì)節(jié)若該 MAC 已是 Peer返回ESP_ERR_ESPNOW_EXIST填充esp_now_peer_info_t信道、接口、加密標(biāo)志與 LMK注冊(cè)成功后把ESP_NOW_Peer*存入靜態(tài)數(shù)組_esp_now_peers容量ESP_NOW_MAX_TOTAL_PEER_NUM若 ESP-NOW 已在運(yùn)行還會(huì)調(diào)用esp_now_set_peer_rate_config()應(yīng)用該 Peer 的 PHY 速率。remove()則調(diào)用esp_now_del_peer()并把內(nèi)部數(shù)組中對(duì)應(yīng)條目清空ESP32_NOW.cpp。3.3 send —— 發(fā)送數(shù)據(jù)size_t send(const uint8_t *data, int len);data待發(fā)送數(shù)據(jù)指針len數(shù)據(jù)長(zhǎng)度字節(jié)返回值實(shí)際發(fā)送的字節(jié)數(shù)出錯(cuò)返回 0。實(shí)現(xiàn)ESP32_NOW.cpp會(huì)先校驗(yàn) ESP-NOW 已初始化且 Peer 已添加否則報(bào)Peer not added.并返回 0然后通過(guò)ESP_NOW.getMaxDataLen()獲取上限并將超長(zhǎng)數(shù)據(jù)截?cái)嘧詈笳{(diào)用esp_now_send(mac, data, len)定向發(fā)送。3.4 addr —— MAC 地址讀寫(xiě)const uint8_t * addr() const; // 獲取對(duì)端 MAC 地址 void addr(const uint8_t *mac_addr); // 設(shè)置對(duì)端 MAC 地址注意源碼實(shí)現(xiàn)ESP32_NOW.cppaddr()的設(shè)置版本在 Peer 已添加且 ESP-NOW 已在運(yùn)行時(shí)會(huì)失敗返回false并提示請(qǐng)?jiān)谔砑?Peer 之前或啟動(dòng) ESP-NOW 之前調(diào)用 addr()。因此 MAC 地址應(yīng)盡量在構(gòu)造對(duì)象時(shí)確定。3.5 getChannel / setChannel —— 信道讀寫(xiě)uint8_t getChannel() const; // 獲取通信信道 void setChannel(uint8_t channel); // 設(shè)置通信信道setChannel()在 Peer 已添加時(shí)會(huì)調(diào)用esp_now_mod_peer()實(shí)時(shí)修改底層 Peer 信息ESP32_NOW.cpp未運(yùn)行時(shí)則僅更新內(nèi)部字段。3.6 getInterface / setInterface —— Wi-Fi 接口讀寫(xiě)wifi_interface_t getInterface() const; // 獲取 Wi-Fi 接口 void setInterface(wifi_interface_t iface); // 設(shè)置 Wi-Fi 接口WIFI_IF_STA / WIFI_IF_AP行為與信道讀寫(xiě)一致已運(yùn)行時(shí)通過(guò)esp_now_mod_peer()實(shí)時(shí)生效ESP32_NOW.cpp。3.7 速率配置 setRate / getRate源碼補(bǔ)充 APIbool setRate(const esp_now_rate_config_t *rate_config); esp_now_rate_config_t getRate() const;setRate()用于配置 Peer 的 PHY 速率傳nullptr恢復(fù)默認(rèn)配置ESP32_NOW.cpp。限制只能在 Peer 添加之前或 ESP-NOW 啟動(dòng)之前調(diào)用否則返回false并打印錯(cuò)誤提示——這與addr()的行為一致屬于運(yùn)行期不可變配置項(xiàng)。3.8 isEncrypted / setKey —— 加密狀態(tài)bool isEncrypted() const; // 是否啟用加密 void setKey(const uint8_t *lmk); // 設(shè)置本地主密鑰LMKsetKey()傳入非空 LMK 即啟用加密傳入nullptr則關(guān)閉Peer 已添加時(shí)通過(guò)esp_now_mod_peer()實(shí)時(shí)更新ESP32_NOW.cpp。3.9 onReceive / onSent —— 子類(lèi)必須實(shí)現(xiàn)的虛方法// 處理來(lái)自該 Peer 的接收數(shù)據(jù) void onReceive(const uint8_t *data, int len, bool broadcast); // 處理向該 Peer 發(fā)送數(shù)據(jù)的完成事件 void onSent(bool success);onReceive參數(shù)data為數(shù)據(jù)指針len為長(zhǎng)度broadcast為true表示廣播消息、false表示單播onSent參數(shù)success為true表示發(fā)送成功。基類(lèi)提供了打印日志的默認(rèn)實(shí)現(xiàn)ESP32_NOW.h上層子類(lèi)必須按需重寫(xiě)。發(fā)送完成回調(diào)_esp_now_tx_cb會(huì)根據(jù)目標(biāo) MAC 在 Peer 列表中查找對(duì)應(yīng)對(duì)象并觸發(fā)其onSentESP32_NOW.cpp。3.10 operator bool源碼補(bǔ)充 APIESP_NOW_Peer還重載了operator bool()ESP32_NOW.cpp返回內(nèi)部added標(biāo)志可直接用if (peer)判斷該 Peer 是否已成功注冊(cè)。四、加密機(jī)制PMK 與 LMK倉(cāng)庫(kù)的 Network 示例ESP_NOW_Network.ino頭部注釋對(duì) ESP-NOW 加密機(jī)制做了權(quán)威說(shuō)明現(xiàn)歸納如下ESP-NOW 采用CCMP 加密方法IEEE Std. 802.11-2012保護(hù)廠商自定義 Action 幀設(shè)備維護(hù)一個(gè)PMKPrimary Master Key和若干LMKLocal Master Key兩者長(zhǎng)度均需為16 字節(jié)PMK 用于以 AES-128 算法加密 LMK。若未設(shè)置 PMK則使用默認(rèn) PMKbegin(pmk)傳入的正是該密鑰配對(duì)設(shè)備的 LMK 用于以 CCMP 方法加密數(shù)據(jù)幀。每臺(tái)設(shè)備最多支持 6 個(gè)不同的 LMK若未設(shè)置 LMK則幀不加密廣播組播幀不支持加密因此以廣播地址注冊(cè)的 Peer 必須傳nullptr作為 LMKPMK 必須全網(wǎng)一致LMK 只需在配對(duì)設(shè)備之間一致。Network 示例中的密鑰定義可供參考#define ESPNOW_EXAMPLE_PMK pmk1234567890123 #define ESPNOW_EXAMPLE_LMK lmk1234567890123 // 使用方式ESP_NOW.begin((const uint8_t *)ESPNOW_EXAMPLE_PMK);五、實(shí)戰(zhàn)示例從廣播到組網(wǎng)倉(cāng)庫(kù)libraries/ESP_NOW/examples/下提供了 4 個(gè)示例ESP_NOW_Broadcast_Master、ESP_NOW_Broadcast_Slave、ESP_NOW_Serial、ESP_NOW_Network其中前三個(gè)由文檔直接引用。5.1 廣播主設(shè)備Broadcast Master示例文件ESP_NOW_Broadcast_Master.ino核心思路創(chuàng)建一個(gè)以廣播地址0xFF:FF:FF:FF:FF:FF為目標(biāo)的 Peer向網(wǎng)絡(luò)內(nèi)所有設(shè)備廣播消息。全局常量ESP_NOW.BROADCAST_ADDR即廣播地址ESP32_NOW.h。#include Arduino.h #include ESP32_NOW.h #include WiFi.h #include esp_mac.h // MAC2STR / MACSTR 宏 #define ESPNOW_WIFI_CHANNEL 6 // 必須繼承 ESP_NOW_Peer class ESP_NOW_Broadcast_Peer : public ESP_NOW_Peer { public: ESP_NOW_Broadcast_Peer(uint8_t channel, wifi_interface_t iface, const uint8_t *lmk) : ESP_NOW_Peer(ESP_NOW.BROADCAST_ADDR, channel, iface, lmk) {} ~ESP_NOW_Broadcast_Peer() { remove(); } bool begin() { if (!ESP_NOW.begin() || !add()) { log_e(Failed to initialize ESP-NOW or register the broadcast peer); return false; } return true; } bool send_message(const uint8_t *data, size_t len) { if (!send(data, len)) { log_e(Failed to broadcast message); return false; } return true; } }; ESP_NOW_Broadcast_Peer broadcast_peer(ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, nullptr); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL); while (!WiFi.STA.started()) delay(100); if (!broadcast_peer.begin()) { Serial.println(Failed to initialize broadcast peer); delay(5000); ESP.restart(); } Serial.printf(ESP-NOW version: %d, max data length: %d\n, ESP_NOW.getVersion(), ESP_NOW.getMaxDataLen()); } void loop() { char data[32]; static uint32_t msg_count 0; snprintf(data, sizeof(data), Hello, World! #% PRIu32, msg_count); broadcast_peer.send_message((uint8_t *)data, sizeof(data)); delay(5000); }要點(diǎn)Wi-Fi 需先設(shè)為 STA 模式并固定信道WiFi.setChannel(ESPNOW_WIFI_CHANNEL)廣播 Peer 使用WIFI_IF_STA接口且 LMK 傳nullptr廣播幀不支持加密。5.2 廣播從設(shè)備Broadcast Slave示例文件ESP_NOW_Broadcast_Slave.ino核心思路從設(shè)備不預(yù)先注冊(cè)任何 Peer而是借助ESP_NOW.onNewPeer回調(diào)動(dòng)態(tài)發(fā)現(xiàn)并注冊(cè)主設(shè)備class ESP_NOW_Peer_Class : public ESP_NOW_Peer { public: ESP_NOW_Peer_Class(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk) : ESP_NOW_Peer(mac_addr, channel, iface, lmk) {} bool add_peer() { return add(); } // 重寫(xiě)接收回調(diào) void onReceive(const uint8_t *data, size_t len, bool broadcast) { Serial.printf(Received a message from master MACSTR (%s)\n, MAC2STR(addr()), broadcast ? broadcast : unicast); Serial.printf( Message: %s\n, (char *)data); } }; std::vectorESP_NOW_Peer_Class * masters; // 未知設(shè)備發(fā)來(lái)消息時(shí)回調(diào) void register_new_master(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) { if (memcmp(info-des_addr, ESP_NOW.BROADCAST_ADDR, 6) 0) { Serial.printf(Unknown peer MACSTR sent a broadcast message\n, MAC2STR(info-src_addr)); ESP_NOW_Peer_Class *new_master new ESP_NOW_Peer_Class( info-src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, nullptr); if (!new_master-add_peer()) { delete new_master; return; } masters.push_back(new_master); } } void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.setChannel(ESPNOW_WIFI_CHANNEL); while (!WiFi.STA.started()) delay(100); if (!ESP_NOW.begin()) { /* 處理失敗 */ } ESP_NOW.onNewPeer(register_new_master, nullptr); Serial.println(Setup complete. Waiting for a master to broadcast a message...); }示例注釋特別提醒masters容器應(yīng)存儲(chǔ)指針而非對(duì)象以避免 vector 重分配時(shí)產(chǎn)生懸垂指針。onReceive中的broadcast參數(shù)在底層由_esp_now_rx_cb通過(guò)比較目的地址與BROADCAST_ADDR計(jì)算得出ESP32_NOW.cpp。5.3 串口流式傳輸ESP_NOW_Serial示例文件ESP_NOW_Serial.ino配套類(lèi)見(jiàn) ESP32_NOW_Serial.h 與 ESP32_NOW_Serial.cpp。ESP_NOW_Serial_Class同時(shí)繼承Stream與ESP_NOW_Peer把 ESP-NOW 通道偽裝成一個(gè)串口兩個(gè) ESP32 之間可以像操作Serial一樣互相收發(fā)數(shù)據(jù)。示例采用單播unicast模式并指出關(guān)鍵事實(shí)不同接口使用不同的 MAC 地址設(shè)備可分別處于 AP 或 Station 模式唯一要求是處于同一 Wi-Fi 信道。#include Arduino.h #include ESP32_NOW_Serial.h #include MacAddress.h #include WiFi.h #include esp_wifi.h #define ESPNOW_WIFI_MODE_STATION 1 #define ESPNOW_WIFI_CHANNEL 1 #if ESPNOW_WIFI_MODE_STATION #define ESPNOW_WIFI_MODE WIFI_STA #define ESPNOW_WIFI_IF WIFI_IF_STA #else #define ESPNOW_WIFI_MODE WIFI_AP #define ESPNOW_WIFI_IF WIFI_IF_AP #endif // 對(duì)端設(shè)備 MAC 地址按實(shí)際情況修改 const MacAddress peer_mac({0xF4, 0x12, 0xFA, 0x40, 0x64, 0x4C}); ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, ESPNOW_WIFI_IF); void setup() { Serial.begin(115200); WiFi.mode(ESPNOW_WIFI_MODE); WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); while (!(WiFi.STA.started() || WiFi.AP.started())) delay(100); NowSerial.begin(115200); Serial.printf(ESP-NOW version: %d, max data length: %d\n, ESP_NOW.getVersion(), ESP_NOW.getMaxDataLen()); } void loop() { while (NowSerial.available()) Serial.write(NowSerial.read()); // 收 while (Serial.available() NowSerial.availableForWrite()) { // 發(fā) if (NowSerial.write(Serial.read()) 0) break; } delay(1); }實(shí)現(xiàn)層面的亮點(diǎn)ESP32_NOW_Serial.cpp發(fā)送緩沖使用 FreeRTOS RingbufferxRingbufferCreatewrite()將數(shù)據(jù)壓入環(huán)形緩沖后由發(fā)送回調(diào)驅(qū)動(dòng)發(fā)送接收緩沖使用 FreeRTOS QueuexQueueCreateonReceive逐字節(jié)入隊(duì)available()/read()/peek()對(duì)應(yīng)隊(duì)列操作自動(dòng)分包與超長(zhǎng)截?cái)郼heckForTxData()每次最多取ESP_NOW.getMaxDataLen()字節(jié)天然適配 v1250B與 v21470B兩種協(xié)議版本且begin()會(huì)按版本自動(dòng)設(shè)置緩沖大小v2 為 4096v1 為 1024均容納約 3-4 個(gè)包失敗重傳onSent(false)時(shí)最多重傳 5 次超過(guò)后丟棄該包若構(gòu)造時(shí)remove_on_fail為true則直接移除 Peer。5.4 多設(shè)備自組網(wǎng)ESP_NOW_Network示例文件ESP_NOW_Network.ino這是最復(fù)雜的示例演示如何構(gòu)建一個(gè)自動(dòng)選主的自組織網(wǎng)絡(luò)流程如下每個(gè)設(shè)備根據(jù)自身 MAC 地址后 3 字節(jié)生成一個(gè)priority設(shè)備通過(guò)廣播地址周期性廣播自己的優(yōu)先級(jí)各設(shè)備監(jiān)聽(tīng)廣播并登記其他設(shè)備的優(yōu)先級(jí)借助onNewPeer回調(diào)動(dòng)態(tài)new出 Peer全部 Peer 就緒后優(yōu)先級(jí)最高的設(shè)備自動(dòng)成為 master其余為 slaveslave 每 5 秒向 master 上報(bào)隨機(jī)數(shù)據(jù)并每隔REPORT_INTERVAL條消息向其他 slave 發(fā)送Hello!master 計(jì)算收到的數(shù)據(jù)平均值定期回傳給所有 slave。其中esp_now_data_t結(jié)構(gòu)體使用__attribute__((packed))確保內(nèi)存緊湊、無(wú)填充字節(jié)并給出了 v1/v2 的負(fù)載上限參考typedef struct { uint32_t count; uint32_t priority; uint32_t data; bool ready; char str[7]; } __attribute__((packed)) esp_now_data_t;該示例同時(shí)演示了 PMK/LMK 的實(shí)際用法ESP_NOW.begin((const uint8_t *)ESPNOW_EXAMPLE_PMK)設(shè)置全網(wǎng)主密鑰Peer 構(gòu)造時(shí)傳入ESPNOW_EXAMPLE_LMK啟用加密廣播 Peer 除外。六、多設(shè)備驗(yàn)證測(cè)試九階段全面覆蓋倉(cāng)庫(kù)在 tests/validation/esp_now/test_esp_now.py 提供了雙設(shè)備master/slave的自動(dòng)化驗(yàn)證腳本從測(cè)試視角再次印證了本文所有 API 的行為約定階段覆蓋內(nèi)容1初始化、未 begin 時(shí)的邊界行為getVersion/getMaxDataLen/getTotalPeerCount均返回 -1、master 廣播觸發(fā)onNewPeer2slave 廣播 broadcast標(biāo)志位驗(yàn)證3-4雙向單播master→slave、slave→master5Peer 管理總數(shù)/加密數(shù)統(tǒng)計(jì)、addr()讀寫(xiě)、setRate()邊界已添加時(shí)調(diào)用必須失敗、setKey后加密數(shù)變化6加密單播7最大長(zhǎng)度負(fù)載嚴(yán)格斷言收發(fā)字節(jié)數(shù)等于max_data_len即 250 或 14708Peer 移除/重新添加operator bool由 true→false→true總數(shù) 2→1→29end()/begin()生命周期 ESP_NOW.write()向所有 Peer 發(fā)送此外每個(gè)示例目錄下的ci.yml都聲明了requires: CONFIG_SOC_WIFI_SUPPORTEDy即 CI 僅在支持 Wi-Fi 的 SoC 上運(yùn)行這些示例。七、使用注意事項(xiàng)與最佳實(shí)踐綜合文檔、源碼與示例總結(jié)以下實(shí)戰(zhàn)要點(diǎn)芯片限制僅 ESP32 系列原生 Wi-Fi SoC受支持Hosted 模式芯片編譯時(shí)會(huì)直接告警見(jiàn) ESP32_NOW.h必須begin()先行所有查詢類(lèi) APIgetVersion、getMaxDataLen、getTotalPeerCount等在未初始化時(shí)返回 -1發(fā)送類(lèi) API 返回 0固定信道所有設(shè)備必須處于同一 Wi-Fi 信道示例統(tǒng)一使用WiFi.setChannel()且 Peer 的信道要與實(shí)際一致繼承而非直接用ESP_NOW_Peer是抽象類(lèi)必須子類(lèi)化并實(shí)現(xiàn)onReceive/onSentadd/remove/send是protected方法需通過(guò)子類(lèi)公開(kāi)包裝見(jiàn) ESP32_NOW.h運(yùn)行期不可變項(xiàng)addr()設(shè)置與setRate()必須在 Peer 添加前或 ESP-NOW 啟動(dòng)前調(diào)用否則失敗廣播不加密廣播 Peer 的 LMK 必須為nullptrPMK 全網(wǎng)一致、LMK 僅需配對(duì)設(shè)備一致兩者均為 16 字節(jié)負(fù)載上限v1.0 單包最大 250 字節(jié)、v2.0 最大 1470 字節(jié)超出部分會(huì)被send()/write()自動(dòng)截?cái)鄤?wù)必用ESP_NOW.getMaxDataLen()動(dòng)態(tài)查詢數(shù)據(jù)包結(jié)構(gòu)推薦使用__attribute__((packed))結(jié)構(gòu)體承載業(yè)務(wù)字段見(jiàn) Network 示例避免跨設(shè)備字節(jié)對(duì)齊不一致。結(jié)語(yǔ)從 API 文檔 到 庫(kù)源碼從四個(gè)可直接運(yùn)行的示例到覆蓋九個(gè)階段的自動(dòng)化測(cè)試Arduino-ESP32 的 ESP-NOW 庫(kù)提供了從單播、廣播、加密到串口流式與自組網(wǎng)的完整能力。無(wú)論是制作一對(duì)遙控器、一組傳感器節(jié)點(diǎn)還是構(gòu)建去中心化的多設(shè)備網(wǎng)絡(luò)這套 API 都能讓你以最少的樣板代碼實(shí)現(xiàn)設(shè)備間的直接通信。【免費(fèi)下載鏈接】arduino-esp32Arduino core for the ESP32 family of SoCs項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考