阿里云物联网及MNS初探

最近公司有个物联网项目,想试试由自有平台切换到阿里云物联网平台,于是花了点时间对阿里云物联网平台小探了下,这里做个小结,给有同样需求的朋友提供一个参考。
因能力有限,以及考虑到实际业务逻辑,我只是研究了一下我所需要的东西,不敢保证能有多深入,只求能将我所了解了的东西表达出来。

项目前提

起源是公司有个项目,硬件设备后期可能会全国各地都有部署,需要通过2G/3G/4G/5G/WiFi网络及MQTT协议连接到平台服务器,考虑到实际业务需求,所使用的平台必须要能提供保证如下功能:

  • 能保证稳定在线,且能在所能考虑的将来容纳需要接入的设备
  • 支持与PHP后端系统对接
  • 因涉及到用户交互控制,系统上报的消息必须在100ms左右到达后端系统
  • 数据可控,不会出现数据拿不到的情况

我们有一套自己开发的平台,但是想试试利用阿里云物模型,实现快速开发部署,以及借鉴阿里云这种大平台的实现方式,想体验一下阿里云物联网平台,于是有了这篇文章。

阿里云物联网数据流传过程

和自建平台差不多,据我的了解,阿里云物联网平台的数据流传过程包含如下几个步骤:

  • 新建产品、设备,获取相应的ProductKey、DeviceName、DeviceSecret等信息并通过SDK等接入物联网平台
  • 设备上传心跳包、业务数据及报警数据等到物联网平台
  • 服务端订阅,通过HTTP/2或者MNS拿到数据
  • 服务端处理数据
  • 服务端将指令推送到物联网平台
  • 设备执行相应的指令

下面是详细步骤。

设备接入阿里云物联网平台

打开阿里云物联网平台:https://iot.console.aliyun.com ,忘了第一次进入要不要进行注册了,如果要的话注册或者购买一下就好了。
因为终端设备在阿里云物联网平台是依附于产品的,所以在设备接入之前,需要先创建一个产品,然后才能添加设备。
然后点击左侧的产品,进入页面之后,点击右上角的创建产品,打开产品创建页面,截图如下:
aliyun-iot-create-product.png
嗯….忽略上述某个单词拼写错误。
产品名称需要唯一,后面创建设备需要用到。而所属分类,阿里云已经针对目前常见的很多设备做了分类,比如家电、安防等,但是因为我们项目所属的设备都不包含在阿里云预定分类里,我这选的是自定义分类。至于是终端设备还是网关以及是否接入网关,根据需求确定。
而至于联网方式,阿里云物联网平台提供WiFi、2G/3G/4G、以太网也就是网线以及一个LoRaWAN接入,这个貌似有点不便,我们购买的DTU都是支持不止一种接入方式的,比如同时支持蜂窝及WiFi。而数据接入方式,因为想体验一下阿里云的物模型,我这里选择Alink JSON格式。
创建成功之后,进入设备页面,创建相应的设备信息:
aliyun-iot-create-device.png
创建成功之后,阿里云会立即给出设备的相关连接信息,如图:
aliyun-iot-device-info.png
这个保不保存都可以,后续可以在设备详情页面找到。

设备关联物模型

创建好产品之后,其实就应该关联物模型,阿里云关于物模型的解释如下:

物模型是对设备在云端的功能描述,包括设备的属性、服务和事件。物联网平台通过定义一种物的描述语言来描述物模型,称之为 TSL (即Thing Specification Language),采用JSON格式,您可以根据TSL组装上报设备的数据。您可以导出完整物模型,用于云端应用开发;您也可以只导出精简物模型,配合设备端SDK实现设备开发。

这个概念用我的理解说人话就是,定义设备能干啥、能执行什么命令等,定义好物模型可以更好地处理设备传来的数据。
根据选择的设备类型的不通,阿里云已经预定义了一些物模型,如图,添加一个示例物模型:
aliyun-iot-device-alink.png

