本帖最后由 正点原子运营 于 2022-9-1 16:07 编辑  
 
1)实验平台:正点原子阿尔法Linux开发板 
2)  章节摘自【正点原子】《I.MX6U 嵌入式Qt开发指南》 
3)购买链接:https://detail.tmall.com/item.htm?id=609033604451 
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html 
5)正点原子官方B站:https://space.bilibili.com/394620890 
6)正点原子阿尔法Linux交流群:1027879335   
 
  
 
  
 
第二十四章 智能家居物联网项目  
本章介绍使用Qt开发智能家居中的一个物联应用。简单直白的说就是通过云服务器远程控制设备(与设备通信等)。本章可以直接做毕设,是毕设物联网项目的一大福音!本章将实现远程点亮开发板LED作为一个项目实例。 在生活中,我们可能使用过WIFI智能插座这款产品。智能家居中常用来控制电器开关。比如远程开热水器,远程打开空调,窗帘等等。这些WIFI智能插座的原理就是将WIFI插座注册到云服务器上,然后通过手机的APP来访问云服务器,然后控制WIFI插座。嗯,原理我们懂了。本章就是模仿?不,或者说是直接开发这样的一个项目。包括WIFI连网,注册到云服务器上,编写Qt UI通过网络来与云服务器通信,然后再下发指令到这个连网的设备,与之通信。恩本章的流程就是这些,带着这个项目流程,然后一步步看编者是如何通过Qt实现的吧! 本章需要读者对正点原子的wifi模块ATK-ESP8266串口转WIFI有一定的了解。正点原子提供了STM32与ESP8266模块通信的例程,如果学习过STM32与ESP8266模块通信的例程的内容,理解起来则会更容易。建议参考文档:ATK-ESP8266 WIFI用户手册_V1.x.pdf及原子云平台API文档V1.2.pdf。 24.1 项目硬件l  必备硬件 本章需要正点原子ATK-ESP8266串口转WIFI模块(免费接入原子云)。另外还需要加上一个USB-TTL模块,外加一根T口USB连接线,可接入PC(电脑)调试。        T口连接线连接USB-TTL模块再连接ATK-ESP8266模块到PC(电脑),用于在PC(电脑)上直接使用串口调试/测试此模块。        这里可能会有部分读者会问是否可以用其他WIFI模块,比如正点原子Linux USB WIFI模块,或者直接使用开发板联网接入到云设备呢?答案是不可以的!只有一些特定的设备,需要刷能接入云的固件才能接入服务器。        又有读者问是否可以直接购买一些WIFI插座来使用呢?答案是不可以的!因为这些WIFI插座也是一样,也是刷了固件,而且这些设备是连接到阿里云的。需要与特定的手机APP结合使用才能注册到阿里云服务器。也就是不能拿来二次开发了!        恰好我们正点原子有物联网模块ESP8266与4G DTU模块。本章主要讲解如何通过正点原子的串口转WIFI ESP8266模块来开发一个物联网的项目应用! l  可选配件        本项目可以在正点原子I.MX6U ALPHA | mini开发板直接使用,下图为正点原子ALPHA开发板在底板上预留的ATK MODULE接口(串口接口)上的接法图。 特别提醒:如果反复实验不正确时,因为ATK-MODULE这个接口,KEY和LED脚如果有其他程序在使用,那么很可能会影响ESP8266模块的功能。刚好那两个脚接到了ESP8266的烧录固件IO-0脚与复位RST脚上,所以我们可以用杜邦线将模块重新连接到这个座子上,只接VCC、GND、TX和RX脚即可! 24.2 测试WIFI模块要实现物联网功能,需要使用正点原子的ATK-ESP8266 WIFI模块。首先我们先测试正点原子的ATK-ESP8266 WIFI模块是否正常使用,及能否正常连接原子云服务器。ATK-ESP8266 WIFI用户手册_V1.3.pdf手册第2.2.3小节硬件连接,将ATK-ESP8266 WIFI模块连接到PC(电脑),再查阅ATK-ESP8266 WIFI用户手册_V1.3.pdf手册的第2.2.9.1小节,注册原子云帐号后,添加设备,然后按2.2.9.1小节测试连接本地WIFI(自己的路由器发出的WIFI,注意不要使用中文名或者有空格的WIFI,确保路由器的WIFI能上网!)。请自行完成并成功连接到原子云。 24.3 WIFI模块连接原子云请先测试个人的ATK-ESP8266模块是否正常使用,及正常连接云。原子云的设备需要先分好组,各个设备命名如下。注意需要和编者命名的名字一样,也就是至少有一个分组及一个名字为“客厅灯”的设备,并记住编号及密码(密码由云生成,默认“12345678”)。  
源码路径4/01_smarthome/esp8266/esp8266.cpp,内容如下。默认使用的WIFI模块串口通信波特率为115200。在Ubuntu上设试WIFI模块时,一般串口名称为“ttyUSB0”,默认是没有权限访问这个/dev/ttyUSB0设备的。所以我们需要使用下面的指令修改权限。(注意:本例适用于I.MX6U Linux开发板与Ubuntu,Windows不作测试!)。 - sudo chmod 777 /dev/ttyUSB0
 
  复制代码修改完成后查看源码内容如下。先看源码,不要急着运行!        查看源码,我们可以知道以下重要内容。        第72行,“ALIENTEK-YF”是编者处的路由器发出的WIFI热点名称,密码是“15902020353”。请修改为自己的WIFI名称及连接密码!没有路由器,用手机开热点也可以。        第81行,是原子云上的“客厅灯”设备的编号“02314701717851074890”,及密码“12345678”,请填写自己的设备编号,及密码,注意这个设备需要命名为“客厅灯”,后面程序需要使用到它!按照上面的程序就容易地可以连接上原子云了。也无需熟读ATK-ESP8266 WIFI用户手册_V1.3.pdf手册。 24.4 智能家居物联UI界面开发项目路径为4/01_smarthome/01_smarthome/01_smarthome.pro,先看项目界面。项目界面如下,采用暗黑主题设计,结合黄色作为亮色,让用户一目了然。界面编者从一些智能家居界面中找到灵感的,编写设计完成的效果不错!请自行查阅源码,掌握了本教程前面第七章的内容,就可以理解这个界面是如何设计的。 24.5 原子云API接口我们想要与原子云通信,那么必须先了解原子云平台的API接口。请参阅原子云平台API文档V1.2.pdf文档。原子云平台API写的非常详细了,请自行翻阅。需要我们从原子云平台了解原子云API的通信流程。 下图是原子云平台API的使用流程图。        我们写Qt应用就应该重点放在HTTPS与WebSocket方向上。查阅原子云平台API可以知道,下面是重点!一些帐号信息,与设备信息是通过HTTPS协议接口获取的,通信用WebSocket协议接口。那么我们就按原子云平台的协议流程编写应用程序。        源码路径为4/01_smarthome/webapi/webapi.cpp。内容如下。 - /******************************************************************
 
 -     Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
 
 -     * @projectName   webapi
 
 -     * @brief         webapi.cpp
 
 -     * @author        Deng Zhimao
 
 -     * @email         1252699831@qq.com
 
 -     * @net            www.openedv.com
 
 -     * @date           2021-05-27
 
 -     *******************************************************************/
 
 - 1   #include "webapi.h"
 
 - 2   #include <QUuid>
 
 - 3   #include <QRegularExpression>
 
 - 4  
 
 - 5   Webapi::Webapi(QObject *parent)
 
 - 6   {
 
 - 7       this->setParent(parent);
 
 - 8       /* 数组清空 */
 
 - 9       groupID.clear();
 
 - 10      deviceID.clear();
 
 - 11      deviceNumber.clear();
 
 - 12 
 
 - 13      timer = new QTimer();
 
 - 14      connect(timer, SIGNAL(timeout()), this, SLOT(onTimerTimeOut()));
 
 - 15 
 
 - 16      networkAccessManager = new QNetworkAccessManager(this);
 
 - 17 
 
 - 18      orgURL = "https://cloud.alientek.com/api/orgs";
 
 - 19      /* 请填写自己的token信息!!! */
 
 - 20      api_token = "bf591984c8fa417584d18f6328e0ef73";
 
 - 21 
 
 - 22      /* 获取账号机构列表 */
 
 - 23      getOrgURL();
 
 - 24 
 
 - 25      QUuid uuid = QUuid::createUuid();
 
 - 26      random_token = uuid.toString();
 
 - 27 
 
 - 28      webSocket = new QWebSocket();
 
 - 29      /* 需要加一些安全配置才能访问https */
 
 - 30      QSslConfiguration config;
 
 - 31      config.setPeerVerifyMode(QSslSocket::VerifyNone);
 
 - 32      config.setProtocol(QSsl::TlsV1SslV3);
 
 - 33      webSocket->setSslConfiguration(config);
 
 - 34 
 
 - 35      connect(webSocket, SIGNAL(connected()),
 
 - 36              this, SLOT(webSocketConnected()));
 
 - 37      connect(webSocket, SIGNAL(binaryMessageReceived(QByteArray)),
 
 - 38              this, SLOT(onBinaryMessageReceived(QByteArray)));
 
 - 39  }
 
 - 40 
 
 - 41  Webapi::~Webapi()
 
 - 42  {
 
 - 43      delete timer;
 
 - 44      delete webSocket;
 
 - 45      webSocket = nullptr;
 
 - 46  }
 
 - 47 
 
 - 48  void Webapi::getOrgURL()
 
 - 49  {
 
 - 50      getDataFromWeb(QUrl(orgURL));
 
 - 51  }
 
 - 52 
 
 - 53  /* 获取设备分组列表 */
 
 - 54  void Webapi::getGroupListUrl()
 
 - 55  {
 
 - 56      getDataFromWeb(QUrl(groupListUrl));
 
 - 57  }
 
 - 58 
 
 - 59  /* 获取设备的信息 */
 
 - 60  void Webapi::getDevOfGroupUrl()
 
 - 61  {
 
 - 62      getDataFromWeb(QUrl(devOfGroupUrl));
 
 - 63  }
 
 - 64 
 
 - 65  /* 获取设备连接状态  */
 
 - 66  void Webapi::getConStateUrl()
 
 - 67  {
 
 - 68      getDataFromWeb(QUrl(conStateUrl));
 
 - 69  }
 
 - 70 
 
 - 71  /* 从云服务器获取数据 */
 
 - 72  void Webapi::getDataFromWeb(QUrl url)
 
 - 73  {
 
 - 74      /* 网络请求 */
 
 - 75      QNetworkRequest networkRequest;
 
 - 76 
 
 - 77      /* 需要加一些安全配置才能访问https */
 
 - 78      QSslConfiguration config;
 
 - 79      config.setPeerVerifyMode(QSslSocket::VerifyNone);
 
 - 80      config.setProtocol(QSsl::TlsV1SslV3);
 
 - 81      networkRequest.setSslConfiguration(config);
 
 - 82 
 
 - 83      /* 设置访问的地址 */
 
 - 84      networkRequest.setUrl(url);
 
 - 85 
 
 - 86      /* 网络响应 */
 
 - 87      networkRequest.setHeader(QNetworkRequest::ContentTypeHeader,
 
 - 88                               "application/json;charset=UTF-8");
 
 - 89 
 
 - 90      /* 参数二为原子云帐号的token信息,填写自己的 */
 
 - 91      networkRequest.setRawHeader("token", api_token.toLatin1());
 
 - 92 
 
 - 93      QNetworkReply *newReply =
 
 - 94              networkAccessManager->get(networkRequest);
 
 - 95 
 
 - 96      connect(newReply, SIGNAL(finished()),
 
 - 97              this, SLOT(replyFinished()));
 
 - 98      connect(newReply, SIGNAL(readyRead()),
 
 - 99              this, SLOT(readyReadData()));
 
 - 100
 
 - 101 }
 
 - 102 void Webapi::replyFinished()
 
 - 103 {
 
 - 104     QNetworkReply *reply = (QNetworkReply *)sender();
 
 - 105
 
 - 106     if (reply->url() == QUrl(orgURL)) {
 
 - 107         /* 设备分组列表ID */
 
 - 108         getID(dataString, reply);
 
 - 109     }
 
 - 110
 
 - 111     if (reply->url() == QUrl(groupListUrl)) {
 
 - 112         /* 列表ID */
 
 - 113         getID(dataString, reply);
 
 - 114
 
 - 115         /* 获取到组ID再开启定时器 */
 
 - 116         if (!timer->isActive())
 
 - 117             timer->start(2000);
 
 - 118     }
 
 - 119
 
 - 120     /* 设备的信息 */
 
 - 121     if (reply->url() == QUrl(devOfGroupUrl)) {
 
 - 122         getID(dataString, reply);
 
 - 123         getNumber(dataString);
 
 - 124         getName(dataString);
 
 - 125     }
 
 - 126
 
 - 127     /* 设备的连接状态 */
 
 - 128     if (reply->url() == QUrl(conStateUrl)) {
 
 - 129         getConnectState(dataString);
 
 - 130     }
 
 - 131
 
 - 132     reply->deleteLater();
 
 - 133     reply = nullptr;
 
 - 134 }
 
 - 135 void Webapi::readyReadData()
 
 - 136 {
 
 - 137     QNetworkReply *reply = (QNetworkReply *)sender();
 
 - 138     QByteArray data = reply->readAll();
 
 - 139     dataString =  QString(data);
 
 - 140     qDebug()<<dataString<<endl;
 
 - 141 }
 
 - 142
 
 - 143 /* 获取ID,包括分组id,设备id */
 
 - 144 void Webapi::getID(QString data, QNetworkReply *reply)
 
 - 145 {
 
 - 146     /* 多个匹配,因为可能有多个合适的字段 */
 
 - 147     QRegularExpression pattern(""id":(\\d+)");
 
 - 148
 
 - 149     QRegularExpressionMatchIterator i = pattern.globalMatch(data);
 
 - 150     while (i.hasNext()) {
 
 - 151         QRegularExpressionMatch match = i.next();
 
 - 152         if (match.hasMatch()) {
 
 - 153             if (reply->url() == QUrl(orgURL)) {
 
 - 154                 org_id =  match.captured(1);
 
 - 155                 groupListUrl = "https://cloud.alientek.com/api/orgs/"
 
 - 156                         + org_id + "/grouplist";
 
 - 157                 getGroupListUrl();
 
 - 158                 /* Socket连接 */
 
 - 159                 webSocket->open(QUrl(QString("wss://cloud.alientek.com/connection/%1/org/%2?token=%3")
 
 - 160                                      .arg(api_token).arg(org_id).arg(random_token)));
 
 - 161             }
 
 - 162
 
 - 163             if (reply->url() == QUrl(groupListUrl)) {
 
 - 164                 group_id = match.captured(1);
 
 - 165                 /* 存储组ID,再由定时器根据组的ID获取设备信息 */
 
 - 166                 groupID.append(group_id);
 
 - 167                 qDebug()<<"组ID:"<<group_id<<endl;
 
 - 168
 
 - 169             }
 
 - 170
 
 - 171             if (reply->url() == QUrl(devOfGroupUrl)) {
 
 - 172                 device_id = match.captured(1);
 
 - 173                 /* 存储设备ID,再由定时器根据设备的ID获取连接状态 */
 
 - 174                 deviceID.append(device_id);
 
 - 175                 qDebug()<<"设备ID:"<<device_id<<endl;
 
 - 176             }
 
 - 177         }
 
 - 178     }
 
 - 179 }
 
 - 180
 
 - 181 void Webapi::getNumber(QString data)
 
 - 182 {
 
 - 183     QRegularExpression pattern(""number":"(\\d+)"");
 
 - 184
 
 - 185     QRegularExpressionMatchIterator i = pattern.globalMatch(data);
 
 - 186     while (i.hasNext()) {
 
 - 187         QRegularExpressionMatch match = i.next();
 
 - 188         if (match.hasMatch()) {
 
 - 189             device_number = match.captured(1);
 
 - 190             deviceNumber.append(device_number);
 
 - 191             qDebug()<<"设备编号:"<<device_number<<endl;
 
 - 192         }
 
 - 193     }
 
 - 194 }
 
 - 195
 
 - 196 void Webapi::getName(QString data)
 
 - 197 {
 
 - 198     /* 匹配中文字符,设备起名需要为中文 */
 
 - 199     QRegularExpression pattern(""name":"([\u4e00-\u9fa5]*)");
 
 - 200
 
 - 201     QRegularExpressionMatchIterator i = pattern.globalMatch(data);
 
 - 202     while (i.hasNext()) {
 
 - 203         QRegularExpressionMatch match = i.next();
 
 - 204         if (match.hasMatch()) {
 
 - 205             device_name = match.captured(1);
 
 - 206             deviceName.append(device_name);
 
 - 207             qDebug()<<"设备名称:"<<device_name<<endl;
 
 - 208         }
 
 - 209     }
 
 - 210 }
 
 - 211
 
 - 212 /* 获取设备的连接状态 */
 
 - 213 void Webapi::getConnectState(QString data)
 
 - 214 {
 
 - 215     QString pattern = ""data":"(\\S*)"";
 
 - 216     QRegularExpression regularExpression(pattern);
 
 - 217     QRegularExpressionMatch  match = regularExpression.match(data, 0);
 
 - 218     if(match.hasMatch()) {
 
 - 219         qDebug()<<"设备连接状态"<<match.captured(1);
 
 - 220         deviceConnectState.append(match.captured(1));
 
 - 221     }
 
 - 222 }
 
 - 223
 
 - 224 void Webapi::webSocketConnected()
 
 - 225 {
 
 - 226     qDebug()<<"webSocket连接原子云成功"<<endl;
 
 - 227 }
 
 - 228
 
 - 229 void Webapi::onBinaryMessageReceived(QByteArray str)
 
 - 230 {
 
 - 231
 
 - 232     QString temp(str);
 
 - 233     if (temp.contains("online")) {
 
 - 234         for (int i = 0; i < deviceNumber.count() ; i++) {
 
 - 235             if (temp.contains(deviceNumber[i])) {
 
 - 236                 /* 发送如客厅灯在线信号*/
 
 - 237                 emit deviceStateChanged(deviceName[i] + "|在线");
 
 - 238                 qDebug()<<deviceName[i] + "|在线"<<endl;
 
 - 239                 break;
 
 - 240             }
 
 - 241         }
 
 - 242     }
 
 - 243 }
 
 - 244
 
 - 245 /* 延时函数 */
 
 - 246 void Webapi::sleep(double second)
 
 - 247 {
 
 - 248     usleep(second * 1000000);
 
 - 249 }
 
 - 250
 
 - 251 void Webapi::onTimerTimeOut()
 
 - 252 {
 
 - 253     static int i = 0;
 
 - 254     if (i < groupID.count()) {
 
 - 255         /* 获取分组下的设备列表 */
 
 - 256         devOfGroupUrl = "https://cloud.alientek.com/api/orgs/"
 
 - 257                 + org_id + "/groups/"
 
 - 258                 + groupID[i] + "/devices";
 
 - 259         dataString.clear();
 
 - 260         getDevOfGroupUrl();
 
 - 261     } else if (i >= groupID.count()
 
 - 262                && i < groupID.count() + deviceID.count() ) {
 
 - 263         timer->start(1000);
 
 - 264         conStateUrl = "https://cloud.alientek.com/api/orgs/"
 
 - 265                 + org_id + "/devicestate/"
 
 - 266                 + deviceID[i - groupID.count()];
 
 - 267         getConStateUrl();
 
 - 268
 
 - 269     } else {
 
 - 270         /* 订阅设备的消息 */
 
 - 271         for (int j = 0; j < deviceNumber.count(); j++) {
 
 - 272             QByteArray cmd;
 
 - 273             cmd[0] = 0x01;
 
 - 274             sendCmd(deviceNumber[j], cmd);
 
 - 275         }
 
 - 276
 
 - 277         timer->stop();
 
 - 278     }
 
 - 279
 
 - 280     i++;
 
 - 281 }
 
 - 282
 
 - 283 /* 订阅指定设备的消息,cmd = 0x01 */
 
 - 284 void Webapi::sendCmd(QString number, QByteArray cmd)
 
 - 285 {
 
 - 286     QStringList list = number.split("");
 
 - 287     for (int i = 0; i < list.count(); i++) {
 
 - 288         if (!list[i].isEmpty()) {
 
 - 289             cmd.append(list[i]);
 
 - 290         }
 
 - 291     }
 
 - 292
 
 - 293     webSocket->sendBinaryMessage(cmd);
 
 - 294 }
 
 - 295
 
 - 296 /* 发送消息到指定设备,cmd = 0x03 */
 
 - 297 void Webapi::sendCmdMessage(QString number,
 
 - 298                             QByteArray cmd, QString message)
 
 - 299 {
 
 - 300     QStringList list = number.split("");
 
 - 301     for (int i = 0; i < list.count(); i++) {
 
 - 302         if (!list[i].isEmpty()) {
 
 - 303             cmd.append(list[i]);
 
 - 304         }
 
 - 305     }
 
 - 306
 
 - 307     cmd.append(message);
 
 - 308
 
 - 309     webSocket->sendBinaryMessage(cmd);
 
 - 310 }
 
 - 311
 
 - 312 void Webapi::whichDeviceNameSendCmd(QString name,
 
 - 313                                     QString message) {
 
 - 314
 
 - 315     for (int i = 0; i < deviceName.count(); i++) {
 
 - 316         if (name ==  deviceName[i]) {
 
 - 317             QByteArray cmd;
 
 - 318             cmd[0] = 0x03;
 
 - 319             sendCmdMessage(deviceNumber[i], cmd, message);
 
 - 320             break;
 
 - 321         }
 
 - 322     }
 
 - 323 }
 
 
  复制代码       第20行,需要填写自己的原子云平台帐号api_token信息,请在原子云》帐号信息中查看!        剩余的代码都按照原子云平台API文档编写,首先是通过网络请求networkRequest,访问需要访问的地址,然后通过网络回应对象newReply来接收网络回复的结果。结果是JSION格式的文本,编者使用正则表达式提取回复的内容,作为下一个地址的参数,如此反复,就可以将原子云服务器的帐号下的设备信息提取出来。        第159行,提取出来的信息转交webSocket对象,让webSocket获取原子云平台的鉴权,就可以实现通信了。        流程都是按照原子云平台API文档的走,剩下的就是webSocket通信了,与TCP,UDP的socket通信相似,这里就不多解释了,和第十一章的TCP/UDP Socket通信内容相似。重点是流程,再参考代码看。 24.6 物联网项目综合测试打开4/01_smarthome/01_smarthome/01_smarthome.pro项目,此项目为智能家居物联网UI界面控制端。 打开4/01_smarthome/esp8266/esp8266.pro项目,此项目设备端(被控端)。 打开上面两个项目如下。  
项目文件夹下内容解释:        01_smarthome项目下: l  webapi文件夹为原子云平台的应用程序,主要用来与原子云通信。 l  Headers文件夹为界面设计的头文件。 l  Sources文件夹为界面设计的源文件。 esp8266项目下: l  led文件夹为I.MX6U开发板控制LED的接口程序。 l  Headers文件夹为esp8266通信的头文件。 l  Sources文件夹为esp8266通信的源文件(使用串口通信)。 24.6.1 Ubuntu上运行运行esp8266.pro项目前请确认ESP8266 WIFI模块已经使用USB-TTL模块通过T口USB连接线连接到Ubuntu上,并赋予/dev/ttyUSB0权限才能访问这个串口。运行后串口终端打印信息如下。若模块已经连接上原子云,重新运行程序时,需要将模块断电复位才能再次通信! 注意,需要修改程序中的个人的本地WIFI帐号及密码,以及原子云上的设备的编号及密码,特别注意,这个设备必须在一个新增的分组下。设备在分组才能被后面的原子云API接口获取到。 运行esp8266.pro项目,Qt Creator的应用程序窗口输出如下。连接原子云成功后就会启用定时器,每15s向原子云服务器发送一次心跳包,webSocket应用程序收到后就会显示此设备在线。   运行01_smarthome.pro项目前请确认原子云》帐号信息处的API TOKEN信息,请填写自己的原子云帐号API TOKEN。否则您将访问到编者的原子云帐号的设备信息。   点击“客厅”开关按钮,可以看到esp8266设备应用程序收到开关信息。这个信息是通过UI界面应用程序发送到原子云服务器,然后原子云服务器转发给WIFI模块设备的。 24.6.2 ALPHA/Mini开发板运行正点原子的ALPHA/Mini Linux开发板交运行上面的01_smarthome项目和esp8266项目后,运行的结果与Ubuntu上面的是一样的,注意,主控开发板需要先连网(通过网线|USB WIFI模块|4G模块|SDIO WIFI模块),可以将正点原子的ESP8266 WIFI插在同一块开发板上进行实验,这样,点击UI界面上的开关按钮,就相当于发信息到原子云上,原子云再转发给ESP8266 WIFI模块,进而控制开发板上的LED。这样就实现了将开发板实现连接到原子云里了。  
  
 
 
 
 |