收起

从 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

我们学到了什么?

设备注册(二)

在本节课中,我们将设计和实现从设备注册到接入 IotHub 的主要流程

首先我们定义一下设备注册到接入 IotHub 的流程。

注册流程

  1. 业务系统调用 Maque IotHub Server API 的设备注册 API,提供要注册设备的 ProductName。
  2. Maque IotHub Server 根据业务系统提供的参数生成一个三元组(ProductName, DeviceName, Secret),然后将该三元组存储到 MongoDB,同时存储到 MongoDB 的还有该设备接入 EMQ X 的用户名: ProductName/DeviceName。
  3. Maque IotHub Server API 将生成的三元组返回给业务系统,业务系统应该保存这个三元组,以后调用 Maque IotHub Server API 时需要使用。
  4. 业务系统通过某种方式,例如烧写 Flash,将这个三元组"写"到物联网设备上。
  5. 设备应用代码调用 Maque IotHub DeviceSDK,传入三元组。
  6. Maque IotHub DeviceSDK 使用 username: ProductName/DeviceName, password: Secret连接到 EMQ X Broker。
  7. EMQ X Broker 到 MongoDB 里面查询 ProductName/DeviceName 和 Secret,如果匹配,则允许连接。

注册流程如下图所示。

avatar

设备注册 API

接下在 IotHub_Server 项目里实现 Maque IotHub Server API 的设备注册 API:

我们在 MongoDB 里创建一个名为 IotHub 的数据来存储设备信息。

定义设备模型

这里,我们使用 mongoose 来做 MongoDB 相关的操作,首先定义 Device 模型:

// IotHub_Server/models/device.js
const deviceSchema = new Schema({
    //ProductName
    product_name: {
        type: String,
        required: true
    },
    //DeviceName
    device_name: {
        type: String,
        required: true,
    },
    //接入 EMQ X 时使用的 username
    broker_username: {
        type: String,
        required: true
    },
    //secret
    secret: {
        type: String,
        required: true,
    }
})

Restful API 实现

每次在生成新设备的时候,由系统自动生成 DeviceName 和 Secret,DeviceName 和 Secret 应该是随机且唯一的字符串,例如 UUID,这里,我们用 shortid 来生成稍短一点的随机唯一字符:

// routes/devices.js
...
router.post("/", function (req, res) {
    var productName = req.body.product_name
    var deviceName = shortid.generate();
    var secret = shortid.generate();
    var brokerUsername = `${productName}/${deviceName}`

    var device = new Device({
        product_name: productName,
        device_name: deviceName,
        secret: secret,
        broker_username: brokerUsername
    })

    device.save(function (err) {
        if(err){
            res.status(500).send(err)
        }else{
            res.json({product_name: productName, device_name: deviceName, secret: secret})
        }
    })
})

...

接着我们将这个 router 挂载到 /devices 下面,并连接到 MongoDB:

//app.js
...
mongoose.connect('mongodb://iot:iot@localhost:27017/iothub', { useNewUrlParser: true })
var deviceRouter = require('./routes/devices');
app.use('/devices', deviceRouter);
...

运行 bin/www 启动 Web 服务器,然后在命令行用 curl 调用这个接口:

curl -d "product_name=IotApp" -X POST http://localhost:3000/devices

输出为:{"product_name":"IotApp","device_name":"V5MyuncRK","secret":"GNxU20VYTZ"}

ProductName 包含的字符是有限制的,不能包含# / +以及 IotHub 预留的一些字符,为了演示,这里跳过了输入参数的校验,但是在实际项目中,是需要加上的。

到这里,设备注册就成功了,我们需要记录下这个三元组。

修改 emqx_auth_mongo.conf

接下来需要按照我们定义的数据库结构来修改 EMQ X MongoDB 认证插件的配置,下面是需要在上一节内容上修改的项:

# 存储用户名和密码的 database
auth.mongo.database = iothub

# 存储用户名和密码的 collection
auth.mongo.auth_query.collection = devices

# 密码字段
auth.mongo.auth_query.password_field = secret

# 查询记录时的 selector
auth.mongo.auth_query.selector = broker_username=%u

编辑完成以后重载下 MongDB 认证插件: <EMQ X 安装目录>/bin/emqx_ctl plugins reload emqx_auth_mongo

修改 DeviceSDK

接下在 IoTHub_Device 项目里对 DeviceSDK 进行修改,接受三元组作为初始化参数:

// sdk/iot_device.js

...
class IotDevice extends EventEmitter {
    constructor({serverAddress = "127.0.0.1:8883", productName, deviceName, secret} = {}) {
        super();
        this.serverAddress = `mqtts://${serverAddress}`
        this.productName = productName
        this.deviceName = deviceName
        this.secret = secret
        this.username = `${this.productName}/${this.deviceName}`
    }
    connect() {
        this.client = mqtt.connect(this.serverAddress, {
            rejectUnauthorized: false
            username: this.username,
            password: this.secret
        })
        ...
    }

    ...
}   
...

然后我们用刚才记录下的三元组作为参数调用 DeviceSDK 接入 Maque IotHub:

// samples/connect_to_server.js
...
var device = new IotDevice({productName: "IotApp", deviceName: "V5MyuncRK", secret: "GNxU20VYTZ"})
...

然后再运行samples/connect_to_server.js,会得到以下输出:

device is online

这说明设备已经完成注册并成功接入 IotHub 了。


这一节我们完成了设备注册到接入的主要流程,下一节,我们将继续完善细节。

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

注意!!! 为了方便学习和技术交流,特意创建了读者群,入群方式放在 第 1-5 课 文末,欢迎已购本课程的同学入群交流。

互动评论
评论
木子鱼4 个月前
Error: connect ECONNREFUSED 127.0.0.1:5672 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1139:16) { errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 5672 } node:internal/process/promises:227 triggerUncaughtException(err, true /* fromPromise */); ^ MongoError: Authentication failed.
评论
木子鱼4 个月前
付老师,我在终端运行 ./bin/www 报错了,(node:13212) Warning: Accessing non-existent property 'count' of module exports inside circular dependency (Use `node --trace-warnings ...` to show where the warning was created) (node:13212) Warning: Accessing non-existent property 'findOne' of module exports inside circular dependency (node:13212) Warning: Accessing non-existent property 'remove' of module exports inside circular dependency (node:13212) Warning: Accessing non-existent property 'updateOne' of module e
评论
木子鱼4 个月前
付老师,我在终端运行 ./bin/www 报错了,(node:13212) Warning: Accessing non-existent property 'count' of module exports inside circular dependency (Use `node --trace-warnings ...` to show where the warning was created) (node:13212) Warning: Accessing non-existent property 'findOne' of module exports inside circular dependency (node:13212) Warning: Accessing non-existent property 'remove' of module exports inside circular dependency (node:13212) Warning: Accessing non-existent property 'updateOne' of module e
评论
关注提示×
扫码关注公众号,获得课程更新动态!