玩转OneNET物联网平台之MQTT服务④ —— 远程控制LED(设备...-Arduino中文社区 - Powered by Discuz! Archiver

单片机菜鸟 发表于 2019-10-27 17:11

玩转OneNET物联网平台之MQTT服务④ —— 远程控制LED(设备...

本帖最后由 单片机菜鸟 于 2019-11-28 09:15 编辑

> 授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。
>
## 你如果想学物联网平台

> 1.[玩转OneNET物联网平台之简介](https://blog.csdn.net/dpjcn1990/article/details/92967825)
> 2.[玩转OneNET物联网平台之MQTT服务①](https://blog.csdn.net/dpjcn1990/article/details/93021175)
> 3.[玩转OneNET物联网平台之MQTT服务② —— 远程控制LED](https://blog.csdn.net/dpjcn1990/article/details/93839603)
> 4.[玩转OneNET物联网平台之MQTT服务③ —— 远程控制LED(设备自注册)](https://blog.csdn.net/dpjcn1990/article/details/94365357)
> 5.[玩转OneNET物联网平台之HTTP服务① —— 模拟上传温度(TcpClient)](https://blog.csdn.net/dpjcn1990/article/details/93965862)
> 6.[玩转OneNET物联网平台之HTTP服务② —— 模拟上传温度(HttpClient)](https://blog.csdn.net/dpjcn1990/article/details/93987810)
> 7.(https://blog.csdn.net/dpjcn1990/article/details/100877884)
> 8.(https://blog.csdn.net/dpjcn1990/article/details/100877937)
> 9.(https://blog.csdn.net/dpjcn1990/article/details/100878039)

!(https://img-blog.csdnimg.cn/20191127092701656.png)

## 1.理论基础
    参考博主线上博文:
- **[玩转PubSubClient MQTT库](https://blog.csdn.net/dpjcn1990/article/details/92831686)**
- **[玩转OneNET物联网平台之简介](https://blog.csdn.net/dpjcn1990/article/details/92967825)**
- **[玩转OneNET物联网平台之MQTT服务①](https://blog.csdn.net/dpjcn1990/article/details/93021175)**
- **[玩转OneNET物联网平台之MQTT服务②](https://blog.csdn.net/dpjcn1990/article/details/93839603)**
- **[玩转OneNET物联网平台之MQTT服务③](https://blog.csdn.net/dpjcn1990/article/details/94365357)**

    在前面的博文中,博主主要通过手动方式去创建设备。这种方式的缺点明显:
- 人为手动控制,对于开发者来说极度不友好;
- 如果设备数量很多,岂不是要手动操作非常多次;

    那么,如何实现设备自注册呢?所谓自注册就是设备连入网络后自动往OneNet云平台注册设备信息并获取设备Id。
- **为了区分唯一性,我们采用ESP-Mac地址的组合形式**
- **同时为了操作方便,博主花了个周末的时间做了一个对应的app,理论上不限制ESP8266接入点的数量**

    本篇博文的目的就在于教会大家如何和app通信,完成MQTT协议下的App远程控制LED灯,并且LED灯的数量可以随意接入,用户可以在app端修改设备名字以便方便操作。

- **博主极度建议大家从第一篇看起,有个大概了解,因为本系列教程都是有相联系的**

    先上个概念图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191027164019666.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)


## 2.远程控制LED,实现设备自注册
### 2.1 实验材料
- ESP8266 NodeMcu
- Android手机
- OneNet平台

### 2.2 实验步骤
#### 2.2.1 创建 ESP8266智能灯系统 产品(MQTT协议)
!(https://imgconvert.csdnimg.cn/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Rpbmd5b3V3dS9BcmR1aW5vL21hc3Rlci9vbmV0bmV0LWVzcDgyNjYlRTYlOTklQkElRTglODMlQkQlRTclODElQUYtbXF0dC5wbmc)

**注意点**:
- 务必选择MQTT协议

    创建完毕后,我们点击查看具体的产品信息:

![在这里插入图片描述](https://img-blog.csdnimg.cn/2019092123165090.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)

**注意点**:
- **需要记录产品ID,其用来区分产品唯一标识符,这个ID待会需要填入App**
- **Master-APIkey,网络请求鉴权信息,接口调用需要带入,这个ID待会需要填入App**

#### 2.2.2 NodeMcu烧录代码 —— MQTT设备端
    为了明确区分代码功能,博哥命名工程名为P_OneNet_Exam05:

- **P_OneNet_Exam05.ino**文件:

```c
/**
*功能: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);
}
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(内存在堆区分配) 代替
   // DynamicJsonBufferjsonBuffer;
   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(内存在堆区分配) 代替
   // DynamicJsonBufferjsonBuffer;
   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 代码:

```c
#ifndef _MAIN_H__
#define _MAIN_H__


extern "C" {
#include "user_interface.h"
#include "smartconfig.h"
}

struct onenet_config
{
char deviceid;
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群里或者 [代码下载地址](https://github.com/tingyouwu/Arduino/tree/master/P_OneNet_Exam05)。

!(https://imgconvert.csdnimg.cn/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Rpbmd5b3V3dS9BcmR1aW5vL21hc3Rlci9QX09uZU5ldF9FeGFtMDQucG5n)

**注意点**:
- 这里用到了JSON,请参考博哥上线博文 **[玩转ArduinoJson库 V5版本](https://blog.csdn.net/dpjcn1990/article/details/92831612)**;
- 我们这里使用到了ESP8266 HttpClient来封装Http请求;

    将工程分别烧进多个NodeMcu(博哥这里烧录了两个),然后可以看到串口打印内容,如下:

!(https://imgconvert.csdnimg.cn/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Rpbmd5b3V3dS9BcmR1aW5vL21hc3Rlci9vbmVuZXQtbXF0dC1leDA0LWRldmljZWEucG5n)

!(https://imgconvert.csdnimg.cn/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Rpbmd5b3V3dS9BcmR1aW5vL21hc3Rlci9vbmVuZXQtbXF0dC1leDA0LWRldmljZWExLnBuZw)

!(https://imgconvert.csdnimg.cn/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Rpbmd5b3V3dS9BcmR1aW5vL21hc3Rlci9vbmVuZXQtbXF0dC1leDA0LWRldmljZWIucG5n)

    同时,也可以在OneNet平台看到设备情况,如下:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190921231751495.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)

    接下来就可以通过App进行远程控制led了。

## 3.配套android App
### 3.1 下载App
- 博主把App放在了个人交流群上以及(https://github.com/tingyouwu/Arduino/blob/master/P_OneNet_Exam05/OneNet%E6%99%BA%E8%83%BD%E7%81%AF.apk)
- App源码暂不开源,博主也上传到了个人交流群

### 3.2 配置App
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191027164327356.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)
- 手机App作为一个特殊的设备,需要自行注册一个新的设备,然后填入deviceId,至于如何注册设备,请参考 之前的博文。

### 3.3 操作App
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191027164958666.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)
- 主页面可以看到当前所有的设备列表(也就是你自注册的所有智能灯),并且标明了设备状态,然后我们就可以远程控制开关灯。

### 3.4 实验效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191027165424764.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191027165438856.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191027165451468.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16,color_FFFFFF,t_70)

## 4.总结
需要注意几点:
- 创建自己的OneNet产品,不要用博哥创建的,不然很容易发生MQTT重连的现象
- 理论上设备接入数是无限制的,基本上能满足普通需求。

单片机菜鸟 发表于 2019-10-28 09:06

欢迎入坑

单片机菜鸟 发表于 2019-11-28 10:06

自我顶一下

kebo 发表于 2020-3-1 10:53

:D:D:D好帖

13413180254 发表于 2021-10-23 22:00

现在距离楼主写这篇文章已经两年了,必须顶一下:lol

fayuOmg 发表于 2021-12-18 22:55

这文章很实用哦,谢谢楼主;P
页: [1]
查看完整版本: 玩转OneNET物联网平台之MQTT服务④ —— 远程控制LED(设备...