跳至主要內容

设备 MQTT 接入

约 6213 字大约 21 分钟

设备 MQTT 接入

什么是 MQTT ?

MQTT 全称是 Message Queuing Telemetry Transport,它是一种基于消息队列的轻量级应用层通信协议,实现了消息发布和订阅。设备可以作为客户端的形式通过它来发布和接收消息,实现数据上报和实时控制。

MQTT 是一种简单的消息传递协议,设计用于具有低带宽的受限设备。因此,它是物联网设备接入的完美解决方案。

ThingsCloud 支持哪些设备?

ThingsCloud 平台提供了标准的 MQTT 接入协议,支持 MQTT v3.1/v.5,任何支持 MQTT 协议的设备都可以通过相应的 MQTT 客户端代码接入平台。

那么,设备要支持 MQTT,有哪些常见的方式呢?通常不外乎以下几种:

  • 各种单片机或嵌入式系统,使用支持 MQTT 或 TCP 协议的 2G/3G/4G/5G 、 NBIoT 等无线通信模组,通过 AT 指令、串口透传、SDK 等方式快速接入平台。
  • 本身支持 OpenCPU 的通信模组,例如:ESP32、Air724UG 等,通过模组固件二次开发实现接入平台。
  • 无法直接连网的传感器、IO 控制器、PLC 等设备,通过支持 MQTT 的网关/DTU 接入平台。

我们也会不断提供各种常见设备的接入示例。

设备 MQTT 接入点

了解了基本概念后,我们开始学习如何让设备通过 MQTT 接入 ThingsCloud 平台,并和平台进行各类消息通信。

提示

在此之前,我们在 快速上手 的章节中简单介绍了在控制台创建设备,并完成了一些基本的 MQTT 通信,如果您没有阅读,建议您大概了解一下。

ThingsCloud 公有云支持全球多个区域,并随着设备接入量不断扩张。每个接入的设备有专用的 MQTT 接入点,获得设备 MQTT 接入点的正确方式,就是在控制台进入设备详情的【连接】页面,复制该设备的 MQTT 接入点。

我们提供多种类型的 MQTT 接入点和认证方式,如下:

接入类型用途接入点示例
MQTT普通认证方式,适合大多数普通设备或资源受限设备mqtt://<endpoint>:1883
MQTT over TLSX.509 认证方式,适合对通信安全要求严格的设备mqtts://<endpoint>:1884

MQTT 身份认证

设备通过 MQTT 协议连接平台时,需要完成基于 MQTT 的身份认证,平台支持以下认证方式。

普通认证方式

对于普通认证方式,在 MQTT 连接时,使用基于 username/password 的认证方式,需要用到设备的普通证书,如下:

MQTT 连接参数说明
usernameAccessToken设备创建后自动生成,每个设备唯一,量产设备可通过 API 自动获取 AccessToken,实现一型一密。
passwordProjectKey项目创建后自动生成,不支持修改。
clientId空或任意不对 clientId 做任何限制,可随意填写。

设备详情页 > 连接 中可以复制以上标识。

这里以 MQTT.fx 为例,连接参数填写大致如下图:

MQTT.fx 设置连接到 ThingsCloud
MQTT.fx 设置连接到 ThingsCloud

提示

请确保以上标识只用于设备固件开发,烧录到设备 ROM/Flash 中,避免泄露。

对于量产阶段的设备,无需手动复制每个设备的 AccessToken 进行单独烧录,可采用 一型一密 的方式,通过 API 自动获取 AccessToken,以及自动创建设备。

需要注意的是,ThingsCloud 对同一个设备身份信息只支持一个 MQTT 连接,也就是说,如果在两个或多个物理设备中,使用同样的 username/password 身份信息连接平台,即便 clientId 使用不同的字符串,平台仍然将这些连接视为同一个设备,这会导致后一个设备连接成功后会顶掉之前的设备连接。

X.509 TLS 认证方式

在一些对通信安全要求严格的物联网领域,比如智能门锁、电表、水表、燃气表等,您可以使用基于 X.509 TLS 的 MQTT 安全认证方式,我们目前只在 ThingsCloud 专有区和私有区中支持该认证方式。

更进一步的物联网安全措施,可以在设备端集成 SE 安全芯片,或使用内置 SE 安全芯片的通信模组,实现设备和平台双向认证。ThingsCloud 对智能设备厂商的私有区服务提供 SE 解决方案。

MQTT 主题一览

