查看: 295|回复: 3

[讨论交流] 阿里云IoT套件测试5:管理多个设备(单个ESP32)

[复制链接]
本帖最后由 szjuliet 于 2019-5-30 10:55 编辑

管理多个设备(单个ESP32)


阿里云IoT套件测试1:开箱、软件安装及示例测试
阿里云IoT套件测试2:IoT平台注册、创建产品及设备
阿里云IoT套件测试3:创建物联网移动应用开发
阿里云IoT套件测试4:智能LED灯
阿里云IoT套件测试5:管理多个设备(单个ESP32)


依据Starter Kit for Aliyun IoT 教程测试:链接地址


感谢@绿水无痕的技术指导!!

演示视频


测试预期目标:
  • 搭建一个模拟智能家居环境,实现以下功能:
  • FireBeetle连接了多个设备:灯、风扇和继电器
  • 在一个APP中可以同时管理多个设备

所需元件:
  • DFR0478   FireBettle Board-ESP32(焊接排母)x1
  • DFR0483   FireBeetle Covers-Gravity Adapter Board(焊接排针)x1
  • DFR0031-R 数字食人鱼红色LED发光模块x1
  • DFR0017   继电器模块 x1
  • FIT0011    数字传感器连接线 x3
  • 小风扇x1
  • 智能手机 x1

说明:
如果将继电器接上插座,就可以实现控制现实生活中的设备。智能插座的项目我在2016年指导学生小课题做过类似的项目,只是通信方式是BLE,使用的是Arduino101,移动端开发使用的是App Inventor。阿里云这个项目的继电器也可以这样来控制,因为时间关系,这一步我就省略了,做法是一样的。下面是Arduino101通过BLE控制电器开关的演示视频。


解决的主要问题

这个项目虽然看上去只是完成了一个简单的功能(实际上我刚开始也觉得这个目标应该很容易实现),但是做了以后才发现不容易。这个项目很是折腾了几天。教程给出的项目都是一个产品一个设备,每次只能控制一个设备。我的设想是控制多个设备。
1. 解决控制多个设备的控制问题:
我采用了一个产品下挂多个设备的方式。在云平台上使用在线模拟虽然可以控制所有设备的开关,但是在手机上却只能控制一个设备,观察串口监视器显示的publish的信息发现虽然控制了不同的设备,但是回调函数返回的topic都是第1个设备(LED)的。向技术人员@绿水无痕 请教后对设置进行了修改,一个产品下只有一个设备,产品自定义功能全部归属到这个设备中,这样就保证subTopic和pubTopic都是唯一的,手机可以控制所有设备。
2. 解决控制的逻辑问题:
虽然可以控制多个设备,但是奇怪的是开启某个设备会把前面开的设备关掉,每次只能有一个设备处于开启的状态,这个和现实是有冲突的。和@绿水无痕多次沟通,在他的帮助下解决了问题。后面会详细说明。

以下是项目测试的详细步骤

一、阿里云IoT设置

实现单ESP32下控制多个设备按下面的流程来进行:
新建项目-->新建产品-->自定义功能(灯,风扇,插座)-->新增设备(一个)-->移动应用开发-->每个控制对应产品的一个自定义功能。


1. 新建项目:
  • 登录阿里云并进入物联网平台控制台,在左侧导航栏选择开发服务->IoT Studio,单击新建项目,根据提示,填写信息。项目名称为“智能家居管理”
ali03.png

2. 新增(或导入已有)产品:
  • 在左侧导航栏选择设备管理->产品,单击新增一个产品(或导入产品,如已有),根据提示,填写信息。产品名称为“智能家居”
ali04.png
3. 为产品添加自定义功能:
  • 点击产品智能家居-->功能定义-->自定义功能-->添加功能,为智能家居添加属性:灯的状态,标识符为LightStatus,数据类型为bool(布尔型),0为关闭,1为打开,读写类型为“读写
