AWS IOT 离线检测功能(MQTT 遗言)

本文环境基于AWSIOT 嵌入式C lib:
https://github.com/aws/aws-iot-device-sdk-embedded-C

环境:ubuntu
语言 : C

关于AWS IOT 基础操作可参见:https://blog.csdn.net/m0_37263637/article/details/80989986
关于AWS IOT 嵌入式lib基础使用:https://blog.csdn.net/m0_37263637/article/details/81113614

aws_iot_shadow_yield(device->mqttClient, 5);//接受消息的回调不return 这里就会阻塞住<br />影子的回调均在这个函数中触发

1 MQTT 遗言

MQTT遗言消息是指:一般是连接着代理程序的客户端预定义好 LWT(Last Will and Testament)的。如果客户端异常地断开连接,代理程序(the broker)将会广播 LWT 消息到所有订阅者的客户端中。也就是当设备异常断线后,server就会发布MQTT连接到server注册的遗言消息。而我们使用这个功能就可以用来判断IOT 是否异常离线。

2 AWS IOT 遗言消息

AWS IOT 下层实质上为MQTT实现,所以同样支持mqtt遗言机制,在sample 我们可以做出相应修改进行测试。

2.1 下载相关源码并编译

2.1.1 先到github 下载aws-iot-c源码:

https://github.com/aws/aws-iot-device-sdk-embedded-C

2.1.2 准备mbedTLS

https://tls.mbed.org/download/start/mbedtls-2.11.0-apache.tgz
下载 mbedTLS lib,解压并将mbedTLS中内容放到aws-iot-device-sdk-embedded-C/external_libs/mbedTLS目录下

2.1.3 准备 CppUTest

https://github.com/cpputest/cpputest/releases/tag/v3.6
下载 tar包,解压并CppUTest中内容放到aws-iot-device-sdk-embedded-C/external_libs/CppUTest目录下

2.1.4 测试编译

cd samples/linux/subscribe_publish_sample
sudo make

编译后生成 subscribe_publish_sample 可执行文件,尝试执行
image.png
报错 ,正常 因为没有配置正确终端节点及证书

2.2 修改源码配置事务及证书

在AWS IOT 控制台创建事物并获取相应事物证书并配置权限,可参见:https://blog.csdn.net/m0_37263637/article/details/80989986
创建好事物和证书后,注意这CA证书要中国区的

2.2.1 配置证书

将aws iot后台创建的设备时下载的证书及对应root证书复制到aws-iot-device-sdk-embedded-C/certs目录下
image.png

2.2.2 修改aws sample config

到aws-iot-device-sdk-embedded-C/samples/linux/subscribe_publish_sample目录下修改配置aws_iot_config.h 配置相应事物信息,找到如下字段

#define AWS_IOT_MQTT_HOST              "" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow
#define AWS_IOT_MQTT_PORT              443 ///< default port for MQTT/S
#define AWS_IOT_MQTT_CLIENT_ID         "c-sdk-client-id" ///< MQTT client ID should be unique for every device
#define AWS_IOT_MY_THING_NAME          "AWS-IoT-C-SDK" ///< Thing Name of the Shadow this device is associated with
#define AWS_IOT_ROOT_CA_FILENAME       "rootCA.crt" ///< Root CA file name
#define AWS_IOT_CERTIFICATE_FILENAME   "cert.pem" ///< device signed certificate file name
#define AWS_IOT_PRIVATE_KEY_FILENAME   "privkey.pem" ///< Device private key filename

//说明
//AWS_IOT_MQTT_HOST:终端节点,AWSIOT 控制台 - 设置- 专有终端节点 可以看到终端节点域名
// AWS_IOT_MQTT_CLIENT_ID:clientID ,AWS后台-具体某个事物-事物 ARN
// AWS_IOT_MY_THING_NAME:事物名,AWS后台-具体某个事物-事物
// AWS_IOT_ROOT_CA_FILENAME : CA证书名,即1中拷贝进去的CA证书文件名
// AWS_IOT_CERTIFICATE_FILENAME: IOT 设备证书,即1 中拷贝进去的设备证书文件名
// AWS_IOT_PRIVATE_KEY_FILENAME:IOT设备私匙,即1 中拷贝进去的设备私匙文件名

