收起

从 0 开始搭建 IoT 平台

代码实操,揭秘大厂 IoT 平台设计之道

付强 · 物联网创业者兼 CTO
已发布 46 篇 共 46 篇
1

开发物联网应用,光会 MQTT 还不够

试读
2

如何运行 Maque IotHub

3

准备工作台

试读
4

设备注册(一)

试读
5

设备注册(二)

试读
6

设备注册(三)

试读
7

设备在线状态管理(一)

8

设备在线状态管理(二)

9

设备禁用与删除

10

设备权限管理

11

加一点扩展性

12

选择一个可扩展的上行数据处理方案

13

功能设计

14

实现(一)

15

实现(二)

16

设备状态上报

17

时序数据库

18

选择下行数据处理方案

19

功能设计

20

设备端实现

21

服务端实现(一)

22

服务端实现(二)

23

RPC 式调用(一)

24

RPC 式调用(二)

25

设备数据请求

26

NTP 服务

27

设备分组——功能设计

28

设备分组——服务端实现

29

设备分组——设备端实现

30

设备间通信

31

OTA 升级——功能设计

32

OTA 升级——服务端实现

33

OTA 升级——设备端实现

34

设备影子概览

35

设备影子——服务端实现

36

设备影子——设备端实现

37

IotHub 状态监控

38

EMQ X 的插件系统

39

我们会用到的 Erlang 特性

40

搭建开发和编译环境

41

编写 emqx-rabbitmq-hook(一)

42

编写 emqx-rabbitmq-hook(二)

43

使用 emqx-rabbitmq-hook

44

CoAP 简介

45

IotHub 接入 CoAP

46

我们学到了什么?

设备注册(三)

这一节,我们将对设备注册接入的细节进行完善。

添加数据库索引

我们需要对 Devices 的 product_name 和 device_name 做一个索引,因为在后面会经常通过这两个字段对 devices 进行查询,在 MongoDB shell 里面输入:

use iothub
db.devices.createIndex({
    "production_name" : 1,
    "device_name" : 1
}, { unique: true })

MongoDB 插件在每次设备接入的时候都会使用 broker_name 来查询 Devices Collectiion, 所以我们也需要在 broker_name 上加一个索引:

use iothub
db.devices.createIndex({
    "broker_username" : 1
})

使用持久化连接

细心的读者可能已经发现了, DeviceSDK 在连接到 Broker 的时候并没有指定 Client Identifier。没错,到目前为止,我们使用的都是在连接时自动分配的 Client Identifer, 没有办法很好地使用 QoS1 和 QoS2 的消息。

Client Identifier 是用来唯一标识 MQTT Client 的,由于我们之前的设计保证了(ProductName, DeviceName)是全局唯一的,所以一般来说用这个二元组作为 Client Identifier 就足够了。 但是,之前我也提到过,在某些场景下,可能会出现多个设备使用同样的设备三元组接入 Maque IotHub,综合这些情况,我们这样来设计 Maque IotHub 里的 Client Identifier。

设备提供一个可选的 ClientID 来标识自己,可以是硬件编号、AndroidID 等,如果设备提供 ClientID,那么使用 ProductName/DeviceName/ClientID 作为连接 Broker 的Client Identifier,否则使用ProductName/DeviceName。 根据这个规则对 DeviceSDK 进行修改。

// IotHub_Device/sdk/iot_devices.js
...
class IotDevice extends EventEmitter {
    constructor({serverAddress = "127.0.0.1:8883", productName, deviceName, secret, clientID} = {}) {
        super();
        this.serverAddress = `mqtts://${serverAddress}`
        this.productName = productName
        this.deviceName = deviceName
        this.secret = secret
        this.username = `${this.productName}/${this.deviceName}`
        //根据 ClientID 设置
        if(clientID != null){
            this.clientIdentifier = `${this.username}/${clientID}`
        }else{
            this.clientIdentifier = this.username
        }
    }

    connect() {
        this.client = mqtt.connect(this.serverAddress, {
            rejectUnauthorized: false,
            username: this.username,
            password: this.secret,
            //设置 ClientID 和 clean session
            clientId: this.clientIdentifier,
            clean: false
        })
        ...
   }
 ... 

之后你可以再运行一次samples/connect_to_server.js看下效果。

Node.js 的 MQTT 库自带了断线重连功能,所以这里就不用我们来实现了。

更多的 Server API

我们还需要几个接口来完善注册流程.

获取某个设备的信息

当业务系统查询设备信息的时候,我们并不是把 Device 的所有字段都返回。首先定义下返回内容:

// IotHub_Server/models/device.js
//定义 device.toJSONObject
deviceSchema.methods.toJSONObject = function () {
    return {
        product_name: this.product_name,
        device_name: this.device_name,
        secret: this.secret
    }
}

然后进行接口实现:

// IotHub_Server/routes/devices.js
router.get("/:productName/:deviceName", function (req, res) {
    var productName = req.params.productName
    var deviceName = req.params.deviceName
    Device.findOne({"product_name": productName, "device_name": deviceName}, function (err, device) {
        if (err) {
            res.send(err)
        } else {
            if (device != null) {
                res.json(device.toJSONObject())
            } else {
                res.status(404).json({error: "Not Found"})
            }
        }
    })
})
curl http://localhost:3000/devices/IotApp/V5MyuncRK

{"product_name":"IotApp","device_name":"V5MyuncRK","secret":"GNxU20VYTZ"}

列出某个产品下的所有设备

// IotHub_Server/routes/devices.js
router.get("/:productName", function (req, res) {
    var productName = req.params.productName
    Device.find({"product_name": productName}, function (err, devices) {
        if (err) {
            res.send(err)
        } else {
            res.json(devices.map(function (device) {
                return device.toJSONObject()
            }))

        }
    })
})
curl http://localhost:3000/devices/IotApp
[{"product_name":"IotApp","device_name":"V5MyuncRK","secret":"GNxU20VYTZ"}]

获取接入 Broker 的一次性密码(JWT)

// IotHub_Server/routes/tokens.js

var express = require('express');
var router = express.Router();
var shortid = require("shortid")
var jwt = require('jsonwebtoken')

//这个值应该和 EMQ X etc/plugins/emqx_auth_jwt.conf 中的保存一致
const jwtSecret = "emqxsecret"

router.post("/", function (_, res) {
    var username = shortid.generate()
    var password = jwt.sign({
        username: username,
        exp: Math.floor(Date.now() / 1000) + 10 * 60
    }, jwtSecret)
    res.json({username: username, password: password})
})

module.exports = router
// IotHub_Server/app.js
var tokensRouter = require('./routes/tokens')
app.use('/tokens', tokensRouter)

通过这个接口,可以签发一个有效期为 1 分钟的 username/password:

curl http://localhost:3000/tokens -X POST
{"username":"apmE_JPll","password":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFwbUVfSlBsbCIsImV4cCI6MTU2ODMxNjk2MSwiaWF0IjoxNTU4MzE2OTYxfQ.-SnqvBGdO3wjSu7IHR91Bo58gb-VLFuQ28BeN6hlTLk"}

大家可能还发现了,在 ServerAPI 里面没有对调用者的身份进行认证和权限控制,也没有对输入参数进行校验,输出列表时也没有进行分页等的处理,当然在实际的项目中,这些都是有必要的。 但是这些属于 Web 编程的范畴,我想大家应该都非常熟悉了,所以在本课程中就跳过了,让课程的内容紧贴主题。

从环境变量中读取配置

根据 The Twelve-Factor App 的理念,从环境变量中读取配置项是一个非常好的 Practice,在我们的项目中有两个地方要用到配置:

  • ServerAPI,比如 mongoDB 的地址;
  • DeviceSDK 端的 samples 里的代码会经常使用到预先注册的三元组(ProductName, DeviceName, Secret)。

这里我们使用 dotenv 来管理环境变量,它可以从一个 .env 文件中读取并设置环境变量。

// IotHub_Server/app.js 
require('dotenv').config()
mongoose.connect(process.env.MONGODB_URL, { useNewUrlParser: true })
// IotHub_Server/routes/tokens.js
const jwtSecret = process.env.JWT_SECRET
# IotHub_Server/.env
MONGODB_URL=mongodb://iot:iot@localhost:27017/iothub
JWT_SECRET=emqxsecret
// IotHub_Device/samples/connect_to_server.js
require('dotenv').config()
var device = new IotDevice({
    productName: process.env.PRODUCT_NAME,
    deviceName: process.env.DEVICE_NAME,
    secret: process.env.SECRET
})
# otHub_Device/samples/.env
PRODUCT_NAME=注册接口获取的 ProductName
DEVICE_NAME=注册接口获取的 DeviceName
SECRET=注册接口获取的 Secret

在这一节里,我们补全了设备注册流程的所有功能,完善了细节,接下来我们看如何实现监控设备的在线状态。

推荐阅读 👉《从 0 开始搭建 IoT 平台》

为了方便与作者交流与学习,GitChat 编辑团队组织了一个《从 0 开始搭建 IoT 平台》读者交流群,添加编辑小姐姐微信:「GitChatty6」,回复关键字「214」给编辑小姐姐获取入群资格。

还没有评论
评论
关注提示×
扫码关注公众号,获得课程更新动态!