ali_iot10.png

  • 同样的方法添加三个自定义功能:风扇状态、插座状态、红外状态,标识符分别为LightFanStatus、RelayStatus、PIRStatus,数据类型为bool(布尔型),0为关(无),1为开(有),读写类型为“读写”。添加完后显示如下:
ali06_function.png
说明:这里添加了红外,是准备后面做智能防盗用的。

4. 新增设备:
  • 在左侧导航栏选择设备管理->设备,单击新增设备,会自动生成一个设备。
ali06.png

  • 设备生成后会出现在设备列表中

ali06_credential1.png

  • 点击上图中右下角的“激活凭证”,可以查看凭证,将凭证复制并保存到安全的位置,我们在后面的程序中会用到这些凭证
ali06_credential2.png

5. 新增可视化应用:
  • 在左侧导航栏选择推荐->移动应用开发,点击右方新增可视化应用,根据实际填写信息:
ali_iot20.png

  • APP用户界面设置


  • 为设备配置数据


  • 在编辑中,点击首页模块-->列表页-->新增页面入口-->配置跳转链接,选择跳转到“智能家居管理”页面,修改标题和描述,上传自定义图片作为图标。
ali15_Link.png

  • 点击右上角构建-->Android构建,将APP打包并安装到手机上并运行。第一次运行需要输入用户名和密码,这个是在账号中设置:
ali16_accountAdd.png

二、线路连接
ali17_connectDevice.png
LED灯接D2,风扇接D4,继电器接D6

三、程序编写
选择样例文件中的任何一个进行改写。
改写的部分代码如下:

1. 定义3个设备的引脚
[C++] 纯文本查看 复制代码
#define LIGHT  D2
#define FAN D4
#define RELAY D6

2. 定义标识符对应每种物理设备
[C++] 纯文本查看 复制代码
/*需要操作的产品标识符*/
String Identifier_Light = "LightStatus";
String Identifier_Fan = "FanStatus";
String Identifier_Relay = "RelayStatus";

3. 三个设备的开关代码
[C++] 纯文本查看 复制代码
static void openLight(){
  digitalWrite(LIGHT, HIGH);
}
static void closeLight(){
  digitalWrite(LIGHT, LOW);
}

static void openFan(){
  digitalWrite(FAN, HIGH);
}

static void closeFan(){
  digitalWrite(FAN, LOW);
}

static void openRelay(){
  digitalWrite(RELAY, HIGH);
}

static void closeRelay(){
  digitalWrite(RELAY, LOW);
}

