玩轉OneNET物聯網平台之MQTT服務④ —— 遠程控制LED(設備自註冊)+ Android App控制

  • 2019 年 10 月 28 日
  • 筆記

授人以魚不如授人以漁,目的不是為了教會你具體項目開發,而是學會學習的能力。希望大家分享給你周邊需要的朋友或者同學,說不定大神成長之路有博哥的奠基石。。。

QQ技術互動交流群:ESP8266&32 物聯網開發 群號622368884,不喜勿噴

一、你如果想學基於Arduino的ESP8266開發技術

一、基礎篇

  1. ESP8266開發之旅 基礎篇① 走進ESP8266的世界
  2. ESP8266開發之旅 基礎篇② 如何安裝ESP8266的Arduino開發環境
  3. ESP8266開發之旅 基礎篇③ ESP8266與Arduino的開發說明
  4. ESP8266開發之旅 基礎篇④ ESP8266與EEPROM
  5. ESP8266開發之旅 基礎篇⑤ ESP8266 SPI通訊和I2C通訊
  6. ESP8266開發之旅 基礎篇⑥ Ticker——ESP8266定時庫

二、網路篇

  1. ESP8266開發之旅 網路篇① 認識一下Arduino Core For ESP8266
  2. ESP8266開發之旅 網路篇② ESP8266 工作模式與ESP8266WiFi庫
  3. ESP8266開發之旅 網路篇③ Soft-AP——ESP8266WiFiAP庫的使用
  4. ESP8266開發之旅 網路篇④ Station——ESP8266WiFiSTA庫的使用
  5. ESP8266開發之旅 網路篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
  6. ESP8266開發之旅 網路篇⑥ ESP8266WiFiGeneric——基礎庫
  7. ESP8266開發之旅 網路篇⑦ TCP Server & TCP Client
  8. ESP8266開發之旅 網路篇⑧ SmartConfig——一鍵配網
  9. ESP8266開發之旅 網路篇⑨ HttpClient——ESP8266HTTPClient庫的使用
  10. ESP8266開發之旅 網路篇⑩ UDP服務
  11. ESP8266開發之旅 網路篇⑪ WebServer——ESP8266WebServer庫的使用
  12. ESP8266開發之旅 網路篇⑫ 域名服務——ESP8266mDNS庫
  13. ESP8266開發之旅 網路篇⑬ SPIFFS——ESP8266 Flash文件系統
  14. ESP8266開發之旅 網路篇⑭ web配網
  15. ESP8266開發之旅 網路篇⑮ 真正的域名服務——DNSServer
  16. ESP8266開發之旅 網路篇⑯ 無線更新——OTA韌體更新

三、應用篇

  1. ESP8266開發之旅 應用篇① 區域網應用 ——炫酷RGB彩燈
  2. ESP8266開發之旅 應用篇② OLED顯示天氣屏
  3. ESP8266開發之旅 應用篇③ 簡易版WiFi小車

四、高級篇

  1. ESP8266開發之旅 進階篇① 程式碼優化 —— ESP8266記憶體管理
  2. ESP8266開發之旅 進階篇② 閑聊Arduino IDE For ESP8266配置
  3. ESP8266開發之旅 進階篇③ 閑聊 ESP8266 Flash
  4. ESP8266開發之旅 進階篇④ 常見問題 —— 解決困擾
  5. ESP8266開發之旅 進階篇⑤ 程式碼規範 —— 像寫文章一樣優美
  6. ESP8266開發之旅 進階篇⑥ ESP-specific APIs說明

1.理論基礎

    參考部落客線上博文:

    在前面的博文中,部落客主要通過手動方式去創建設備。這種方式的缺點明顯:

  • 人為手動控制,對於開發者來說極度不友好;
  • 如果設備數量很多,豈不是要手動操作非常多次;

    那麼,如何實現設備自註冊呢?所謂自註冊就是設備連入網路後自動往OneNet雲平台註冊設備資訊並獲取設備Id。

  • 為了區分唯一性,我們採用ESP-Mac地址的組合形式
  • 同時為了操作方便,部落客花了個周末的時間做了一個對應的app,理論上不限制ESP8266接入點的數量

    本篇博文的目的就在於教會大家如何和app通訊,完成MQTT協議下的App遠程控制LED燈,並且LED燈的數量可以隨意接入,用戶可以在app端修改設備名字以便方便操作。

  • 部落客極度建議大家從第一篇看起,有個大概了解,因為本系列教程都是有相聯繫的

    先上個概念圖:
在這裡插入圖片描述

2.遠程控制LED,實現設備自註冊

2.1 實驗材料

  • ESP8266 NodeMcu
  • Android手機
  • OneNet平台

2.2 實驗步驟

2.2.1 創建 ESP8266智慧燈系統 產品(MQTT協議)

