107浏览
查看: 107|回复: 2

[ESP8266/ESP32] ESP32-C6开发板通过EspN-ow连接MQTT实现控制

[复制链接]
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服务。


ESP32-C6开发板通过EspN-ow连接MQTT实现控制图1

一、ESPNow2Mqtt支持库
eccnil提供了一个ESPNow2Mqtt的支持库: library for ESP32 to bridge ESPNow and MQTT
其实现的核心功能如下:
ESP32-C6开发板通过EspN-ow连接MQTT实现控制图2

他提供了两个关键的类:EspNow2MqttServer、EspNow2MqttClient
ESP32-C6开发板通过EspN-ow连接MQTT实现控制图3

基于这两个库,就可以完成这篇分享需要的功能。

不过这个库,是3年前发布的,要在咱们得ESP32C6上面使用,需要做一些移植改造。

二、Esp-Now网关代码
网关部分的完整代码如下:下载附件espnow_mqtt_server.zip

  1. #include <Arduino.h>
  2. #ifdef ESP32
  3.   #include <WiFi.h>
  4. #else
  5.   #include <ESP8266WiFi.h>
  6. #endif
  7. // #include "display.hpp"
  8. #include "display_to_serial.hpp"
  9. #include "EspNow2MqttGateway.hpp"
  10. #include <WiFi.h>
  11. #include <WiFiClient.h>
  12. #include "secrets.h"
  13. #include <esp_wifi.h>
  14. // lcd display object creation for tests (not needed for gateway)
  15. Display display = Display(true);
  16. #define DISPLAY_LINE_IN_MAC 1
  17. #define DISPLAY_LINE_IN 2
  18. #define DISPLAY_LINE_OPERATIONS 3
  19. #define DISPLAY_LINE_WIFI 6
  20. #define DISPLAY_LINE_MAC 7
  21. //shared criptokey, must be the same in all devices. create your own
  22. byte sharedKey[16] = {10,200,23,4,50,3,99,82,39,100,211,112,143,4,15,106};
  23. byte sharedChannel = 6;
  24. //gateway creation, needs initialization at setup, but after init mqtt
  25. WiFiClient wifiClient;
  26. EspNow2MqttGateway gw = EspNow2MqttGateway(sharedKey, wifiClient, MQTT_SERVER_IP, 1883, sharedChannel, MQTT_SERVER_CLIENT_ID, MQTT_SERVER_USER, MQTT_SERVER_PASSWORD);
  27. void setupWiFi(const char* ssid, const char* password){
  28.     // WiFi.mode(WIFI_MODE_STA);
  29.     WiFi.mode(WIFI_STA);
  30.     WiFi.setSleep(false);
  31.     esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B|WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N|WIFI_PROTOCOL_LR);
  32.     WiFi.begin(ssid, password, sharedChannel);
  33.     display.print(DISPLAY_LINE_WIFI,"wifiConnect ", true);
  34.     while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
  35.         delay(100);
  36.         display.print(DISPLAY_LINE_WIFI, "trying to connect to wifi..");
  37.     }
  38.     display.print(DISPLAY_LINE_WIFI,"Connection established!");
  39.     /*
  40.     */
  41.     String ipMsg =  String("ip ");
  42.     ipMsg.concat( WiFi.localIP().toString());
  43.     ipMsg.concat( " ch ");
  44.     ipMsg.concat( String((int) WiFi.channel()) );
  45.     display.print(DISPLAY_LINE_WIFI, ipMsg.c_str());  
  46. }
  47. void displayRequestAndResponse(bool ack, request &rq, response &rsp ){
  48.     char line[13];
  49.     for (int opCount = 0; opCount < rq.operations_count; opCount ++)
  50.     {
  51.         int lineNum = DISPLAY_LINE_OPERATIONS + opCount;
  52.         switch (rq.operations[opCount].which_op)
  53.         {
  54.         case request_Operation_ping_tag:
  55.             snprintf(line, sizeof(line), "ping: %d", rq.operations[opCount].op.ping.num );
  56.             break;
  57.         case request_Operation_send_tag:
  58.             snprintf(line, sizeof(line), "send: %s", rq.operations[opCount].op.send.queue );
  59.             break;
  60.         case request_Operation_qRequest_tag:
  61.             snprintf(line, sizeof(line), "ask: %s", rq.operations[opCount].op.qRequest.queue );
  62.             break;
  63.         default:
  64.             snprintf(line, sizeof(line), "unknown op");
  65.             break;
  66.         }
  67.         display.print(lineNum,line,false);
  68.         Serial.println(line);
  69.     }
  70.     snprintf(line, sizeof(line), "%s: %d ops",
  71.         rq.client_id,
  72.         rq.operations_count);
  73.     display.print(DISPLAY_LINE_IN,line,true);
  74.     Serial.println(line);
  75.         String ipMsg =  String("ip ");
  76.     ipMsg.concat( WiFi.localIP().toString());
  77.     ipMsg.concat( " ch ");
  78.     ipMsg.concat( String((int) WiFi.channel()) );
  79.     display.print(DISPLAY_LINE_WIFI, ipMsg.c_str());
  80. }
  81. void displayMyMac(){
  82.     char macStr[22];
  83.     strcpy(macStr, "Mac ");
  84.     strcat(macStr,WiFi.macAddress().c_str());
  85.     display.print(DISPLAY_LINE_MAC, macStr);
  86. }
  87. void onEspNowRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
  88.     char macStr[18+1+4]; //18 mac + 1 space + 3 len
  89.     Serial.print("Packet received from: ");
  90.     snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x %db",
  91.             mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], len);
  92.     Serial.println(macStr);
  93.     display.print(DISPLAY_LINE_IN,"---", false);
  94.     display.print(DISPLAY_LINE_IN_MAC,macStr, false);
  95. }
  96. void setup() {
  97.     Serial.begin(115200);
  98.     delay(1000);
  99.     display.init();
  100.     displayMyMac();
  101.     setupWiFi(WIFI_SSID, WIFI_PASSWORD);
  102.     //init gateway
  103.     gw.init();
  104.     gw.onProcessedRequest = displayRequestAndResponse;
  105.     gw.onDataReceived = onEspNowRecv;
  106.     EspNow2Mqtt_subscribe(); //FIXME: porque se tiene que llamar a esta función desde aqui??
  107. }
  108. void loop() {
  109.     // put your main code here, to run repeatedly:
  110.     delay(50);
  111.     gw.loop(); //required to fetch messages from mqtt
  112. }