连接设备到阿里云物联网平台

由于是测试,我这里选择使用直接运行阿里云提供的设备SDK的方式连接到阿里云,相关C语言SDK下载地址:

https://code.aliyun.com/linkkit/c-sdk/repository/archive.zip

使用阿里云提供的SDK只需要将设备的相关账号key之类填上直接编译就能运行。
根据阿里云文档,这个SDK需要在Linux下运行,官方推荐是在Ubuntu 16.04,我在Ubuntu 18.04下编译运行也没问题,相关命令如下:

apt-get install -y build-essential make git gcc unzip
wget https://code.aliyun.com/linkkit/c-sdk/repository/archive.zip
unzip archive.zip

然后编辑对应的文件,将对应的设备连接信息加入就行,命令如下:

vim wrappers/os/ubuntu/HAL_OS_linux.c
    #ifdef DEVICE_MODEL_ENABLED
        char _product_key[IOTX_PRODUCT_KEY_LEN + 1]       = "a1RIsMLz2BJ";
        char _product_secret[IOTX_PRODUCT_SECRET_LEN + 1] = "fSAF0hle6xL0oRWd";
        char _device_name[IOTX_DEVICE_NAME_LEN + 1]       = "example1";
        char _device_secret[IOTX_DEVICE_SECRET_LEN + 1]   = "RDXf67itLqZCwdMCRrw0N5FHbv5D7jrE";

对应的ProductKey、ProductSecret、DeviceName、DeviceSecret可在阿里云的控制面板的产品及设备详情页面找到,对应修改之后,就可以开始编译了:

make distclean
make

1分钟不到即可编译好,成功之后运行如下命令运行编译之后的可执行程序:

./output/release/bin/linkkit-example-solo

执行之后控制台会有输出:



user_initialized.81: Device Initialized user_connected_event_handler.63: Cloud Connected > { > "id": "2", > "version": "1.0", > "params": { > "Counter": 0 > }, > "method": "thing.event.property.post" > } user_post_property.231: Post Property Message ID: 2 > { > "id": "3", > "version": "1.0", > "params": { > "ErrorCode": 0 > }, > "method": "thing.event.HardwareError.post" > } user_post_event.242: Post Event Message ID: 3 < { < "code": 200, < "data": { < "Counter": "tsl parse: params not exist" < }, < "id": "2", < "message": "success", < "method": "thing.event.property.post", < "version": "1.0" < } < { < "code": 460, < "data": { < }, < "id": "3", < "message": "event not exist", < "method": "thing.event.HardwareError.post", < "version": "1.0" < } user_report_reply_event_handler.93: Message Post Reply Received, Message ID: 2, Code: 200, Reply: {"Counter":"tsl parse: params not exist"} user_trigger_event_reply_event_handler.104: Trigger Event Reply Received, Message ID: 3, Code: 460, EventID: HardwareError, Message: event not exist

阿里云控制台能看到终端已经在线了:
aliyun-iot-device-online.png
虽然有报错,但是至少连接到了阿里云。这个报错也诡异,竟然在Google都搜索不到是因为什么报错的。下一步就是通过阿里云物联网平台拿到设备上报的数据了。

服务端订阅阿里云物联网平台数据

阿里云物联网平台提供了两种方式供我们开发者获取设备的数据,分别是HTTP/2推送以及MNS消息服务,两种都可以获取数据,其中HTTP/2是物联网平台原生支持的,MNS相当于通过了一层中转,数据可能有一定的延迟,如果可以,当然推荐使用HTTP/2了。
但是这里有个问题就是,HTTP/2推送SDK目前(2019.08)只支持Java和.Net两种开发语言的SDK(来源),如果后端是Java和.Net,可以考虑使用HTTP/2,但是因为我们是PHP,所以只能选择使用MNS推送的方式进行获取数据。
要通过MNS获取数据,还需要在物联网后台设置一下,让阿里云将设备上传的数据转发到MNS服务。