ThingsCloud 作为物联网 PaaS 平台,对设备 MQTT 接入提供了内置的访问协议规范,让设备和平台的消息通信更加有章可循,大大简化了物联网项目的开发难度,缩短了产品的开发周期。

不同于普通的 MQTT 使用方式,我们提供了标准的内置主题,这足以实现绝大多数的物联网应用场景。

而对于另一些个性化的消息通信场景,我们也提供了灵活的自定义主题,您完全创建自己的主题,并配合包括云函数在内的规则引擎,实现任何消息通信需求。

发布主题

以下主题用于 设备向平台发布消息

消息类型主题
设备上报属性值attributes
设备获取当前属性值attributes/get/<id>
设备上报事件event/report/<id>
设备回复命令command/reply/<id>
设备自定义数据上报data/<identifier>
设备上报离线主题attributes/series

请注意:

  • 以上的主题只能用于设备端发布消息,不能用来订阅。如需在应用端接收设备发布的消息,请使用 MQTT应用端订阅规则转发,更多介绍请浏览 API
  • 设备端在发布(publish)消息时,QoS 请使用 0,不要使用 1 或 2。ThingsCloud 不支持 QoS,以免通信模组重复发送。

订阅主题

以下主题用于 设备接收平台下发的消息

消息类型主题
接收属性上报的响应attributes/response
接收属性获取的响应attributes/get/response/+
接收下发的属性attributes/push
接收事件上报的响应event/response/+
接收下发的命令command/send/+
接收命令回复的响应command/reply/response/+
接收自定义数据下发data/<identifier>/set
接收离线属性上报的响应attributes/series/response

提示

以上自定义数据相关主题中的 identifier,是指自定义数据流的标识符。详细介绍请浏览 自定义数据流

请注意,以上主题只能用于设备端订阅,不能用来发布。如需在应用端向设备下发消息,可使用以下方式:

提示

您可能已经发现,所有的主题中并没有出现设备标识,这也是 ThingsCloud 的不同之处。

当设备连接 ThingsCloud 平台后,会通过独有的虚拟映射方式,拥有独立的主题命名空间,所以每次发布消息或订阅消息时,主题名称非常简洁,大大简化了开发和配置过程。

下面我们会逐个介绍每个发布主题和订阅主题的使用方法。

先看看最佳实践教程?

设备上报属性

当设备自身的属性发生变化时,通过向平台上报属性,实现平台设备属性的及时更新,以及应用系统的设备数据实时更新。

设备发布消息

设备上报属性使用以下主题:

attributes

消息内容必须是 JSON 键值对格式,否则会被平台拒绝接收,甚至断开连接。

一个简单的属性上报消息如下:

{
    "temperature": 28.5,
    "light": 2000,
    "switch": true
}

设备发布以上消息后,控制台的设备详情页会实时更新显示设备属性的最新值。

提示

ThingsCloud 控制台通过 MQTT Websockets 方式订阅设备属性的实时更新,您可以在不刷新页面的情况下实时查看设备属性值的变化。有时网络原因导致 Websockets 连接中断,您只需刷新页面即可恢复。

我们也将这种 Websockets 订阅方式开放给应用开发,如果您的应用基于 ThingsCloud 平台,便可以实现页面实时更新。

如果设备按一定时间间隔上报属性,就实现了一个基本的数据采集上报功能。

平台会自动保存这些属性的时序数据,如下图:

属性上报
属性上报

对于 number 类型的属性,平台还提供了时序图表,方便浏览历史曲线。

属性上报
属性上报

当然,您也可以使用如下的 JSON 消息格式来上报属性数据:

{
    "sensor_1": {
        "temperature": 28.5,
        "light": 2000,
        "switch": true  
    },
    "sensor_2": {
        "temperature": 27.1,
        "light": 1800,
        "switch": false  
    }
}

但是对于这种将属性值放入嵌套结构中的消息,平台并不会直接保存嵌套结构内的属性时序数据,而只保存 sensor_1 这样的结构体属性值,供设备和应用查询。

如果设备一定要按这样的嵌套格式上报属性,或者有些时候您无法改变设备的上报规则,有没有办法让平台也生成属性时序数据呢?答案是肯定的,那就是利用平台的消息规则,自动将嵌套结构中的属性数据,复制到 JSON 消息的顶层结构中,这样就会自动被平台记录时序数据。具体的做法请参考后边的章节。

设备接收消息

如果想知道属性上报是否被平台成功接收,可以订阅如下主题:

attributes/response

请注意,订阅主题只需要在设备 MQTT 成功连接平台后执行一次即可。设备上报属性后,可通过该主题立即接收来自平台的响应消息。