复制代码


上述代码的核心中,首先是完成WiFi联网:
  1. setupWiFi(WIFI_SSID, WIFI_PASSWORD);
复制代码
联网的信息,在secrets.h文件中提供:
  1. #ifndef _secrets_hpp_
  2. #define _secrets_hpp_
  3. const char* WIFI_SSID = "OpenBSD";
  4. const char* WIFI_PASSWORD = "********";
  5. const char* OTA_PASSWORD = "********";
  6. const char* MQTT_SERVER_IP = "broker.emqx.io";
  7. const char* MQTT_SERVER_CLIENT_ID = "espnow_mqtt_gw";
  8. const char* MQTT_SERVER_USER = "";
  9. const char* MQTT_SERVER_PASSWORD = "";
复制代码
其中有WiFi连接信息,以及MQTT服务连接信息

WiFi连接后,进行ESP-Now网关初始化:
  1.     gw.init();
  2.     gw.onProcessedRequest = displayRequestAndResponse;
  3.     gw.onDataReceived = onEspNowRecv;
复制代码


最后,再进行MQTT订阅处理:
  1. EspNow2Mqtt_subscribe();
复制代码

在loop循环中,再使用 gw.loop() 处理消息。

网关的部分,通常情况下,代码不需要修改,主要起到一个桥梁的作用。

三、终端代码
终端的完整代码如下:下载附件espnow_mqtt_client.zip