//配置完成后可能是这样
// =================================================
#define AWS_IOT_MQTT_HOST              "xxxxxxxxxxxxx.ats.iot.cn-north-1.amazonaws.com.cn" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow
#define AWS_IOT_MQTT_PORT              443 ///< default port for MQTT/S
#define AWS_IOT_MQTT_CLIENT_ID         "AWSIOTTESTLWT" ///< MQTT client ID should be unique for every device
#define AWS_IOT_MY_THING_NAME 		     "AWSIOTTESTLWT" ///< Thing Name of the Shadow this device is associated with
#define AWS_IOT_ROOT_CA_FILENAME       "root-CA.crt" ///< Root CA file name
#define AWS_IOT_CERTIFICATE_FILENAME   "1e44fc1711-certificate.pem.crt" ///< device signed certificate file name
#define AWS_IOT_PRIVATE_KEY_FILENAME   "1e44fc1711-private.pem.key" ///< Device private key filename
// =================================================

配置好了只有可以测试sample是否可用

2.2.3 测试sample 是否可用

执行
./subscribe_publish_sample

出现如下结果即AWS IOT 配置成功
image.png

2.3 修改源码添加遗言消息

修改samples/linux/subscribe_publish_sample/subscribe_publish_sample.c 185行
屏蔽 connectParams.isWillMsgPresent = false;
添加以下内容

  connectParams.isWillMsgPresent = true;  // This needs to be set to true so that server will continue parsing the packet and look for last will configuration
  connectParams.will = iotMqttWillOptionsDefault; // This also configure the last will to be QoS0
  connectParams.will.pTopicName = "AWSIOTLWTTEST";
  connectParams.will.topicNameLen = strlen("AWSIOTLWTTEST");
  connectParams.will.pMessage = "AndyLi test  my  awsiot device last will message!";
  connectParams.will.msgLen = strlen("AndyLi test  my  awsiot device last will message!");

也就是
image.png
以上代码则是打开遗言消息。
消息topic 为AWSIOTLWTTEST
消息内容为AndyLi test my awsiot device last will message!

重新编译并执行程序

测试在AWSIOT 控制台 测试一栏 订阅AWSIOTLWTTEST 消息:
image.png
点击订阅主题后, 执行 ./subscribe_publish_sample 启动成功后 杀掉进程,可以看到后台收到了对应遗言消息
image.png
这里遗言消息功能就测试完毕了。

3 设备影子使用遗言实现离线检测功能

因为遗言机制是基于MQTT的本身协议机制实现,而通常用于管理设备状态都是用于设备影子(shadow)实现,所以很自然的想到,当设备异常离线后,如果能通过遗言消息去更新设备影子中的某个字段来表示是否离线。而AWS 官方文档中也提出了该种方案,但描述不够具体且不能通配所有设备,给使用者带来一定困扰,需要进行一定程度改造才能使用。

链接:https://docs.aws.amazon.com/zh_cn/iot/latest/developerguide/device-shadow-data-flow.html

在设备影子中使用遗言更新shadow标志位流程如下:
主要流程分为三步:

  1. 定义一个shadow属性作为设备离线标志位,每次代码运行时做出响应更新
  2. 修改AWS IOT aws-iot-device-sdk-embedded-C lib源码以支持shadow 可传入遗言消息参数。通过控制台监听遗言消息判断是否遗言消息发送成功。
  3. 配置AWSIOT规则引擎,当收到遗言消息重新发布shadow更新请求。

3.1 在事物影子中定义一个字段表示状态

image.png
在AWS IOT Thing 后台中定义一个connected 属性标识设备在线/离线状态

{
  "reported": {
    "connected": "false"
  }
}

3.2 修改aws-iot-device-sdk-embedded-C 支持LWT消息

如2.2节中 的内容修改LWT 遗言消息 来触发规则引擎,
这里通2.2节 但这里有所不同消息内容需要设置为特定的内容,通过规则引擎配置的规则后即会更新到设备影子中的connect字段

{
  "state": {
    "reported": {
      "connected": "false"
    }
  }
}