4. 修改回调函数
[C++] 纯文本查看 复制代码
void callback(char * topic, byte * payload, unsigned int len){
  Serial.print("Recevice [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < len; i++){
    Serial.print((char)payload);
  }
  Serial.println();
  StaticJsonBuffer<300> jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject((const char *)payload);
  if(!root.success()){
    Serial.println("parseObject() failed");
    return;
  }

  const uint16_t LightStatus = root["params"][Identifier_Light];
  if(LightStatus == 1){
    openLight();
  }else{
    closeLight();
  }
  Serial.println("LightStatus:");
  Serial.println(LightStatus);
  
  const uint16_t FanStatus = root["params"][Identifier_Fan];
  if(FanStatus == 1){
    openFan();
  }else{
    closeFan();
  }
  Serial.println("FanStatus:");
  Serial.println(FanStatus);
  
  const uint16_t RelayStatus = root["params"][Identifier_Relay];
  if(RelayStatus == 1){
    openRelay();
  }else{
    closeRelay();
  }
  Serial.println("RelayStatus:");
  Serial.println(RelayStatus); 

  String tempMseg_Light = "{\"id\":"+ClientId+",\"params\":{\""+Identifier_Light+"\":"+(String)LightStatus+"},\"method\":\"thing.event.property.post\"}";
  char sendMseg_Light[tempMseg_Light.length()];
  strcpy(sendMseg_Light,tempMseg_Light.c_str());
  client.publish(pubTopic,sendMseg_Light);
  
  String tempMseg_Fan = "{\"id\":"+ClientId+",\"params\":{\""+Identifier_Fan+"\":"+(String)FanStatus+"},\"method\":\"thing.event.property.post\"}";
  char sendMseg_Fan[tempMseg_Fan.length()];
  strcpy(sendMseg_Fan,tempMseg_Fan.c_str());
  client.publish(pubTopic,sendMseg_Fan); 
   
  String tempMseg_Relay = "{\"id\":"+ClientId+",\"params\":{\""+Identifier_Relay+"\":"+(String)RelayStatus+"},\"method\":\"thing.event.property.post\"}";
  char sendMseg_Relay[tempMseg_Relay.length()];
  strcpy(sendMseg_Relay,tempMseg_Relay.c_str());
  client.publish(pubTopic,sendMseg_Relay);

5. 定义引脚输出模式
[C++] 纯文本查看 复制代码
  pinMode(LIGHT,OUTPUT);
  pinMode(FAN,OUTPUT);
  pinMode(RELAY,OUTPUT); 


四、调试程序

在调试中会遇到很多错误,如编译错误,程序运行错误。编译错误比较好解决,程序运行错误就要仔细分析串口监视器的返回内容,查找错误原因。在DF给的KIT中有许多.h头文件,根据需要打开相应的头文件了解程序运行的机制,还可以根据错误返回信息查找头文件来找出错误原因。
ali11_errorCode_monitor.png
这个错误可以看到是MQTT连接失败,rc=4,返回的错误代码是4,我们找到相应的头文件PubSubClient.h可以查看到返回代码4是凭证错误,也就是三联码:ProductKey,DeviceName和DeviceSecret中的一个或几个有问题,检查后发现确实有一个凭证是错的,修改后问题解决。从下图中可以看到返回代码所代表的含义:
ali11_errorCode.png

MQTT连接超时 -4
MQTT连接丢失 -3
MQTT连接失败 -2
MQTT断开连接 -1
MQTT连接成功 0
MQTT连接协议错误 1
MQTT连接ClientId错误 2
MQTT连接不可用 3
MQTT连接凭证错误 4
MQTT连接未授权 5

进行在线模拟的时候发现虽然在平台上可以控制设备的开关,但是手机上只能控制灯的开关,其他两种设备无法控制,查看串口监视器发现回调函数返回的Topic是有问题的。这是因为我们在一个产品下有多个设备,这样造成Topic不唯一,解决方法是在一个产品下只增加一个设备,产品下面添加自定义功能,每个功能对应设备的一种属性。
ali08_devices_table.png

按照上面的想法修改程序后,在线模拟以及手机都可以控制所有设备,但是奇怪的是在打开某个设备时也会关闭其他开着的设备,如下面的视频演示:


与@绿水无痕进行探讨,他分析原因应该是“LightStatus是ArduinoJson中的子对象,没法用containSkey判断,只能用root["params"]["LightStatus"]获取,如果没有的话自动返回NULL,会与Bool类型的0冲突,导致无法判断”,即每次发送命令时只有一个设备的状态被发送,另外两个没有发送,而没有发送系统返回null,即0。所以应付对其他两个设备采取关闭的操作,导致上面所演示的错误发生。

解决方法是引入3个变量来读取返回的topic里是否包含了对应的标识符,如果没有包含就不执行任何操作(保持设备原有的状态);如果包含再判断返回来的值是1还是0,1就执行开启操作,0执行关闭操作。

修改回调函数:
[C++] 纯文本查看 复制代码
void callback(char * topic, byte * payload, unsigned int len){
  Serial.print("Recevice [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < len; i++){
    Serial.print((char)payload);
  }
  Serial.println();
  StaticJsonBuffer<300> jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject((const char *)payload);
  if(!root.success()){
    Serial.println("parseObject() failed");
    return;
  }
  
  const char* val1=root["params"][Identifier_Light];
  const char* val2=root["params"][Identifier_Fan];
  const char* val3=root ["params"][Identifier_Relay];
  if(val1!=NULL)
  {
      Serial.println("++++++++++");
      const uint16_t LightStatus = root["params"][Identifier_Light];
      if (LightStatus == 1){
        openLight(); 
      }else{
        closeLight();  
        }
  String tempMseg = "{\"id\":"+ClientId+",\"params\":{\""+Identifier_Light+"\":"+(String)LightStatus+"},\"method\":\"thing.event.property.post\"}";
  char sendMseg[tempMseg.length()];
  strcpy(sendMseg,tempMseg.c_str());
  client.publish(pubTopic,sendMseg);
  }

  if(val2!=NULL)
  {
      Serial. println("----------");
      const uint16_t FanStatus = root["params"][Identifier_Fan];
      if (FanStatus == 1){
        openFan();
      }else{
        closeFan();  
        }  
  String tempMseg = "{\"id\":"+ClientId+",\"params\":{\""+Identifier_Fan+"\":"+(String)FanStatus+"},\"method\":\"thing.event.property.post\"}";
  char sendMseg[tempMseg.length()];
  strcpy(sendMseg,tempMseg.c_str());
  client.publish(pubTopic,sendMseg);           
  }

  if(val3!=NULL)
  {
      Serial. println("**********");
      const uint16_t RelayStatus = root["params"][Identifier_Relay];
      if (RelayStatus == 1){
        openRelay();
      }else{
        closeRelay();  
        }
  String tempMseg = "{\"id\":"+ClientId+",\"params\":{\""+Identifier_Relay+"\":"+(String)RelayStatus+"},\"method\":\"thing.event.property.post\"}";
  char sendMseg[tempMseg.length()];
  strcpy(sendMseg,tempMseg.c_str());
  client.publish(pubTopic,sendMseg);  
  }
}


程序修改完后运行,执行开启设备命令后串口监视器如下:
ali14_Serial.png

提升
在现实家居生活中有多个设备,单用一个ESP32肯定是不够也不方便,可以用多个ESP32,每个ESP32下可以挂多个设备。每个ESP32程序结构大致一样,不需要做过多的修改。只需要在阿里云的项目中增加产品,每个产品对应一个ESP32即可。手头有FireBeetle,掌控板,Linkit7697,还有Obloq,ArduinoUNO WiFi,等有机会试验一下。


无力吐槽一下阿里云。这个平台目前还是不太稳定,总会有奇奇怪怪的问题出现:删除的设备在组件属性中还是会出现,即使更新并保存,过一会儿又恢复已删除的东西;打包的app运行后点进页面出现“访问发现错误,当前版本没有打包”,重新开发一个新的app才运行正常;23号更坑,打包的app一运行就闪退,换电脑,换手机,换网络,重新构建...试了各种方法仍然不行,24号早上才得知是阿里云本身出了问题,最近更新出了bug,这是得多大的bug,太不应该了啊!另外已经在线模拟或使用的app不能删除也是一个大槽点,太不方便了。
倒是DF的器材性能很稳定,尤其主控板连接很迅速,响应也很快。赞一个!!

Ali_MultiDevices.zip

2.09 KB, 下载次数: 3, 下载积分: 创造力 -1

售价: 10 创造力  [记录]

INO文件

安卓机器人  初级技神

发表于 2019-5-25 11:35:51

赞!很认真、好详细,收藏
回复 支持 反对

使用道具 举报

szjuliet  版主
 楼主|

发表于 2019-5-25 13:50:31

安卓机器人 发表于 2019-5-25 11:35
赞!很认真、好详细,收藏

回复 支持 反对

使用道具 举报

senghu  初级技师

发表于 2019-5-25 21:23:14

好棒!解决了我这几天的困惑
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
wifi气象站

硬件清单

btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2019 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail