最近半年一直在尝试传统行业物联网相关技术和战略规划,结合阿里云和阿里云IoT平台,初步实现了几个主要业务功能,整理分享如下:

1. 背景

核心诉求是终端设备接入物联网:一方面是为了数据的远程交互,另一方面是数据的收集和应用

目前终端种类繁多,未统一平台和标准,操作系统覆盖Windows、Android、Linux. 而IoT模组又依据主流通讯技术分成2G、3G、4G和NB-IoT,不同通讯技术的模组使用的协议不尽相同,这对于终端仪器开发的同学来说是个痛点,各个操作系统和开发语言均需对接IoT模组,一旦模组被更换,又需要重新匹配新的协议。

数据接收端也不好过,不同的运营商有自己的IoT平台,管理物联网卡和数据收发。更有运营商强制要求指定型号的模组只能用自家平台进行数据管理(如移远 BC95-5 只能使用电信Easy IoT平台)。应用系统开发需要维护多个运营商IoT平台,接口亦需开发多套。

所以我们想设计一套解决方案,在满足核心诉求的前提下,减少终端和应用系统接入IoT的难度。

2. 架构

阿里提供了一套Link Kit SDK,如下图所示,用于终端快速接入阿里云IoT平台

Link Kit SDK首先是支持多语言及跨平台的,支持MQTT、CoAP、HTTP/S协议,数据直接对接阿里云IoT平台,“没有中间运营商赚差价”,对于不支持TCP/UDP的终端,阿里也提供了网关做统一收发,这就让架构精简了不少。但依然存在一些没填平的坑:

  • 终端开发仍需要学习 Link Kit SDK,而应用开发需要学习阿里云AIoT平台

  • SDK支持的协议只能发送到阿里云IoT平台,数据转储也只能发送到MQ、RDS等阿里云的SaaS或PaaS。同时目前首国家与运营商的政策,某些模组只能接入运营自己的平台(前文提到的BC95-5),云服务商并不能通吃

  • SDK需三元组注册,注册分两种,但不论哪一种都比较麻烦,批量的设备更希望动态注册

    • 一机一密:在设备上烧写设备的ProductKey、DeviceName、DeviceSecret,然后适配相应的HAL并调用SDK提供的连接云端的函数即可,这种方式要求对设备的产线工具进行一定的修改,需要对每个设备烧写不同的DeviceName和DeviceSecret;
    • 一型一密:设备上烧写设备的ProductKey、ProductSecret,每个设备需要具备自己的唯一标识并将该标识预先上传到阿里云IoT物联网平台,然后调用SDK提供的函数连接云端。这种方式每个设备上烧写的信息是固定的ProductKey、ProductSecret

最终设计了一套基于阿里云IoT且更为通用的解决方案架构

我们根据不同的操作系统、不同的开发语言做中间件,中间件再调用Link Kit SDK,同时中间件实现动态注册和封装一些面向上位机的标准化业务接口,如数据通讯、文件下载、远程控制等。对于不能接入阿里云IoT的模组,中间件还需要直接对接到运营商平台。

对于终端设备上位机来说,中间件就是SDK,他们只需按需调用标准化的业务接口,无需关注IoT模组、协议即可快速接入IoT平台。而对于后台系统,我们也从阿里云IoT平台和运营商平台收集数据,进行标准化后再输出给应用系统。从而实现双向快速接入IoT.

3. 技术

3.1 阿里云MQ的设计和使用

中间件包含阿里云IoT和运营商直连两部分,这里我们只讨论阿里云IoT平台这一块,数据流转方式如下图所示,其中SDK服务端订阅模式支持的语言有限,使用场景有限,根据终端数量和TPS,最终选择了规则引擎转发到RocketMQ

阿里云IoT平台目前在国内只有华东2有节点,但平台的规则引擎可以转发到任意一个区域节点,比如我们的RocketMQ实例在华南区,又因为RocketMQ的Topic、Tag机制如下图所示:

所以整体方案架构为:

  • 一个应用实例可以开一个RocketMQ消费者/生产者,用一个GID,其集群也是同一个GID
  • 消费者、生产者可注册或订阅多个Topic
  • 用DeviceID作为Tag用于区分是哪个终端发的消息
  • 在物联网平台需针对物联网平台的产品Topic设置转发规则到RocketMQ

3.2 rocketmq-client-go踩坑

由于我们技术栈是Go系,阿里云IoT平台的MQ是Rocket,原生只支持Java和C++,HTTP协议虽然通用,但效率过于低下,Go的SDK是社区版,阿里云支持有限。经过一番摸索,也算是填平了几个大坑。

首先Go的SDK分原生和非原生,非原生是cgo加RocketMQ C++SDK,在 https://github.com/apache/rocketmq-client-go 的Master分支下,使用时间较久,稳定性较高,但编译麻烦。

原生在 https://github.com/apache/rocketmq-client-go native分支下,是纯go,无需SDK,看上去更像是个完美解决方案,但native尚在开发阶段,支持的功能少,作者也不建议在生产环境使用。

最后我们还是选择使用非原生版本,需要提醒的是当前(2019.11),github的用户文档会产生一些干扰和误解,官方指导是下载RocketMQ的C++版本和GCC,并提供了一个编译后的bin文件,但这个bin文件版本已经过期了,不能对应最新的rocketmq-client-go (1.2.2 与 1.2.4),虽然程序能跑起来,但是会产生很多影响功能的问题。

正确的做法是参考阿里云的文档 https://help.aliyun.com/document_detail/140296.html?spm=a2c4g.11186623.6.619.7fb56e2bIv135O ,官方提供了不同系统环境下的GCC、CGO及SDK的安装方式。由于我们用的K8S,所以使用了Docker镜像并通过Dockerfile来完成RocketMQ的使用。

1
2
3
4
// 安装 gcc
wget https://ons-client-sdk.oss-cn-hangzhou.aliyuncs.com/cpp-rpm/debian/cgo/rocketmq_2.0.0_amd64.deb
dpkg -i rocketmq_2.0.0_amd64.deb
// golang:1.13 默认安装了cgo

4. 功能

4.1 注册

上文也说到阿里云IoT的设备注册可以批量烧录,也可以分配三元组。但对于批量设备注册,还需一套完整的动态解决方案。语雀社区也给了一些思路 https://www.yuque.com/cloud-dev/iot-tech/qtg9m9

方案是通过调用阿里云IoT平台的API来实现动态分配三元组的。根据中间件的设计需求,我们调整了时序图如下所示:

首先是设备启用和新增,应用后台可以新增设备,新增一个设备ID并传递给阿里云IoT平台用于申请三元组,注意设备ID必须唯一,应用平台需控制,不允许出现相同的设备ID传递到IoT平台,否则数据会紊乱。新增、启用设备主要是应用后台申请三元组并存储在应用系统的DB中。

当应用系统申请到三元组后,中间件才能使用,否则会拿不到三元组从而导致无法注册到IoT平台。中间件一旦启动,会调用应用系统的注册接口,注意这部分请求不是通过Link Kit SDK去实现的,所以需要中间件实现一个HTTP(S)的请求。通过这个请求,中间件把仪器端的设备ID传递给应用系统(设备ID与应用系统添加的一致),应用系统将DB中的三元组返回给中间件,中间件再使用三元组完成阿里云IoT平台的注册。

最后,当应用系统想停止某个仪器设备,就可以在应用后台调用停用设备的接口,则应用后台会通知阿里云IoT删除对应的三元组,并删除应用系统DB的三元组数据。中间件就会因为三元组的丢失,不能再向后台发送数据。

4.2 软件更新(非ota)

软件更新这块,阿里云提供了官方的OTA方案,我们并没有完全去使用,一方面考虑方案的灵活性,另一方面OTA目前还不算特别成熟。我们是基于阿里云OSS和IoT平台来实现软件的远程更新。

首先是应用系统针对软件有一个版本管理,包括软件包的上传(上传至OSS)。

然后是推送功能,推送功能可以让用户选择在线的仪器,则应用后台会向选中仪器的中间件发送一条消息,标识有软件更新,并提供下载连接(OSS/CDN下载连接)。

中间件收到消息后,静默下载升级包到本地临时文件,并校验安装包完整性。

当安装包下载完整无误,再通知上位机,并传递本地路径。

最后用户执行或自动策略执行升级文件,完成升级。

同理,任何文件格式的传输都可以采用这种方式,比如配置文件等。

4.3 远程控制

远程控制这块,有现成的轮子 https://github.com/zhaojh329/rtty ,主要想通过远程的ssh,实现故障快速定位,降低运维成本。这里就不再多说了。

以上就是我们近期在IoT实践中的部分总结。