如果平台接收成功,响应消息如下:

{
  "result": 1,
  "ts": 1609143039050
}

如果平台未成功接收,响应消息中会包含错误原因,常见的错误原因如下:

  • 属性上报的消息格式错误
{
  "result": 0,
  "message": "Invalid attributes data format",
  "errcode": 402
}
  • 属性上报频率过快。
{
  "result": 0,
  "message": "Device message frequency is too high, please wait for a moment",
  "errcode": 403
}
  • 已达到日消息量上限
{
  "result": 0,
  "message": "Device messages exceeds today maximum limit",
  "errcode": 413
}
  • 属性上报不符合设备类型的功能定义
{
  "result": 0,
  "message": "Invalid attributes: device type attributes define check failed",
  "errcode": 406
}

设备获取属性

对于连网设备,有些属性是在云端进行设置下发的。当设备重启后,需要立即从平台获取属性当前值,实现设备初始化。

设备发送消息

当设备希望从平台获取属性当前值时,发送消息到如下主题:

attributes/get/<id>

<id> 可以使用任意不超过 6 位的整数,例如:

attributes/get/1000

消息内容格式如下:

{
    "keys": []
}

这表示获取所有属性,也可以指定个别属性,如下:

{
    "keys": [
        "temperature",
        "humidity"
    ]
}

设备接收消息

请确保设备已经订阅了如下主题:

attributes/get/response/+

如果设备不支持 + MQTT 通配符,也可以订阅如下主题:

attributes/get/response/<id>

这里的 <id> 要和前边发送消息中的 <id> 一致,否则无法接收响应消息。

当设备发送获取属性的消息后,便会通过以上订阅主题,收到如下的响应消息:

{
  "result": 1,
  "attributes": {
    "temperature": 34.2,
    "humidity": 67
  }
}

设备接收云端下发属性

除了设备主动获取属性以外,我们也需要让设备可以实时接收平台下发的属性更新。

非常简单,确保设备已订阅如下主题:

attributes/push

当平台下发属性给设备时,设备会通过以上订阅主题,立即收到 JSON 结构的消息,例如:

{
    "switch": false
}

这个示例消息显然是通知设备关闭某个开关,设备通过自身的程序实现该功能后,可以接着向平台上报属性或上报事件,让平台得到确认。

那么,如何实现平台下发属性呢?我们提供两种方式:

控制台

在控制台的设备详情【属性】页面中,这种方式适合开发调试阶段或小规模项目。

属性下发
属性下发

应用端 API

调用平台提供的应用端 API,这种方式可以集成到任何应用系统中。

设备上报事件

前边都是属性相关的消息,这一节我们来了解一下事件。

设备发送消息

通过事件,设备可以向云端报告消息,而不需要上报任何属性。例如:设备的 AI 视觉传感器捕捉到活动人数,上报给平台。

一个事件由如下内容组成:

  • 事件名称

可以理解为函数名,是事件的唯一表示。

  • 事件参数

可以理解为函数的参数,是 JSON 格式的结构体,包含若干参数。

设备可以随时向平台上报事件,只需将事件消息发布到如下主题:

event/report/<id>

<id> 的取值为不超过 6 位的整数,例如:

event/report/1000

发布的事件消息格式如下:

{
    "method": "{name}",
    "params": {
        "key1": "{value1}",
        "key2": "{value2}",
        ...
    }
}

例如前边提到的,工厂生产线上的 AI 视觉传感器捕捉到产品数量后,上报事件给平台,那么消息可能是这样的:

{
    "method": "productFound",
    "params": {
        "count": 3,
        "location": "REGION.B43",
        "tags": "RED",
        "detail": [
            "100474",
            "100475",
            "100342"
        ]
    }
}

设备接收消息

如果我们想知道事件上报是否被平台成功接收,可以订阅如下主题:

event/response/+

或者

event/response/<id>

请注意,订阅主题只需要在设备 MQTT 成功连接平台后执行一次即可。当设备上报事件后,便会通过该主题收到来自平台的响应消息,如果平台接收成功,响应消息如下:

{
  "result": 1,
  "ts": 1609143039050
}

值得注意的是,响应消息中的成功,只代表平台成功收到了事件上报,并不代表对事件进行业务处理的结果

平台收到设备的事件上报后,如何处理事件呢?规则引擎便派上用场了,通过规则引擎,您可以将事件实时推送到第三方 Webhook URL,或通过规则引擎的云函数实现告警策略,当然还可以实现对其它设备的联动控制。

