• 基于MQTTv5的智慧园区消息总线系统设计与实现


    背景

    经过多年的发展,智慧园区已经初见规模,目前基本形成了以MQTT v3.1.1为基础,综合应用modbus、BACnet、RTSP、RTMP等技术的智慧园区整体解决方案,为智慧园区提供了底层的技术支持。

    随着MQTTv5协议的发布,基于MQTTv5的服务端和客户端实现在近一两年的时间里逐渐走向成熟,而MQTTv5解决了MQTTv3.1.1存在的一些问题,尤其是在服务端需要主动断开连接的场景下。同时MQTTv5带来了大量的新特性,诸如共享订阅、主题别名、认证方法等。

    MQTT v5服务端实现

    为此,我们设计并实现了海舟智慧园区消息总线系统SailMQ。

    海舟智慧园区消息总线系统,是一个MQTT服务端的实现,基于gmqtt提供的全面的MQTT v3.1.1和MQTT v5支持,为用户提供面向未来的智慧园区基础设施。

    海舟智慧园区消息总线系统包含以下子系统:

    • 海舟智慧园区管理系统SailUI
    • 海舟智慧园区消息总线系统SailMQ

    这两个子系统相辅相成,缺一不可。

    设计模型与术语

    设备(Device)与 设备管理员(Device Manager)

    各种物联网智能终端设备与用于与旧式系统交互的工控机等被统一称为设备,是用户管理的基本单位,设备归属于某个特定的设备管理员,一个设备管理员可以管辖多个设备。

    App 与 App管理员(App Manager)

    用于展示、控制、获取设备状态的应用系统被称为App,App归属于某个特定的App管理员,App管理员被授权访问多个设备,其所管理的App具有同这些设备进行交互的权限。

    客户端

    统一将设备和App称为客户端。

    主题

    主题被划分为三级,例如dm-1/dev-1/DATA,其中dev-1为相应客户端的Client ID,dm-1为其所属的管理员的User Name,DATA为通道名(Channel Name)。

    所有通道名如下:

    • OFFLINE
    • DATA
    • CONTROL
    • CREPLY
    • QUERY
    • QREPLY

    规则引擎

    规则引擎是一个用于校验消息格式的系统模块,后续章节会进行详细介绍。

    访问控制

    连接

    在MQTT协议中,客户端向服务端发送的第一个报文是CONNECT报文,本系统对此报文中的客户标识符(Client Identifier)、用户名(User Name)、密码(Password)、遗嘱主题(Will Topic)和遗嘱消息(Will Payload)进行校验,在MQTT v5协议的客户端中,如果存在认证方法(Authentication Method),则应将其设置为jwt,并将认证数据(Authentication Data)设置为与密码相同。

    下面详细介绍以上字段的配置规则:

    • 客户端标识符

    应根据其类型配置为 dev-XXX(设备) 或 app-YYY (App)。

    • 用户名

    根据客户端类型进行配置,当客户端类型为设备,此处应设置为其设备管理员的User Name;当客户端类型为App,此处应设置为其App管理员的User Name。

    • 密码

    如需获取密码,请安装SailPasswordGenerator,打开后按照界面提示输入相应参数即可获得密码。

    • 遗嘱主题

    遗嘱主题的设置根据客户端类型的不同采用不同的设置,如果类型为设备则应设置为dm-XXX/dev-YYY/OFFLINE;如果类型为App则应将其置空。

    • 遗嘱消息

    遗嘱主题的设置根据客户端类型的不同采用不同的设置,如果类型为App则应将其置空;如果类型为设备,遗嘱消息需通过规则引擎的验证,关于规则引擎请参考后续章节。

    订阅

    订阅支持共享订阅和非共享订阅,订阅根据不同的客户端类型而有所不同:

    • 当客户端类型为设备,其可以订阅主题dm-XXX/dev-YYY/CONTROL和dm-XXX/dev-YYY/QUERY。
    • 当客户端类型为App,她可以订阅其所属App管理员被授权的设备,可以订阅的通道为DATA、CREPLY、QREPLY;此时也可以订阅相应的共享订阅主题。例如有名为app-1的App,其所属App管理员为am-1,am-1被授权的设备有dev-1,而dev-1所属的管理员为dm-1,这时app-1可以订阅的主题有dm-1/dev-1/DATA、dm-1/dev-1/CREPLY和dm-1/dev-1/QREPLY;及相应的共享订阅主题,例如$share/{ShareName}/dm-1/dev-1/DATA。

    发布

    发布支持QOS0、1、2。

    发布根据不同的客户端类型而有所不同:

    • 当客户端类型为设备,其可以发布的主题为dm-XXX/dev-YYY/DATA、dm-XXX/dev-YYY/CREPLY和dm-XXX/dev-YYY/QREPLY。
    • 当客户端类型为App,她可以发布消息到其所属App管理员被授权的设备,可以发布的通道为CONTROL和QUERY。例如有名为app-1的App,其所属App管理员为am-1,am-1被授权的设备有dev-1,而dev-1所属的管理员为dm-1,这时app-1可以发布的主题有dm-1/dev-1/CONTROL和dm-1/dev-1/QUERY。

    如果以上的发布的访问控制校验通过,那么会进行规则引擎的校验,关于规则引擎请参考后续章节。

    规则引擎

    当消息进入系统时,规则引擎会对其进行校验。

    首先,规则引擎会根据消息的主题去Redis中寻找相应的规则,例如,当主题为dm-1/dev-1/CONTROL时,规则引擎会按如下的优先级顺序查找规则:

    • dm-1/dev-1/CONTROL
    • dm-1/*/CONTROL
    • */*/CONTROL
    • */*/*

    当规则引擎找到相应的规则后,不会继续向下查询。

    规则为一段特殊定制的json,下面举例说明:

    {
        "age": "number",
        "books": "list",
        "isStudent": "bool",
        "name": "string",
        "other": "object",
        "score": {
            "math": "number",
            "music": "number",
            "sport": "number"
        },
        "tag": "value"
    }
    

    消息若想通过上述规则的校验,必须满足其定义:

    • age 应设置为number类型
    • books 应设置为一个列表
    • isStudent 应设置为 true 或 false
    • name 应设置为字符串
    • other 应设置为一个对象,对象的键值对没有要求。
    • score 应设置为一个对象,并且其应该递归满足规则中键值对的要求,递归的层级没有限制
    • tag 应设置为number、bool或string类型的值
    • 消息中不得包含规则中未定义的字段

    附录

    已知支持MQTTv5的库

    • paho java
    • paho python
    • paho c
    • paho c++
    • MQTT.js

    Python示例代码

    import paho.mqtt.client as mqtt
    import json
    
    PASSWORD = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRJZCI6ImFwcC0xIiwidXNlcm5hbWUiOiJhbS0xIn0.fkotiit0lMJhO1FO2P9cvAF8g7KKL9xHF7XgMyq1H-5"
    
    
    def on_connect(client, userdata, flags, reason, properties):
        print(flags, reason, properties)
    
        client.subscribe("dm-1/dev-1/CREPLY")
        
        msg = {"light": "on"}
        client.publish("dm-1/dev-1/CONTROL", json.dumps(msg), 2, True)
    
    
    def on_message(client, userdata, msg):
        print(msg.topic + " " + str(msg.payload))
    
    
    client = mqtt.Client(client_id="app-1", protocol=mqtt.MQTTv5)
    client.username_pw_set("am-1", PASSWORD)
    client.on_connect = on_connect
    client.on_message = on_message
    
    client.connect("localhost", 1883, 60)
    
    client.loop_forever()
    
    作者:Hevienz
    出处:http://www.cnblogs.com/hymenz/
    知识共享许可协议
    本博客原创作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
  • 相关阅读:
    Python基础编程常用模块汇总
    博客目录
    网络编程
    python 对象
    python模块和规范开发
    python常用内置函数
    python递归函数和匿名函数
    python装饰器
    python迭代器,生成器,推导式
    python作用域
  • 原文地址:https://www.cnblogs.com/hymenz/p/14622269.html
Copyright © 2020-2023  润新知