将物联网数据转发到MNS

点击阿里云控制台左侧的产品连接,从产品列表进入对应的产品详情页面,然后切换到服务端订阅选项卡,如图:
aliyun-iot-product-detail.png
然后点击服务端订阅(推送MNS)右侧的设置按钮,在弹出的窗口选择需要的消息类型,然后保存,如图:
aliyun-iot-data-to-mns.png
保存完毕之后会给出对应的区域及队列名称,这个在后面使用MNS服务的时候用到,如图:
aliyun-iot-mns-info.png

通过阿里云MNS获取物联网设备数据

设置好了之后,我们即可以通过MNS获取到我们设备上传的数据,于此同时,我们也能在MNS控制板看到我们创建的MNS队列信息,MNS控制面板地址:

https://mns.console.aliyun.com

进去之后,可能会提示创建一个子账户,为了安全,根据提示创建一个就好。
而MNS的SDK就比较多了,PHP版的相关下载地址:https://github.com/aliyun/aliyun-mns-php-sdk
下载之后,解压,其中有个Samples文件夹,里面有几个示例文件,其中的大部分功能我们用不到,如果只是拿数据,只需新建一个文件,然后贴入如下代码即可:

<?php
require_once(dirname(dirname(dirname(__FILE__))).'/mns-autoloader.php');
use AliyunMNS\Client;
use AliyunMNS\Requests\SendMessageRequest;
use AliyunMNS\Requests\CreateQueueRequest;
use AliyunMNS\Exception\MnsException;
class CreateQueueAndSendMessage
{
    private $accessId;
    private $accessKey;
    private $endPoint;
    private $client;
    public function __construct($accessId, $accessKey, $endPoint)
    {
        $this->accessId = $accessId;
        $this->accessKey = $accessKey;
        $this->endPoint = $endPoint;
    }
    public function run()
    {
        $queueName = "aliyun-iot-a1Wxxxxxx";
        $this->client = new Client($this->endPoint, $this->accessId, $this->accessKey);
        $queue = $this->client->getQueueRef($queueName);
        // receive message
        $receiptHandle = NULL;
        try
        {
            // when receiving messages, it's always a good practice to set the waitSeconds to be 30.
            // it means to send one http-long-polling request which lasts 30 seconds at most.
//            $res = $queue->receiveMessage(30);
            $res = $queue->batchReceiveMessage(new \AliyunMNS\Requests\BatchReceiveMessageRequest(16,30) );;
            print_r($res);
        }
        catch (MnsException $e)
        {
            echo "ReceiveMessage Failed: " . $e;
            return;
        }
    }
}
$accessId = "LTAIU7YoxxxxxxxxUCy";
$accessKey = "XzcKWzfzOuxxxxxxxxxxxxxxxxxxx9Vzr";
$endPoint = "https://xxxxx.mns.cn-shanghai.aliyuncs.com/";
if (empty($accessId) || empty($accessKey) || empty($endPoint))
{
    echo "Must Provide AccessId/AccessKey/EndPoint to Run the Example. \n";
    return;
}
$instance = new CreateQueueAndSendMessage($accessId, $accessKey, $endPoint);
$instance->run();
?>

上面的queueName就是物联网控制台中的队列字段,也就是上一张截图的队列字段,accessId和accessKey字段是新建的子账户信息,至于endPoint字段,是在MNS控制面板的右上角,如图:
aliyun-mns-get-endpoint.png
如果一切没问题的话,应该能拿到数据了,示例输出如下:

