玩轉OneNET物聯網平台之MQTT服務④ —— 遠程控制LED(設備自註冊)+ Android App控制
- 2019 年 10 月 28 日
- 筆記
授人以魚不如授人以漁,目的不是為了教會你具體項目開發,而是學會學習的能力。希望大家分享給你周邊需要的朋友或者同學,說不定大神成長之路有博哥的奠基石。。。
QQ技術互動交流群:ESP8266&32 物聯網開發 群號622368884,不喜勿噴
一、你如果想學基於Arduino的ESP8266開發技術
一、基礎篇
二、網路篇
- ESP8266開發之旅 網路篇① 認識一下Arduino Core For ESP8266
- ESP8266開發之旅 網路篇② ESP8266 工作模式與ESP8266WiFi庫
- ESP8266開發之旅 網路篇③ Soft-AP——ESP8266WiFiAP庫的使用
- ESP8266開發之旅 網路篇④ Station——ESP8266WiFiSTA庫的使用
- ESP8266開發之旅 網路篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
- ESP8266開發之旅 網路篇⑥ ESP8266WiFiGeneric——基礎庫
- ESP8266開發之旅 網路篇⑦ TCP Server & TCP Client
- ESP8266開發之旅 網路篇⑧ SmartConfig——一鍵配網
- ESP8266開發之旅 網路篇⑨ HttpClient——ESP8266HTTPClient庫的使用
- ESP8266開發之旅 網路篇⑩ UDP服務
- ESP8266開發之旅 網路篇⑪ WebServer——ESP8266WebServer庫的使用
- ESP8266開發之旅 網路篇⑫ 域名服務——ESP8266mDNS庫
- ESP8266開發之旅 網路篇⑬ SPIFFS——ESP8266 Flash文件系統
- ESP8266開發之旅 網路篇⑭ web配網
- ESP8266開發之旅 網路篇⑮ 真正的域名服務——DNSServer
- ESP8266開發之旅 網路篇⑯ 無線更新——OTA韌體更新
三、應用篇
四、高級篇
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("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群里或者 程式碼下載地址。
注意點:
- 這裡用到了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重連的現象
- 理論上設備接入數是無限制的,基本上能滿足普通需求。