主要代码为:

  1. /* example with LCD screen that sends single mesages*/
  2. #include <Arduino.h>
  3. // #include "display.hpp"
  4. #include "display_to_serial.hpp"
  5. #ifdef ESP32
  6.   #include <WiFi.h>
  7. #else
  8.   #include <ESP8266WiFi.h>
  9. #endif
  10. #include <esp_wifi.h>
  11. #define DEBUG_MODE 0
  12. #include "EspNow2MqttClient.hpp"
  13. #include <ArduinoJson.h>
  14. JsonDocument doc;
  15. #define LED_PIN 15
  16. Display display = Display();
  17. #define DISPLAY_LINE_OPERATION 1
  18. #define DISPLAY_LINE_DATA_SENT 2
  19. #define DISPLAY_LINE_DELIVERY_STATUS 3
  20. #define DISPLAY_LINE_RESPONSE 4
  21. #define DISPLAY_LINE__ 5
  22. #define DISPLAY_LINE_ESPNOW_STATUS 6
  23. #define DISPLAY_LINE_MAC 7
  24. #define MESSAGE_TYPE_A 1
  25. #define MESSAGE_TYPE_B 2
  26. const char* queueA = "light";
  27. const char* queueB = "motor";
  28. byte sharedKey[16] = {10,200,23,4,50,3,99,82,39,100,211,112,143,4,15,106};
  29. byte sharedChannel = 6 ;
  30. // uint8_t gatewayMac[6] = {0xA4, 0xCF, 0x12, 0x25, 0x9A, 0x30};
  31. uint8_t gatewayMac[6] = {0x54, 0x32, 0x04, 0x0B, 0x2F, 0x64};
  32. EspNow2MqttClient client = EspNow2MqttClient("tstRq", sharedKey, gatewayMac, sharedChannel);
  33. void displayDataOnCompletion( response & rsp)
  34. {
  35.   char line[30];
  36.   int resultCode;
  37.   if  (1 == rsp.opResponses_count)
  38.   {
  39.     resultCode = rsp.opResponses[0].result_code;
  40.     const char * queue = rsp.message_type == MESSAGE_TYPE_A? queueA : queueB ;
  41.     DeserializationError err;
  42.     switch (resultCode)
  43.     {
  44.     case response_Result_OK:
  45.       snprintf(line, sizeof(line), "%s ok: %s", queue, rsp.opResponses[0].payload);
  46.       err = deserializeJson(doc, (const byte*)rsp.opResponses[0].payload);
  47.       if (!err) { //检查反序列化是否成功
  48.         if(doc.containsKey("status")) {
  49.           int val = doc["status"];
  50.           if(val) {
  51.             Serial.println("Set LED On");
  52.             digitalWrite(LED_PIN, HIGH);
  53.           } else {
  54.             Serial.println("Set LED Off");
  55.             digitalWrite(LED_PIN, LOW);
  56.           }
  57.         }
  58.       } else {
  59.         Serial.print(F("deserializeJson() failed: "));
  60.         Serial.println(err.c_str());
  61.       }
  62.       break;
  63.     case response_Result_NO_MSG:
  64. #if DEBUG_MODE
  65.       snprintf(line, sizeof(line), "%s ok but no msg %d", queue, rsp.message_type);
  66. #endif
  67.       break;
  68.     default:
  69. #if DEBUG_MODE
  70.       snprintf(line, sizeof(line), "%s status: %d %s", queue, resultCode, rsp.opResponses[0].payload);
  71. #endif
  72.       break;
  73.     }
  74.   } else {
  75.     snprintf(line, sizeof(line), "error: %d", rsp.opResponses_count);
  76.   }
  77. #if DEBUG_MODE
  78.   display.print(DISPLAY_LINE_RESPONSE,line,true);
  79. #else
  80.   if (resultCode == response_Result_OK){
  81.     display.print(DISPLAY_LINE_RESPONSE,line,true);
  82.   }
  83. #endif
  84.   // Serial.println(line);
  85. }
  86. void onDataSentUpdateDisplay(bool success) {
  87. #if DEBUG_MODE
  88.   display.print(DISPLAY_LINE_DELIVERY_STATUS, success ? "Delivery Success" : "Delivery Fail", false);
  89. #endif
  90. }
  91. void displayGwMac()
  92. {
  93.   char macStr[24];
  94.   snprintf(macStr, sizeof(macStr), "GwMac %02x:%02x:%02x:%02x:%02x:%02x",
  95.          gatewayMac[0], gatewayMac[1], gatewayMac[2], gatewayMac[3], gatewayMac[4], gatewayMac[5]);
  96.   display.print(DISPLAY_LINE_MAC, macStr);
  97. }
  98. void displayMyMac()
  99. {
  100.   char macStr[22];
  101.   strcpy(macStr, "Mac ");
  102.   strcat(macStr,WiFi.macAddress().c_str());
  103.   display.print(DISPLAY_LINE_MAC, macStr);
  104. }
  105. int32_t getWiFiChannel(const char *ssid) {
  106.   if (int32_t n = WiFi.scanNetworks()) {
  107.       for (uint8_t i=0; i<n; i++) {
  108.           if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
  109.               return WiFi.channel(i);
  110.           }
  111.       }
  112.   }
  113.   return 0;
  114. }
  115. void testRequest(int msgType)
  116. {
  117.   bool ret;
  118.   const char* queue = MESSAGE_TYPE_A == msgType ? queueA: queueB;
  119. #if DEBUG_MODE
  120.   display.print(DISPLAY_LINE_OPERATION, "request", false);
  121.   display.print(DISPLAY_LINE_DATA_SENT, queue, false);
  122. #endif
  123.   ret = client.doSubscribe(queue, msgType);
  124. #if DEBUG_MODE
  125.   if(ret) {
  126.     display.print(DISPLAY_LINE_MAC, "doSubscribe OK.");
  127.   } else {
  128.     display.print(DISPLAY_LINE_MAC, "doSubscribe Fail.");
  129.   }
  130.   display.print(DISPLAY_LINE_MAC, "\n");
  131. #endif
  132. }
  133. void setup() {
  134.   Serial.begin(115200);
  135.   delay(1000);
  136.   pinMode(LED_PIN, OUTPUT);
  137.   display.init();
  138.   displayGwMac();
  139.   displayMyMac();
  140.   int initcode;
  141.   do {
  142.     display.print(DISPLAY_LINE_ESPNOW_STATUS, "             TRYING");
  143.     initcode = client.init();
  144.     switch (initcode)
  145.     {
  146.     case 1:
  147.       display.print(DISPLAY_LINE_ESPNOW_STATUS ,"CANNOT INIT");
  148.       break;
  149.     case 2:
  150.       display.print(DISPLAY_LINE_ESPNOW_STATUS ,"CANNOT PAIR");
  151.       break;
  152.     default:
  153.       display.print(DISPLAY_LINE_ESPNOW_STATUS ,"PAIRED");
  154.       break;
  155.     }
  156.     delay(1001);
  157.   } while (initcode != 0);
  158.   client.onSentACK = onDataSentUpdateDisplay;
  159.   client.onReceiveSomething = displayDataOnCompletion;
  160. }
  161. void loop() {
  162.     testRequest (MESSAGE_TYPE_A);
  163.     // delay(3000);
  164.     // testRequest (MESSAGE_TYPE_B);
  165.     delay(1000);
  166. }
