玩转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重连的现象
  • 理论上设备接入数是无限制的,基本上能满足普通需求。