ESP-NOW 是乐鑫定义的一种无线通信协议,能够在无路由器的情况下直接、快速、低功耗地控制智能设备。它能够与 Wi-Fi 和 Bluetooth LE 共存,支持乐鑫 ESP8266、ESP32、ESP32-S 和 ESP32-C 等多系列 SoC。 广泛应用于智能家电、远程控制和传感器等领域。 
据官方接受,ESP-NOW 在空旷环境中可实现200 m+ 的通信距离。 
 
 
关于ESP-NOW的信息,可以通过视频了解: 
 
 
 
这篇分享,使用两块ESP32-C6开发板,一块为FireBetlee 2 ESP32-6(大板)做为Esp-Now网关,提供MQTT接入服务,Betlee 2 ESP32-6(小板)做为终端,连接到Esp-Now网关,获得MQTT服务。 
 
 
  
 
一、ESPNow2Mqtt支持库 
eccnil提供了一个ESPNow2Mqtt的支持库: library for ESP32 to bridge ESPNow and MQTT 
其实现的核心功能如下: 
  
 
他提供了两个关键的类:EspNow2MqttServer、EspNow2MqttClient 
  
 
基于这两个库,就可以完成这篇分享需要的功能。 
 
不过这个库,是3年前发布的,要在咱们得ESP32C6上面使用,需要做一些移植改造。 
 
二、Esp-Now网关代码 
网关部分的完整代码如下: espnow_mqtt_server.zip 
 
			
			
			- 
 - #include <Arduino.h>
 - #ifdef ESP32
 -   #include <WiFi.h>
 - #else
 -   #include <ESP8266WiFi.h>
 - #endif
 - // #include "display.hpp"
 - #include "display_to_serial.hpp"
 - #include "EspNow2MqttGateway.hpp"
 - #include <WiFi.h>
 - #include <WiFiClient.h>
 - #include "secrets.h"
 - #include <esp_wifi.h>
 - 
 - // lcd display object creation for tests (not needed for gateway)
 - Display display = Display(true);
 - #define DISPLAY_LINE_IN_MAC 1
 - #define DISPLAY_LINE_IN 2
 - #define DISPLAY_LINE_OPERATIONS 3
 - #define DISPLAY_LINE_WIFI 6
 - #define DISPLAY_LINE_MAC 7
 - 
 - //shared criptokey, must be the same in all devices. create your own
 - 
 - byte sharedKey[16] = {10,200,23,4,50,3,99,82,39,100,211,112,143,4,15,106}; 
 - byte sharedChannel = 6; 
 - //gateway creation, needs initialization at setup, but after init mqtt
 - WiFiClient wifiClient;
 - EspNow2MqttGateway gw = EspNow2MqttGateway(sharedKey, wifiClient, MQTT_SERVER_IP, 1883, sharedChannel, MQTT_SERVER_CLIENT_ID, MQTT_SERVER_USER, MQTT_SERVER_PASSWORD);
 - 
 - void setupWiFi(const char* ssid, const char* password){
 -     // WiFi.mode(WIFI_MODE_STA);
 -     WiFi.mode(WIFI_STA);
 -     WiFi.setSleep(false);
 -     esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B|WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N|WIFI_PROTOCOL_LR);
 -     WiFi.begin(ssid, password, sharedChannel); 
 -     display.print(DISPLAY_LINE_WIFI,"wifiConnect ", true);
 -     while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
 -         delay(100);
 -         display.print(DISPLAY_LINE_WIFI, "trying to connect to wifi.."); 
 -     }
 -     display.print(DISPLAY_LINE_WIFI,"Connection established!");
 -     /*
 -     */
 -     String ipMsg =  String("ip ");
 -     ipMsg.concat( WiFi.localIP().toString());
 -     ipMsg.concat( " ch ");
 -     ipMsg.concat( String((int) WiFi.channel()) );
 -     display.print(DISPLAY_LINE_WIFI, ipMsg.c_str());  
 - }
 - 
 - void displayRequestAndResponse(bool ack, request &rq, response &rsp ){
 -     char line[13];
 -     for (int opCount = 0; opCount < rq.operations_count; opCount ++)
 -     {
 -         int lineNum = DISPLAY_LINE_OPERATIONS + opCount;
 -         switch (rq.operations[opCount].which_op)
 -         {
 -         case request_Operation_ping_tag:
 -             snprintf(line, sizeof(line), "ping: %d", rq.operations[opCount].op.ping.num );
 -             break;
 -         case request_Operation_send_tag:
 -             snprintf(line, sizeof(line), "send: %s", rq.operations[opCount].op.send.queue );
 -             break;
 -         case request_Operation_qRequest_tag:
 -             snprintf(line, sizeof(line), "ask: %s", rq.operations[opCount].op.qRequest.queue );
 -             break;
 -         default:
 -             snprintf(line, sizeof(line), "unknown op");
 -             break;
 -         }
 -         display.print(lineNum,line,false);
 -         Serial.println(line);
 -     }
 -     snprintf(line, sizeof(line), "%s: %d ops",
 -         rq.client_id, 
 -         rq.operations_count);
 -     display.print(DISPLAY_LINE_IN,line,true);
 -     Serial.println(line);
 -         String ipMsg =  String("ip ");
 -     ipMsg.concat( WiFi.localIP().toString());
 -     ipMsg.concat( " ch ");
 -     ipMsg.concat( String((int) WiFi.channel()) );
 -     display.print(DISPLAY_LINE_WIFI, ipMsg.c_str()); 
 - }
 - 
 - void displayMyMac(){
 -     char macStr[22];
 -     strcpy(macStr, "Mac ");
 -     strcat(macStr,WiFi.macAddress().c_str());
 -     display.print(DISPLAY_LINE_MAC, macStr);
 - }
 - 
 - void onEspNowRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
 -     char macStr[18+1+4]; //18 mac + 1 space + 3 len
 -     Serial.print("Packet received from: ");
 -     snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x %db",
 -             mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], len);
 -     Serial.println(macStr);
 -     display.print(DISPLAY_LINE_IN,"---", false);
 -     display.print(DISPLAY_LINE_IN_MAC,macStr, false);
 - }
 - 
 - void setup() {
 -     Serial.begin(115200);
 -     delay(1000);
 -     display.init();
 -     displayMyMac();
 - 
 -     setupWiFi(WIFI_SSID, WIFI_PASSWORD);
 - 
 -     //init gateway
 -     gw.init();
 -     gw.onProcessedRequest = displayRequestAndResponse;
 -     gw.onDataReceived = onEspNowRecv;
 -     EspNow2Mqtt_subscribe(); //FIXME: porque se tiene que llamar a esta función desde aqui??
 - }
 - 
 - void loop() {
 -     // put your main code here, to run repeatedly:
 -     delay(50);
 -     gw.loop(); //required to fetch messages from mqtt
 - }
 
  复制代码
  
 
上述代码的核心中,首先是完成WiFi联网:- setupWiFi(WIFI_SSID, WIFI_PASSWORD);
 
  复制代码 联网的信息,在secrets.h文件中提供: 
- #ifndef _secrets_hpp_
 - #define _secrets_hpp_
 - 
 - const char* WIFI_SSID = "OpenBSD";
 - const char* WIFI_PASSWORD = "********";
 - const char* OTA_PASSWORD = "********";
 - 
 - const char* MQTT_SERVER_IP = "broker.emqx.io";
 - const char* MQTT_SERVER_CLIENT_ID = "espnow_mqtt_gw";
 - const char* MQTT_SERVER_USER = "";
 - const char* MQTT_SERVER_PASSWORD = "";
 - 
 
  复制代码 其中有WiFi连接信息,以及MQTT服务连接信息 
 
WiFi连接后,进行ESP-Now网关初始化: 
-     gw.init();
 -     gw.onProcessedRequest = displayRequestAndResponse;
 -     gw.onDataReceived = onEspNowRecv;
 
  复制代码
  
 
最后,再进行MQTT订阅处理: 
复制代码
  
在loop循环中,再使用 gw.loop() 处理消息。 
 
网关的部分,通常情况下,代码不需要修改,主要起到一个桥梁的作用。 
 
三、终端代码 
终端的完整代码如下: espnow_mqtt_client.zip 
 
主要代码为: 
 
- 
 - /* example with LCD screen that sends single mesages*/
 - 
 - #include <Arduino.h>
 - // #include "display.hpp"
 - #include "display_to_serial.hpp"
 - #ifdef ESP32
 -   #include <WiFi.h>
 - #else
 -   #include <ESP8266WiFi.h>
 - #endif
 - #include <esp_wifi.h>
 - 
 - #define DEBUG_MODE 0
 - 
 - #include "EspNow2MqttClient.hpp"
 - 
 - #include <ArduinoJson.h>
 - JsonDocument doc;
 - 
 - #define LED_PIN 15
 - 
 - Display display = Display();
 - #define DISPLAY_LINE_OPERATION 1
 - #define DISPLAY_LINE_DATA_SENT 2
 - #define DISPLAY_LINE_DELIVERY_STATUS 3
 - #define DISPLAY_LINE_RESPONSE 4
 - #define DISPLAY_LINE__ 5
 - #define DISPLAY_LINE_ESPNOW_STATUS 6
 - #define DISPLAY_LINE_MAC 7
 - 
 - #define MESSAGE_TYPE_A 1
 - #define MESSAGE_TYPE_B 2
 - const char* queueA = "light";
 - const char* queueB = "motor";
 - 
 - byte sharedKey[16] = {10,200,23,4,50,3,99,82,39,100,211,112,143,4,15,106}; 
 - byte sharedChannel = 6 ;
 - // uint8_t gatewayMac[6] = {0xA4, 0xCF, 0x12, 0x25, 0x9A, 0x30};
 - uint8_t gatewayMac[6] = {0x54, 0x32, 0x04, 0x0B, 0x2F, 0x64};
 - EspNow2MqttClient client = EspNow2MqttClient("tstRq", sharedKey, gatewayMac, sharedChannel);
 - 
 - void displayDataOnCompletion( response & rsp)
 - {
 -   char line[30];
 -   int resultCode;
 - 
 -   if  (1 == rsp.opResponses_count)
 -   {
 -     resultCode = rsp.opResponses[0].result_code;
 -     const char * queue = rsp.message_type == MESSAGE_TYPE_A? queueA : queueB ;
 -     DeserializationError err;
 -     switch (resultCode)
 -     {
 -     case response_Result_OK:
 -       snprintf(line, sizeof(line), "%s ok: %s", queue, rsp.opResponses[0].payload);
 -       err = deserializeJson(doc, (const byte*)rsp.opResponses[0].payload);
 -       if (!err) { //检查反序列化是否成功
 -         if(doc.containsKey("status")) {
 -           int val = doc["status"];
 -           if(val) {
 -             Serial.println("Set LED On");
 -             digitalWrite(LED_PIN, HIGH);
 -           } else {
 -             Serial.println("Set LED Off");
 -             digitalWrite(LED_PIN, LOW);
 -           }
 -         }
 -       } else {
 -         Serial.print(F("deserializeJson() failed: "));
 -         Serial.println(err.c_str());
 -       }
 -       break;
 -     case response_Result_NO_MSG:
 - #if DEBUG_MODE
 -       snprintf(line, sizeof(line), "%s ok but no msg %d", queue, rsp.message_type);
 - #endif
 -       break;
 -     default:
 - #if DEBUG_MODE
 -       snprintf(line, sizeof(line), "%s status: %d %s", queue, resultCode, rsp.opResponses[0].payload);
 - #endif
 -       break;
 -     }
 -   } else {
 -     snprintf(line, sizeof(line), "error: %d", rsp.opResponses_count);
 -   }
 - #if DEBUG_MODE
 -   display.print(DISPLAY_LINE_RESPONSE,line,true);
 - #else
 -   if (resultCode == response_Result_OK){
 -     display.print(DISPLAY_LINE_RESPONSE,line,true);
 -   }
 - #endif
 -   // Serial.println(line);
 - }
 - 
 - void onDataSentUpdateDisplay(bool success) {
 - #if DEBUG_MODE
 -   display.print(DISPLAY_LINE_DELIVERY_STATUS, success ? "Delivery Success" : "Delivery Fail", false);
 - #endif
 - }
 - 
 - void displayGwMac()
 - {
 -   char macStr[24];
 -   snprintf(macStr, sizeof(macStr), "GwMac %02x:%02x:%02x:%02x:%02x:%02x",
 -          gatewayMac[0], gatewayMac[1], gatewayMac[2], gatewayMac[3], gatewayMac[4], gatewayMac[5]);
 -   display.print(DISPLAY_LINE_MAC, macStr);
 - }
 - 
 - void displayMyMac()
 - {
 -   char macStr[22];
 -   strcpy(macStr, "Mac ");
 -   strcat(macStr,WiFi.macAddress().c_str());
 -   display.print(DISPLAY_LINE_MAC, macStr);
 - }
 - 
 - int32_t getWiFiChannel(const char *ssid) {
 -   if (int32_t n = WiFi.scanNetworks()) {
 -       for (uint8_t i=0; i<n; i++) {
 -           if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
 -               return WiFi.channel(i);
 -           }
 -       }
 -   }
 -   return 0;
 - }
 - 
 - void testRequest(int msgType)
 - {
 -   bool ret;
 -   const char* queue = MESSAGE_TYPE_A == msgType ? queueA: queueB;
 - #if DEBUG_MODE
 -   display.print(DISPLAY_LINE_OPERATION, "request", false);
 -   display.print(DISPLAY_LINE_DATA_SENT, queue, false);
 - #endif
 -   ret = client.doSubscribe(queue, msgType);
 - #if DEBUG_MODE
 -   if(ret) {
 -     display.print(DISPLAY_LINE_MAC, "doSubscribe OK.");
 -   } else {
 -     display.print(DISPLAY_LINE_MAC, "doSubscribe Fail.");
 -   }
 -   display.print(DISPLAY_LINE_MAC, "\n");
 - #endif
 - }
 - 
 - void setup() {
 -   Serial.begin(115200);
 -   delay(1000);
 -   pinMode(LED_PIN, OUTPUT);
 - 
 -   display.init();
 -   displayGwMac();
 -   displayMyMac();
 - 
 -   int initcode;
 -   do {
 -     display.print(DISPLAY_LINE_ESPNOW_STATUS, "             TRYING");
 -     initcode = client.init();
 -     switch (initcode)
 -     {
 -     case 1:
 -       display.print(DISPLAY_LINE_ESPNOW_STATUS ,"CANNOT INIT");
 -       break;
 -     case 2:
 -       display.print(DISPLAY_LINE_ESPNOW_STATUS ,"CANNOT PAIR");
 -       break;
 -     default:
 -       display.print(DISPLAY_LINE_ESPNOW_STATUS ,"PAIRED");
 -       break;
 -     }
 -     delay(1001);
 -   } while (initcode != 0);
 - 
 -   client.onSentACK = onDataSentUpdateDisplay;
 -   client.onReceiveSomething = displayDataOnCompletion;
 - }
 - 
 - void loop() {
 -     testRequest (MESSAGE_TYPE_A);
 -     // delay(3000);
 -     // testRequest (MESSAGE_TYPE_B);
 -     delay(1000);
 - }
 
  复制代码
  
 
其中,进行的主要处理如下。首先,是客户端初始化: 
- client.init();
 - 
 - 
 -   client.onSentACK = onDataSentUpdateDisplay;
 -   client.onReceiveSomething = displayDataOnCompletion;
 
  复制代码 初始化完成后,发送时会调用client.onSentACK对应的函数,接收时,会调用client.onReceiveSomething对应的函数。 
 
在接受调用displayDataOnCompletion中,有进行消息解码的处理,具体代码如下: 
- 
 -       snprintf(line, sizeof(line), "%s ok: %s", queue, rsp.opResponses[0].payload);
 -       err = deserializeJson(doc, (const byte*)rsp.opResponses[0].payload);
 -       if (!err) { //检查反序列化是否成功
 -         if(doc.containsKey("status")) {
 -           int val = doc["status"];
 -           if(val) {
 -             Serial.println("Set LED On");
 -             digitalWrite(LED_PIN, HIGH);
 -           } else {
 -             Serial.println("Set LED Off");
 -             digitalWrite(LED_PIN, LOW);
 -           }
 -         }
 -       } else {
 -         Serial.print(F("deserializeJson() failed: "));
 -         Serial.println(err.c_str());
 -       }
 
  复制代码
  
在这段代码中,会检测收到的消息是否为json格式,如果是的,则根据status字段,来控制LED的亮灭。 
四、实际测试 
我家在13楼,所以我把大板调试好,放在了窗户边的桌子上。 
 
然后参考我之前的文章, ESP32-C6安全(SSL)MQTT服务点灯全套流程 DF创客社区 (dfrobot.com.cn) 在手机上做好mqtt工具的设置: 
   
 
 
然后我手拿小板就出发了。 
 
  
 
实际测试时,75米左右,都可以很好的进行控制,再远了一些,有时候信号就不太好了。如果是有外置天线的情况下,距离应该会更好吧。 
 
 
 
 |