所以修改内容如下,这里用到CJSON 来封装上面这种消息格式:

 IOT_INFO("Connecting ...");

  cJSON *monitor1 = cJSON_CreateObject();
	cJSON *state1 = cJSON_CreateObject();
  cJSON *reported1 = cJSON_CreateObject();
	cJSON_AddStringToObject(reported1, "connected", "false");
	cJSON_AddItemToObject(state1, "reported", reported1);
	cJSON_AddItemToObject(monitor1, "state", state1);

	char* stringCJson = cJSON_PrintUnformatted(monitor1);//生成JSON PrintUnformatted函数为不格式化生成,生成一个紧凑的json字串
   if (stringCJson == NULL) {
        fprintf(stderr, "Failed to print monitor.\n");
   }

	int str_len = strlen(stringCJson);
  
  char* thingName = "AWSIOTTESTLWT";
  connectParams.isWillMsgPresent = true;  // This needs to be set to true so that server will continue parsing the packet and look for last will configuration
  connectParams.will = iotMqttWillOptionsDefault; // This also configure the last will to be QoS0
 
  connectParams.will.pTopicName  = (char *)malloc(50+22);  //开辟一个空间存储消息
  sprintf(connectParams.will.pTopicName , "/things/%s/shadow/update", thingName);
  connectParams.will.topicNameLen = strlen(connectParams.will.pTopicName);
  
	connectParams.will.pMessage = malloc(str_len);
  strcpy(connectParams.will.pMessage, stringCJson);
  connectParams.will.msgLen = strlen(connectParams.will.pMessage);
	
  cJSON_free(stringCJson);
  cJSON_Delete(monitor1);

3.3 配置AWS IOT规则引擎

在AWS IOT控制台选择行动,点击创建规则image.png

填入规则名及描述,填入SQL语句筛选出相应LWT消息,比如按照上面流程来的SQL 语句如下

SELECT * FROM '/things/+/shadow/update'

我们需要筛选出3.2中我们发送的的LWT消息。即/thing/ThingName/shadow/update 消息,上诉代码使用+号进行通配。筛选出符合动态ThingName的消息,而不是固定的ThingName。

配置筛选出相应LWT消息SQL语句后,应该为触发这消息添加相关的后续处理,添加下面的添加操作,选择将消息重新发布到 AWS IoT主题,去更新对应的设备shadow中的字段。这里主题需要修改该为 更新shadow的消息(发送$aws/things/ThingName/shadow/update)则会进行更新对应事物shadow,请填入下面字段中的内容,这里可能需要相应的权限,需要去IAM中创建。

$$aws${topic()}

添加好的规则如下图:
image.png
添加好,保存规则即可开始测试。
测试当你杀掉IOT 程序时,离线标志位connected就会被更新为false。达到预期效果。

3.4 测试

我们在控制台订阅MQTT消息看 杀掉程序是否收到遗言消息:
image.png
good 收到了 我们先要的遗言消息,既然收到了这个遗言消息,如果规则引擎有效就会将该消息转换成更新shadow的消息 $aws/things/ThingName/shadow/update, 这里订阅下这个消息尝试一下:
image.png同样也收到了对应的消息,最后看下shadow的行为。先将shadow中connect 更改为true。(PS:这其实也是IOT的行为,当程序启动时应该更新shadow 中的connected 属性为true)。
image.png
运行程序,然后杀掉程序。可以看到connected 从true 编程false。到这里我们就完成了AWS IOT 利用MQTT消息实现离线检测功能。

3.5 遗言消息的行为

我们在配置MQTT时,其中在下层有一个属性keepAliveIntervalInSec

ConnectParams->keepAliveIntervalInSec = 600; // NOTE: Temporary fix

如果是手动杀掉会立刻触发遗言消息,而如果是断网,设备掉电,则则会更根据这个属性来触发遗言消息。
比如默认设置是600s。 即设备异常10分钟后才会触发遗言消息。

4 测试code

环境:X86 Linux gcc
github: https://github.com/CollapsarLi/aws_iot_c_shadow_lwt_sample.git
把github中的代码放到samples/linux/subscribe_publish_sample 目录下覆盖原sample 并参照2.2.2 中配置aws_iot_config 及 AWSIOT 控制台规则引擎。进行测试。
好用请start (#.#)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章