image

注意點

  • 務必選擇MQTT協議

    創建完畢後,我們點擊查看具體的產品資訊:

在這裡插入圖片描述

注意點

  • 需要記錄產品ID,其用來區分產品唯一標識符,這個ID待會需要填入App
  • Master-APIkey,網路請求鑒權資訊,介面調用需要帶入,這個ID待會需要填入App

2.2.2 NodeMcu燒錄程式碼 —— MQTT設備端

    為了明確區分程式碼功能,博哥命名工程名為P_OneNet_Exam05:

  • P_OneNet_Exam05.ino文件:
/**   *  功能:ESP8266 Mqtt客戶端自注冊功能,通過配套App控制Led消息,理論上可以接入無數個esp8266   *  作者:單片機菜鳥   *  時間:2019-10-27   *  描述:   *      1.初始化工作:初始化網路配置,Mqtt客戶端自注冊,連接鑒權,訂閱主題   *      2.訂閱消息:獲取發送過來的消息(json格式),解析消息,實現控制亮滅燈  */    #include <ESP8266WiFi.h>  #include <PubSubClient.h>  #include <ESP8266HTTPClient.h>  #include <ArduinoJson.h>  #include <EEPROM.h>  #include <Ticker.h>  #include "H_project.h"    #define MAGIC_NUMBER 0xAA    int state;  WiFiClient espClient;    //聲明方法  void initSystem();  void initOneNetMqtt();  void callback(char* topic, byte* payload, unsigned int length);  void saveConfig();  void loadConfig();  bool parseRegisterResponse();  void parseOneNetMqttResponse(char* payload);    /**   * 初始化   */  void setup() {    initSystem();    initOneNetMqtt();  }    void loop() {    ESP.wdtFeed();    state = connectToOneNetMqtt();    if(state == ONENET_RECONNECT){       //重連成功 需要重新註冊       mqttClient.subscribe(TOPIC,1);       mqttClient.loop();    }else if(state == ONENET_CONNECTED){       mqttClient.loop();    }    delay(2000);  }    void initSystem(){      int cnt = 0;      Serial.begin (115200);      Serial.println("rnrnStart ESP8266 MQTT");      Serial.print("Firmware Version:");      Serial.println(VER);      Serial.print("SDK Version:");      Serial.println(ESP.getSdkVersion());      wifi_station_set_auto_connect(0);//關閉自動連接      ESP.wdtEnable(5000);      WiFi.disconnect();      delay(100);      WiFi.begin(ssid, password);      while (WiFi.status() != WL_CONNECTED) {            delay(500);            cnt++;            Serial.print(".");            if(cnt>=40){              cnt = 0;              //重啟系統              delayRestart(1);            }      }      pinMode(LED_BUILTIN, OUTPUT);        loadConfig();      //還沒有註冊      if(strcmp(config.deviceid,DEFAULT_ID) == 0){          int tryAgain = 0;          while(!registerDeviceToOneNet()){            Serial.print(".");            delay(500);            tryAgain++;            if(tryAgain == 5){              //嘗試5次              tryAgain = 0;              //重啟系統              delayRestart(1);            }          }          if(!parseRegisterResponse()){              //重啟系統              delayRestart(1);              while(1);          }      }  }    void initOneNetMqtt(){      mqttClient.setServer(mqttServer,mqttPort);      mqttClient.setClient(espClient);      mqttClient.setCallback(callback);        initOneNet(PRODUCT_ID,API_KEY,config.deviceid);  }    void callback(char* topic, byte* payload, unsigned int length) {    Serial.print("Message arrived [");    Serial.print(topic);    Serial.print("] ");    for (int i = 0; i < length; i++) {      Serial.print((char)payload[i]);    }    Serial.println();    parseOneNetMqttResponse((char *)payload);  }    /*   * 保存參數到EEPROM  */  void saveConfig()  {    Serial.println("Save OneNet config!");    Serial.print("deviceId:");    Serial.println(config.deviceid);      EEPROM.begin(150);    uint8_t *p = (uint8_t*)(&config);    for (int i = 0; i < sizeof(config); i++)    {      EEPROM.write(i, *(p + i));    }    EEPROM.commit();  }    /*   * 從EEPROM載入參數  */  void loadConfig()  {    EEPROM.begin(150);    uint8_t *p = (uint8_t*)(&config);    for (int i = 0; i < sizeof(config); i++)    {      *(p + i) = EEPROM.read(i);    }    EEPROM.commit();    if (config.magic != MAGIC_NUMBER)    {      strcpy(config.deviceid, DEFAULT_ID);      config.magic = MAGIC_NUMBER;      saveConfig();      Serial.println("Restore config!");    }    Serial.println("-----Read config-----");    Serial.print("deviceId:");    Serial.println(config.deviceid);    Serial.println("-------------------");  }    /**   * 解析mqtt數據   */  void parseOneNetMqttResponse(char* payload){     Serial.println("start parseOneNetMqttResponse");     StaticJsonBuffer<100> jsonBuffer;       // StaticJsonBuffer 在棧區分配記憶體   它也可以被 DynamicJsonBuffer(記憶體在堆區分配) 代替       // DynamicJsonBuffer  jsonBuffer;     JsonObject& root = jsonBuffer.parseObject(payload);         // Test if parsing succeeds.     if (!root.success()) {         Serial.println("parseObject() failed");         return ;     }       String deviceId = root["Did"];     int status = root["sta"];       if(strcmp(config.deviceid,deviceId.c_str()) == 0){          if (status == 1) {              digitalWrite(LED_BUILTIN, LOW);          } else {              digitalWrite(LED_BUILTIN, HIGH);          }      }  }    /**   * 解析註冊返回結果   */  bool parseRegisterResponse(){     Serial.println("start parseRegisterResponse");     StaticJsonBuffer<200> jsonBuffer;       // StaticJsonBuffer 在棧區分配記憶體   它也可以被 DynamicJsonBuffer(記憶體在堆區分配) 代替       // DynamicJsonBuffer  jsonBuffer;     JsonObject& root = jsonBuffer.parseObject(response);         // Test if parsing succeeds.     if (!root.success()) {         Serial.println("parseObject() failed");         return false;     }       int errno = root["errno"];     if(errno !=0){         Serial.println("register failed!");         return false;     }else{         Serial.println("register sucess!");         strcpy(config.deviceid, root["data"]["device_id"]);         saveConfig();         return true;     }  }
  • H_project.h 程式碼:
#ifndef _MAIN_H__  #define _MAIN_H__      extern "C" {  #include "user_interface.h"  #include "smartconfig.h"  }    struct onenet_config  {    char deviceid[15];    uint8_t magic;  };    /************** ESP8266相關操作 **************************/  void delayRestart(float t);  void delayNs(uint8_t m);  /*********************************************************/    /*************** OneNet MQTT相關操作 ****************************/  void initOneNet(uint8_t *productId,uint8_t *apiKey,uint8_t *deviceId);  int connectToOneNetMqtt();  /*********************************************************/    /**************** OneNet Http相關操作 ***************************/  HTTPClient http;  String response;  const char* host = "api.heclouds.com";  bool registerDeviceToOneNet();  /****************************************************************/    #define ONENET_DISCONNECTED 1 //已經斷開  #define ONENET_CONNECTED 2    //已經連接上  #define ONENET_RECONNECT 3    //重連成功    //常量  #define VER             "MQTT_LED_V1.0"  const char* ssid = "xxxxxxxx";//wifi帳號  const char* password = "xxxxxxx";//wifi秘密    //OneNet相關  PubSubClient mqttClient;  const char* mqttServer = "183.230.40.39";//mqtt伺服器  const uint16_t mqttPort = 6002;  #define PRODUCT_ID    "253190" //此為博哥自己的產品id 請新建自己的  #define API_KEY    "xxxxxx"  #define DEFAULT_ID "123456"  #define TOPIC     "esp8266led"    unsigned long lastWiFiCheckTick = 0;  bool ledState = 0;    onenet_config config;    #endif  

    全部工程程式碼,博哥放在個人QQ群里或者 程式碼下載地址

image

注意點

  • 這裡用到了JSON,請參考博哥上線博文 玩轉ArduinoJson庫 V5版本
  • 我們這裡使用到了ESP8266 HttpClient來封裝Http請求;

    將工程分別燒進多個NodeMcu(博哥這裡燒錄了兩個),然後可以看到串口列印內容,如下:

image

image

image

    同時,也可以在OneNet平台看到設備情況,如下:

在這裡插入圖片描述

    接下來就可以通過App進行遠程控制led了。

3.配套android App

3.1 下載App

  • 部落客把App放在了個人交流群上以及Github
  • App源碼暫不開源,部落客也上傳到了個人交流群

3.2 配置App

在這裡插入圖片描述

  • 手機App作為一個特殊的設備,需要自行註冊一個新的設備,然後填入deviceId,至於如何註冊設備,請參考 之前的博文。

3.3 操作App

在這裡插入圖片描述

  • 主頁面可以看到當前所有的設備列表(也就是你自註冊的所有智慧燈),並且標明了設備狀態,然後我們就可以遠程控制開關燈。

3.4 實驗效果

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

4.總結

需要注意幾點:

  • 創建自己的OneNet產品,不要用博哥創建的,不然很容易發生MQTT重連的現象
  • 理論上設備接入數是無限制的,基本上能滿足普通需求。