设备接收云端下发命令

与事件相反,命令是由平台向设备主动下发的一组消息,用来实时通知设备执行某个特定的功能。

您可以认为命令是平台向设备发起的一个远程调用(RPC)。

设备接收消息

设备在 MQTT 连接平台后,确保订阅如下主题:

command/send/+

如果设备不支持 + MQTT 通配符,也可以订阅如下主题:

command/send/<id>

订阅成功后,设备便处于等待接收命令的状态。当平台下发命令时,设备便会收到命令消息。

命令消息的格式也是基于 JSON 的结构体,如下:

{
    "method": "{name}",
    "params": {
        "key1": "{value1}",
        "key2": "{value2}",
        ...
    },
    "id": {id}
}

您可能发现了,它和事件消息格式非常类似,只是多了一个 id

设备回复消息

设备收到命令消息后,通过自身的代码逻辑实现特定的功能后,可以回复命令,也可以不回复命令。

平台会自动接收回复命令,并和下发命令进行匹配,一次下发命令只能接收一次回复命令,它们的匹配正是通过使用一致的 <id> 来保证。

设备回复命令消息的格式如下:

{
    "method": "{name}",
    "params": {
        "key1": "{value1}",
        "key2": "{value2}",
        ...
	}
}

没错,它和命令下发的消息格式一模一样,回复命令发布的主题如下:

command/reply/<id>

请注意,回复命令的主题中的 <id>,一定要和对应的命令下发消息中的 id 保持一致。

设备自定义数据上报

详情浏览 自定义数据流

设备接收自定义数据下发

详情浏览 自定义数据流

设备上报离线属性数据

在某些低功耗设备的应用场景中,设备无法保持始终在线,需要在离线的状态下记录数据和时间戳,随后在设备连接平台上线后,上报这些离线数据,实现在平台对历史数据的浏览和分析。

另外,在设备网络环境条件较差时,该功能也可用于补传属性数据。

提示

设备上报离线属性数据的功能,仅在企业版及以上版本中支持。了解详情

设备发布消息

设备上报离线属性使用以下主题:

attributes/series

消息内容必须是 JSON 数组格式,并符合以下结构:

[
    {
        "ts": {timestamp},
        "values": {
            "key1": "{value1}",
            "key2": "{value2}",
            ...
        }
    },
    ...
]

数组中的每个元素都表示一条属性历史数据,格式如下:

字段数据类型说明
tsnumberUNIX 时间戳(10位秒数)
valuesobject属性键值对。
键名必须符合属性标识符的合法格式,值必须是 number 类型。

提示

UNIX 时间戳(Unix Timestamp),也被称为 Unix 时间、POSIX 时间或者是 Unix 纪元时间(Unix Epoch Time)。它是从 1970 年 1 月 1 日 00:00:00 UTC(协调世界时)开始到特定时间所经过的秒数(10 位)或者毫秒数(13 位)。例如:

  • 时间戳为 0 代表 1970 年 1 月 1 日 00:00:00 UTC 这个起始时刻。
  • 时间戳为 1714521600 代表 2024 年 5 月 1 日 00:00:00 UTC 这个时刻,同时也是上海时区的 2024 年 5 月 1 日 08:00:00 GMT+08:00。

推荐常用的在线 UNIX 时间转换工具:

例如:设备上报一条昨天某时间点 2024-05-01 11:30:00 GMT+08:00 的温度数据,可以发布如下的消息:

[
    {"ts": 1714534200, "values": { "temperature": 22.5 } },
]

如果设备需要同时上报温度和湿度数据,可以发布如下的消息:

[
    {"ts": 1714534200, "values": { "temperature": 22.5, "humidity": 68.3 } },
]

也可以同时上报多条离线属性数据,例如发送如下消息:

[
    {"ts": 1717171200, "values": { "temperature": 12.5, "humidity": 50.3 }},
    {"ts": 1717174800, "values": { "temperature": 11.2, "humidity": 52.1 }},
    {"ts": 1717178400, "values": { "temperature": 10.6, "humidity": 53.3 }},
    {"ts": 1717182000, "values": { "temperature": 10.2, "humidity": 54.7 }},
    {"ts": 1717185600, "values": { "temperature": 9.5, "humidity": 55.5 }},
    {"ts": 1717189200, "values": { "temperature": 8.9, "humidity": 56.1 }},
    {"ts": 1717192800, "values": { "temperature": 8.5, "humidity": 56.8 }},
    {"ts": 1717196400, "values": { "temperature": 9.2, "humidity": 55.3 }},
    {"ts": 1717200000, "values": { "temperature": 10.5, "humidity": 54.1 }},
    {"ts": 1717203600, "values": { "temperature": 10.7, "humidity": 53.6 }}
]

以上示例中的 10 条属性数据,是上海时区 2024 年 6 月 1 日 00:00:00 起每小时一条的属性数据,包含温度和湿度。

上报成功后,在设备调试日志中,会陆续看到每条属性数据消息,与常规的属性上报消息不同的是,消息正文左侧有一个离线图标,如下图:

点击消息查看详情,可以看到与其它类型消息不同的时,多了一个上报时间,这就是设备上报离线属性数据时指定的时间戳 ts

在设备详情页的温度属性历史数据中,选定该时间段,可以看到上报的历史属性数据,如下图:

在设备详情页的湿度属性时序图表中,选定该时间段,可以看到上报的历史属性数据,如下图:

关于设备上报离线属性数据的一些注意事项:

  • 上报离线属性数据的功能主要应用在低功耗设备,应尽量减少设备不必要的上报操作,以节省能源消耗和网络资源。
  • 设备上报离线属性数据后,平台会将数据存储到属性历史数据中,但不会更新属性当前值。
  • 设备上报的离线属性数据包含多条时,平台会按数据时间点先后顺序,依次存入属性历史数据,这可能会持续一小段时间,在设备调试日志中可以刷新查看。
  • 设备上报离线属性数据,不会触发消息规则和告警规则。

对设备端的一些建议:

  • 设备端应具备本地时钟或网络时间同步机制,以确保在长时间离线后,设备的时间仍然能够与标准时间保持一致,从而确保数据的时间戳准确,否则将影响数据的准确性和可靠性。
  • 设备端应具备一定的本地持久化存储能力,以便在离线期间断电重启后,不会丢失之前保存的离线数据。

设备接收消息

如果想知道上报离线属性数据是否被平台成功接收,设备端可以订阅如下主题:

attributes/series/response

如果平台接收成功,响应消息如下:

{
  "result": 1,
  "ts": 1609143039050
}

如果平台未成功接收,响应消息中会包含错误原因,常见的错误原因如下:

  • 离线属性数据的时间范围无效。离线属性数据的时间戳必须在过去 180 天到当前时间之间,且时间跨度范围不能超过 30 天。
{
  "result": 0,
  "message": "Invalid attributes series time range",
  "errcode": 441
}
  • 离线属性数据条数超出限制,单次上报离线属性数据支持最多 100 条。
{
  "result": 0,
  "message": "Attributes series length exceeds the maximum limit",
  "errcode": 446
}
  • 离线属性数据的消息格式错误
{
  "result": 0,
  "message": "Invalid attributes series data format",
  "errcode": 402
}
  • 离线属性数据的时间间隔过短。平台要求同时上报多条离线属性数据的时间戳至少间隔 60 秒。
{
  "result": 0,
  "message": "Invalid interval time of attributes series",
  "errcode": 443
}
  • 离线属性数据的时间范围内,平台已存在属性历史数据。这种情况请调整离线属性数据的时间戳,以确保在该时间范围内不存在属性历史数据。
{
  "result": 0,
  "message": "Data already exists in the attributes series time range",
  "errcode": 442
}
  • 设备上报频率过快
{
  "result": 0,
  "message": "Device message frequency is too high, please wait for a moment",
  "errcode": 403
}

当包含多条离线属性数据时,需要间隔更长的时间,这时平台会提示需要等待的大概时间,如下:

{
  "result": 0,
  "message": "Device message frequency is too high, please wait for about {N} seconds",
  "errcode": 403
}
  • 已达到日消息量上限
{
  "result": 0,
  "message": "Device messages exceeds today maximum limit",
  "errcode": 413
}
  • 属性上报不符合设备类型的功能定义。当某条离线属性数据不符合设备类型的功能定义时,平台会拒绝该条离线属性数据的上报,并终止剩余离线属性数据的上报。
{
  "result": 0,
  "message": "Invalid attributes: device type attributes define check failed",
  "errcode": 406
}

MQTT 调试

在接入平台的设备开发过程中,有多种方法可以帮助您快速调试,并验证消息收发通信。

MQTT 客户端工具

使用 MQTT 客户端工具,例如:

MQTT.fx 设置连接到 ThingsCloud
MQTT.fx 设置连接到 ThingsCloud
MQTT.fx 发布数据到 ThingsCloud
MQTT.fx 发布数据到 ThingsCloud