复制代码


其中,进行的主要处理如下。首先,是客户端初始化:
  1. client.init();
  2.   client.onSentACK = onDataSentUpdateDisplay;
  3.   client.onReceiveSomething = displayDataOnCompletion;
复制代码
初始化完成后,发送时会调用client.onSentACK对应的函数,接收时,会调用client.onReceiveSomething对应的函数。

在接受调用displayDataOnCompletion中,有进行消息解码的处理,具体代码如下:
  1.       snprintf(line, sizeof(line), "%s ok: %s", queue, rsp.opResponses[0].payload);
  2.       err = deserializeJson(doc, (const byte*)rsp.opResponses[0].payload);
  3.       if (!err) { //检查反序列化是否成功
  4.         if(doc.containsKey("status")) {
  5.           int val = doc["status"];
  6.           if(val) {
  7.             Serial.println("Set LED On");
  8.             digitalWrite(LED_PIN, HIGH);
  9.           } else {
  10.             Serial.println("Set LED Off");
  11.             digitalWrite(LED_PIN, LOW);
  12.           }
  13.         }
  14.       } else {
  15.         Serial.print(F("deserializeJson() failed: "));
  16.         Serial.println(err.c_str());
  17.       }
复制代码

在这段代码中,会检测收到的消息是否为json格式,如果是的,则根据status字段,来控制LED的亮灭。
四、实际测试
我家在13楼,所以我把大板调试好,放在了窗户边的桌子上。

然后参考我之前的文章, ESP32-C6安全(SSL)MQTT服务点灯全套流程 DF创客社区 (dfrobot.com.cn) 在手机上做好mqtt工具的设置:
ESP32-C6开发板通过EspN-ow连接MQTT实现控制图7ESP32-C6开发板通过EspN-ow连接MQTT实现控制图8


然后我手拿小板就出发了。

ESP32-C6开发板通过EspN-ow连接MQTT实现控制图6

实际测试时,75米左右,都可以很好的进行控制,再远了一些,有时候信号就不太好了。如果是有外置天线的情况下,距离应该会更好吧。



_深蓝_  中级技师

发表于 2024-4-24 08:34:33

好好学习,天天向上哈,虽然读起来吃力
回复

使用道具 举报

HonestQiao  高级技师
 楼主|

发表于 3 天前

_深蓝_ 发表于 2024-4-24 08:34
好好学习,天天向上哈,虽然读起来吃力

加油!加油!加油!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail