1. 程式人生 > >玩轉OneNET物聯網平臺之MQTT服務④ —— 遠端控制LED(裝置自注冊)+ Android App控制

玩轉OneNET物聯網平臺之MQTT服務④ —— 遠端控制LED(裝置自注冊)+ Android App控制

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

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.理論基礎

    參考博主線上博文:

  • 玩轉PubSubClient MQTT庫
  • 玩轉OneNET物聯網平臺之簡介
  • 玩轉OneNET物聯網平臺之MQTT服務①
  • 玩轉OneNET物聯網平臺之MQTT服務②
  • 玩轉OneNET物聯網平臺之MQTT服務③

    在前面的博文中,博主主要通過手動方式去建立裝置。這種方式的缺點明顯:

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

    那麼,如何實現裝置自注冊呢?所謂自注冊就是裝置連入網路後自動往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協議)

注意點:

  • 務必選擇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("\r\n\r\nStart 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群裡或者 程式碼下載地址。

注意點:

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

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

    同時,也可以在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重連的現象
  • 理論上裝置接入數是無限制的,基本上能滿足普通需求。