➜  Queue php GetMessage.php
AliyunMNS\Responses\BatchReceiveMessageResponse Object
(
    [messages:protected] => Array
        (
            [0] => AliyunMNS\Model\Message Object
                (
                    [receiptHandle:protected] => 8-0zumy92qPzZzxxxxxxx31jdjfqb
                    [messageBody:protected] => {"payload":"eyJsYXN0VGltZSI6IjIwMTktMDgtMDEgMjA6NTI6MzxxxxdXRjTGFzdFRpbWUiOiIyMDE5LTA4LTAxVDEyOjUyOjMyLjM5NloiLCJjbGllbnRJcCI6IjEwNi4xxxx0LjIwMCIsInV0Y1RpbWUiOiIyMDE5LTA4LTAxVDEyOjUyOjMyLjQwNVoiLCJ0aW1lIjoixxxxxxxMjozMi40MDUiLCJwcm9kdWN0S2V5IjoiYTF2WDBSV3dLYjQiLCJkZXZpY2VOYW1lIjoiTm9kZUVkZ2VQcm9kdWN0MURldmljZTEiLCJzdGF0dXMiOiJvbmxpbmUifQ==","messagetype":"status","topic":"/as/mqtt/status/a1vX0Rxxxb4/cloudboolProduct1Device1","messageid":1156910584684348928,"timestamp":1564663952}
                    [enqueueTime:protected] => 1564663952534
                    [nextVisibleTime:protected] => 1564665219953
                    [firstDequeueTime:protected] => 1564665159953
                    [dequeueCount:protected] => 1
                    [priority:protected] => 8
                    [messageId:protected] => 367C78D83BAxxxx8047ABC959695
                    [messageBodyMD5:protected] => 43E3411F1828xxxxF155B9014C2833
                )
        )
    [base64:protected] => 1
    [succeed:protected] => 1
    [statusCode:protected] => 200
)

进一步操作的话,解析其中的messageBody字段下面的payload字段,将其base64解码之后能得到如下json数据:

{
    "lastTime": "2019-08-01 20:52:32.396",
    "utcLastTime": "2019-08-01T12:52:32.396Z",
    "clientIp": "106.xx6.xxx.200",
    "utcTime": "2019-08-01T12:52:32.405Z",
    "time": "2019-08-01 20:52:32.405",
    "productKey": "a1vX0xxxxb4",
    "deviceName": "cloudboolProduct1Device1",
    "status": "online"
}

到这一步,我们设备连接和获取设备数据就算完成了,还剩一步就是通过阿里云物联网云端平台给终端发送指令,但这一步我并没有去研究,原因参考下面的总结。

阿里云物联网平台总结

阿里云物联网平台是否可用?

可用,但是就目前来说,对我们不是很友好,因为HTTP/2没有PHP版本的SDK,只能通过MNS定时获取数据,然后业务处理,再调用阿里云物联网平台API将指令发送到设备,这对我们有用户交互的物联网项目设备来说,延迟太高,无法接受。

MNS是否有缺陷?

对我们来说,有的。可能上面的例子没有说明,其实MNS服务一次只能批量获取到16条数据,报错举例:

ReceiveMessage Failed: Code: 400 Message: The value of numofmessages should between 1 and 16 MnsErrorCode: InvalidArgument RequestId: 5D42E946433xxxxE25F11DC6 HostId: http://xxxxxxxx.mns.cn-shanghai.aliyuncs.com

考虑到后期设备太多,这对我们来说完全不够,可能一秒1600条数据都不不止,再考虑到物联网到MNS服务,以及加上期间网络本身开销,还有HTTPS握手等步骤,延迟实在是太高,业务会受此影响。
当然,这部分只是对于我们的实际情况而言,如果后端是用的Java或者.Net,还是可以考虑走HTTP/2降低延迟的。
所以,考虑到如上这些问题,我并没有测试从我们的用户UI再到我们的服务端给设备终端发送指令,因为到获取数据这一步就放弃了阿里云物联网平台,最终还是选择了自建MQTT服务器直接进行业务逻辑处理。
相关文档:
https://help.aliyun.com/product/30520.html
https://help.aliyun.com/product/27412.html