产品定价 立即试用
边缘
API > 设备连接API > LWM2M设备API
入门 文档 安装 架构
常见问题
目录

LwM2M设备API参考

建议先阅读通用快速入门指南熟悉ThingsBoard基础,并查阅设备配置文档。

LwM2M基础

LwM2M (Lightweight M2M)是一种面向受限设备及机器对机器(M2M)环境的设备管理协议。更多信息请参见此处

LwM2M的一大优势是丰富的数据结构库,即LwM2M对象与资源注册表。最新可用对象列表见GitHub仓库

该注册表支持遥测数据的高效序列化/反序列化。LwM2M协议定义了以下流程:

  • 设备注册
  • 配置与管理
  • 固件/软件更新

LwM2M对象结构

DTLS Connection ID (CID) — 仅在启用安全模式时使用

  • 默认服务端CID长度现为8字节

(tb-lwm2m-transport.yml中的参数COAP_DTLS_CONNECTION_ID_LENGTH)。

  • 将服务端CID长度设为8字节会自动启用MultiNodeConnectionIdGenerator,为每个客户端会话分配唯一CID。可提升DTLS连接健壮性,并在多节点部署中保持稳定运行。

  • 当LWM2M_DTLS_CONNECTION_ID_LENGTH = 8时,将启用以下行为:

    • NodeID = 0x00的MultiNode Connection ID生成器(适用于单服务器部署)。

    • 确保DTLS会话在重连后保持连续性,尤其在NAT或不稳定网络环境中。

  • 表:LwM2M客户端与服务端CID长度对齐

CID客户端 CID服务端 连接模式 NodeID 说明
1 null null DTLS 1.2 (no CID) 双方均不支持CID
2 1 null DTLS 1.2 (no CID) 服务端不支持CID → 不使用CID
3 null 4 DTLS 1.2 (no CID) 客户端未发起CID → 无Connection ID握手
4 0 0 DTLS 1.2 (no CID) 允许CID但双方均未生成
5 1 1 DTLS 1.2 + CID active 短CID(SingleNode模式)
6 3 3 DTLS 1.2 + CID active CID = 3字节,单节点模式
7 1 5 DTLS 1.2 + CID active (MultiNode) 0x00 服务端CID > 4字节 → 服务端生成NodeID
8 2 8 DTLS 1.2 + CID active (MultiNode) 0x00 服务端CID = 8字节 → 服务端生成NodeID
9 16 2 DTLS 1.2 + CID active 服务端无NodeID,客户端使用长CID
10 null 8 DTLS 1.2 (no CID) 0x00 服务端处于MultiNode模式,但客户端不支持CID

LwM2M模型结构中的对象版本处理

模型结构中的每个对象始终包含 标签。 示例:3

将模型添加到配置文件时,系统按以下逻辑确定ObjectVersion:

如果模型中明确包含给定 标签,则使用该值作为ObjectVersion。

如果缺少 标签,系统在将模型结构添加到设备配置文件时会将ObjectVersion设为1.0。

⚠️ 注意:重要!!!ObjectVersion始终由ThingsBoard LwM2M传输层通过添加到对应设备配置文件的模型来控制。

在LwM2M客户端注册期间,系统会按照配置文件中的定义触发初始化流程:读取(属性和/或遥测数据)以及观测这些字段。 如果配置文件中的ObjectVersion与LwM2M客户端注册时发送的ObjectVersion不一致,该对象的所有初始化操作将被拒绝。

必须精确确定此版本号,因为它直接影响请求的生成和发送方式。 因此,从终端发送任何请求时,都必须指定对象的ObjectVersion。 如果省略此信息,系统将使用ObjectVersion = LwM2MVersion,该值通常由LwM2M设备在注册过程中提供。 如果注册请求中也缺少LwM2MVersion,则默认使用LwM2MVersion = 1.0。

从终端发送请求时的对象版本语法使用示例:

1
2
3
4
5
6
"/3_1.2/0/9"          // ObjectID = 3, ObjectVersion = 1.2
"/3/0/9"              // ObjectID = 3, ObjectVersion = LwM2MVersion. 
  // If LwM2MVersion = 1.1, the request sent to the LwM2M client will be: 
"/3_1.1/0/9"
  // If LwM2MVersion = 1.2, the request sent to the LwM2M client will be: 
"/3_1.2/0/9"

从终端发送请求时的对象版本处理

如果在ThingsBoard LwM2M传输层中注册的LwM2M客户端具有ObjectID = 3、ObjectVersion = 1.1,则请求”/3_1.1/0/9”将被成功处理。

如果LwM2M客户端的ObjectID = 3、ObjectVersion = 1.2,而请求为”/3_1.1/0/9”,则该请求将被拒绝并返回错误信息: “Invalid object version. Required version: 1.1”

1
2
3
4
5
6
7
  // LwM2M client registered in the ThingsBoard LwM2M transport has ObjectID = 3, ObjectVersion = 1.1
"/3_1.1/0/9"    // ok
"/3_1.2/0/9"    // return error

  // LwM2M client registered in the ThingsBoard LwM2M transport has ObjectID = 3, ObjectVersion = 1.2
"/3_1.1/0/9"    // return error
"/3_1.2/0/9"    // ok

资源处理

每个LwM2M对象实例包含多个资源

什么是LwM2M资源?

LwM2M资源表示可以从设备读取或写入设备的一段数据。

例如,资源”3.0.2”始终表示设备序列号

  • 3 – 对象ID
  • 0 – 对象实例
  • 2 – 资源ID

每个资源具有以下主要属性:

  • Name - 资源的可读名称
  • Type - 数据类型:String、Integer等
  • Operations - R(读取)、RW(读写)、E(执行)等

快速入门

本节介绍如何在ThingsBoard中配置您的第一台LwM2M设备。我们将使用ThingsBoard LwM2M演示客户端测试客户端来模拟LwM2M设备。

步骤1. 上传LwM2M模型

首先,租户管理员必须上传LwM2M模型。

提示:建议从官方GitHub仓库下载最新的LwM2M模型列表并全部导入。

请确保上传的LwM2M模型版本与实际设备使用的LwM2M对象版本一致。

  • 以系统管理员身份登录ThingsBoard实例。
  • 进入”Resources”部分下的”Resources library”页面。
  • 点击窗口右上角的”+”(添加资源)按钮。
  • 上传一个或多个LwM2M模型文件。
  • 点击”Add”完成上传。

上传完成后,可以在资源库中看到新添加的模型。

租户管理员可以:

  • 使用系统管理员上传的LwM2M模型。
  • 覆盖现有模型或上传自定义模型。

⚠️ 重要提示:租户管理员无法删除系统管理员上传的文件,只能删除自己上传的模型。

步骤2. 定义LwM2M设备配置文件

上传LwM2M模型后,可以用它们来配置LwM2M设备的设备配置文件。 关于设备配置文件的一般信息,请参阅此处

步骤2.1创建LwM2M配置文件

关键步骤是在传输配置阶段选择LwM2M传输类型。 该配置允许您定义设备支持的LwM2M对象列表。

  • 进入”Profiles”部分下的”Device profiles”页面。
  • 点击窗口右上角的”+”(添加设备配置)按钮,在弹出菜单中选择”Create new device profile”。
  • 输入配置文件名称。
  • 切换到”Transport configuration”选项卡。
  • 从下拉菜单中选择”LwM2M”作为传输类型。
  • 点击”Add”创建设备配置文件。

新配置文件已成功创建。

步骤2.2选择LwM2M对象

让我们在设备配置文件中定义以下LwM2M对象:

  • Device对象 - Device #3_1.2
  • 连接监控 - Connectivity Monitoring #4_1.3
  • 固件更新 - Firmware Update #5_1.1
  • 位置监控 - Location #6_1.0

按以下步骤操作:

  • 选择之前创建的LwM2M配置文件
  • 进入”Transport configuration“选项卡。
  • 点击”Edit“按钮。
  • 从下拉列表中添加对象

⚠️ 注意:这些对象必须预先加载到资源库中。

步骤2.3配置映射关系

现在配置ThingsBoard如何处理LwM2M对象数据:

  • Device对象提供制造商型号序列号。我们将配置ThingsBoard以属性形式接收这些数据。
  • 我们将观测并采集无线信号强度链路质量设备位置等数据,并将其作为遥测数据存储在ThingsBoard中。

LwM2M中的Observe功能允许服务器仅在值发生变化时接收数据。
您还可以通过LwM2M属性配置特定资源的上报条件(详见高级章节)。
设备配置文件中的所有设置用于在注册操作期间初始化LwM2M客户端。
如果LwM2M客户端会话处于活跃状态,设备配置文件的任何更改将立即生效;否则在下次更新注册时生效。

⚠️ 重要提示: 所有配置文件更改仅在配置文件中的对象版本与LwM2M客户端使用的版本匹配时才会生效,具体规则请参见LwM2M模型结构中的对象版本处理
如果不确定LwM2M客户端使用的对象版本,或者无法提前获取此信息,则在LwM2M客户端连接服务器后,客户端的遥测日志中会显示支持的对象及其版本的完整信息。

LwM2M客户端连接服务器后的注册日志示例:

1
info: Endpoint [MyClientNoSec] Client registered with registration id: [fR5In7YZNM] LwM2mVersion: [1.1], SupportedObjectIdVer [{1=1.1, 3442=1.0, 3=1.2, 19=1.0, 5=1.1, 6=1.0, 3303=1.1, 9=1.0}] QueueMode [false], BindingMode [U, T]

按以下步骤操作:

1. 对于每个已选对象:

  • 勾选”Attributes“复选框,以便在设备连接时读取数据并将其作为ThingsBoard属性存储。
  • 勾选”Telemetry“和/或”Observe“复选框,以便服务器监控这些值、获取更新并将其作为ThingsBoard遥测数据存储。

2. 使用Observe策略初始化属性和遥测

默认情况下,使用Observe策略初始化属性和遥测选项为禁用状态,即通过逐个读取的方式初始化属性和遥测数据;启用后,将通过所选的Observe策略订阅方式进行初始化。

3. Observe策略

默认的Observe策略设置为Single,但可以切换为Composite allComposite by object以减少流量或更高效地分组资源。

点击”Save“应用更改。

⚠️ 注意:如果取消勾选某个对象的所有项目(Attributes、Telemetry、Observe),该对象将不会显示在设备配置文件配置中。

此外,”Transport configuration“选项卡还允许配置引导设置和其他设置。

步骤2.3.1 Observe策略

ThingsBoard支持多种Observe策略,用于定义LwM2M资源的分组和监控方式。

  • Single(默认):每个资源单独观测。 ✓ 精度最高
    ✗ 网络流量较大。

  • Composite All:通过单个Composite Observe请求观测所有对象的所有资源。 ✓ 效率最高
    ✗ 粒度较粗。

  • Composite by Object:资源按对象类型分组,每组单独观测。 ✓ 精度和流量之间的平衡。

Observe策略在设备配置文件的”Transport configuration“选项卡中的”LWM2M Model“部分进行配置。


如果在客户端连接后通过配置文件更改了Observe策略,更改将在以下时机生效:

  • 如果LwM2M会话处于活跃状态,则立即生效
  • 如果会话不活跃,则在下次更新注册时生效。

如果您手动执行Observe操作(例如通过终端),请确保考虑当前的Observe策略:

  • 当前策略:Single新策略:Single 可以直接执行Observe相关操作,无需或可选择先对特定资源执行取消观测——详见Observe操作取消观测操作章节。

  • 当前策略:Composite by Object新策略:Composite by Object 可以直接执行Observe相关操作,无需或可选择先取消观测,具体取决于您的需求——详见取消复合观测操作复合观测操作,以及如有需要,Observe操作取消观测操作

  • 当前策略与新策略不同 在这种情况下,应始终先执行取消所有观测操作,然后再应用新的Observe策略或执行进一步的Observe相关操作。

步骤3. 定义LwM2M设备凭证

假设您已在上述步骤中成功创建LwM2M设备的设备配置文件。 现在让我们创建一个新设备,为其分配之前创建的LwM2M配置文件,并配置其凭证。

ThingsBoard支持四种凭证类型:

  • 预共享密钥 (PSK)
  • 原始公钥 (RPK)
  • X.509证书
  • 无安全(默认)

为简单起见,我们将使用”No Security“模式通过普速UDP连接设备:

🔐 在No Security模式下,仅需Endpoint Client Name即可标识设备。

  • 点击”Add”。

设备已添加。

要增强安全性,可通过启用DTLS模式使用其他凭证类型。 关于如何设置此模式,请参阅DTLS配置指南。

步骤4. 连接设备

此时,您应该已经完成:

现在可以启动客户端并在ThingsBoard中观察传入的遥测数据。

启动测试客户端

在终端中运行以下命令:

1
java -jar thingsboard-lwm2m-demo-client-{version}.jar -u coap://localhost -n $UNIQUE_ENDPOINT_NAME

or

1
java -jar thingsboard-lwm2m-demo-client-4.1.0.jar -u coap://localhost -n $UNIQUE_ENDPOINT_NAME

or

1
docker run --rm -it thingsboard/tb-lwm2m-demo-client:latest -u coap://localhost -n $UNIQUE_ENDPOINT_NAME

其中:

  • localhost - LwM2M服务器的主机名,LwM2M服务器的端口 = 5685
  • $UNIQUE_ENDPOINT_NAME - 端点的唯一名称(例如IMEI或其他唯一ID)

⚠️ 请确保将$UNIQUE_ENDPOINT_NAME替换为您的实际设备标识符。

监控遥测数据

客户端连接后:

  • 设备将在ThingsBoard LwM2M传输层注册。
  • 您将开始接收遥测数据。

LwM2M传输实现还会将与设备的通信日志存储为遥测数据。 可以在”Latest telemetry“选项卡的”transportLog“事件下查看这些日志。

ThingsBoard LwM2M支持

ThingsBoard完整支持LwM2M服务器和引导服务器,通信方式包括:

  • 普速UDP
  • DTLS(基于UDP的安全传输)

作为平台用户,您可以配置LwM2M设备并定义LwM2M资源与ThingsBoard设备属性遥测数据(时序数据)之间的映射关系。 这些映射在对应的LwM2M设备配置文件中配置。

关于创建设备配置文件的分步说明,请参阅快速入门指南

在以下章节中,我们将使用LwM2M设备配置文件UI截图来说明主要功能和配置步骤。

将LwM2M资源读取为ThingsBoard属性

您可以配置设备配置文件来读取和/或观测特定的LwM2M资源。配置后,这些资源的值将作为设备属性存储在ThingsBoard中。

要将资源存储为属性:

  • 进入LwM2M设备配置文件的”Transport configuration“选项卡。
  • 找到所需的LwM2M资源
  • 勾选”Attribute“复选框将其值存储为ThingsBoard属性。
  • 可选择修改自动生成的键名来定义自定义属性名称

ThingsBoard会在设备注册(LwM2M”Register”操作)或注册更新(LwM2M”Update”操作)时读取属性值。

示例: 配置平台读取LwM2M资源 /3/0/2(设备序列号)并将其作为名为”serialNumber“的属性存储在ThingsBoard中。

观测属性以实时更新

除了在注册时读取值外,您还可以选择观测LwM2M资源,以保持属性在值变化时始终为最新状态。 为此,请勾选所需资源的”Observe“复选框。这将使服务器订阅该资源的实时更新。

示例: 监控LwM2M资源 /3/0/15(时区)并将其值存储为”timezone“属性。

通过此设置,ThingsBoard中的timezone属性将始终包含Timezone资源的最新值。

通过ThingsBoard属性更新写入LwM2M资源

ThingsBoard支持通过共享属性将配置更新推送到LwM2M设备。 这些更新可以从以下来源发起:

当您更改共享属性时,ThingsBoard会在设备配置文件中搜索属性键与LwM2M资源之间的映射关系。 如果资源被标记为属性,平台将向LwM2M客户端设备发送LwM2M Write操作。

请参见读取属性章节中的Timezone示例。

将LwM2M资源读取为时序数据

您可以配置设备配置文件来读取和观测特定的LwM2M资源,并将其值作为遥测时序数据存储在ThingsBoard中。

要将资源存储为遥测数据:

  • 进入LwM2M设备配置文件的”Transport configuration“选项卡。
  • 找到所需的LwM2M资源
  • 勾选”Telemetry“复选框。
  • 可选择通过修改自动生成的键名来自定义遥测键名。

示例: 配置平台读取LwM2M资源/3/0/7(电源电压)、/3/0/8(电源电流)、/3/0/9(电池电量)和/3/0/10(可用内存),并将它们作为时序数据存储在ThingsBoard中:

使用ThingsBoard RPC命令执行LwM2M操作

ThingsBoard支持使用其远程过程调用(RPC)功能按需执行LwM2M操作。为简化起见,我们通常将RPC称为”命令“(commands)。

您可以通过以下方式发送这些命令:

⚠️ 注意:LwM2M设备的RPC命令结构和格式在平台内已有明确定义和标准化。请参考此文档 了解完整详情。

命令结构

每个RPC命令包含两个主要属性:

  • method” – 要执行的LwM2M操作类型
  • params” – 定义资源ID或多个资源ID的JSON对象。

支持的method值:

  • Execute - 用于LwM2M服务器发起某个动作(例如重启);
  • Read - 读取特定资源的当前值;
  • Discover - 发现对象或对象实例上可用的LwM2M资源;
  • WriteUpdate - 更新资源的值;
  • WriteAttributes - 修改与资源相关的属性;
  • ReadComposite - 选择性地读取任意对象组合;
  • WriteComposite - 修改跨一个或多个对象的不同实例中多个不同资源的值;
  • Delete - 删除LwM2M客户端内的对象实例;
  • Observe - 发起对特定资源变化的观测请求;
  • ObserveCancel - 结束之前通过”Observe”操作创建的观测关系;
  • ObserveCancelAll - ThingsBoard特有操作,允许一次取消设备上的所有观测;
  • ObserveReadAll - ThingsBoard特有操作,允许获取设备上设置的所有观测;
  • DiscoverAll - ThingsBoard特有操作,允许获取客户端上实例化的对象和资源层次结构。

示例:重启设备

要使用资源 /3/0/4 触发设备重启,向ThingsBoard发送以下RPC命令:

1
2
3
4
5
6
{
   "method": "Execute",
   "params": {
     "id": "/3/0/4"
   }
}

我们准备了一个简单的仪表盘来演示如何:

  • 通过/3/0/4重启设备
  • 更新属性(如/3/0/15(时区))

完成快速入门指南后,可以从Gist导入仪表盘

⚠️ 别忘了调整仪表盘别名以匹配您的设备或资产。

RPC命令

LwM2M传输层支持RPC命令,这些命令反映了设备管理和服务启用接口信息报告接口的子集。

设备管理和服务启用接口用于LwM2M服务器访问已注册LwM2M客户端上可用的对象实例和资源。资源支持的操作在对象定义中通过对象模板定义。

信息报告接口用于LwM2M服务器观测已注册LwM2M客户端上资源的任何变化,当新值可用时接收通知。通过向LwM2M客户端发送”Observe”或”Observe-Composite”操作来对对象、对象实例或资源发起观测关系。执行”Cancel Observation”或”Cancel Observation-Composite”操作即可结束观测关系。

我们将使用调试终端组件向设备发送命令。

要执行属性相关命令,有两种方式指定目标资源:按资源ID和按键名。

资源ID是”/ObjectId/ObjectInstance/ResourceID”数字的组合, 其中:

  • ObjectId” 表示对象编号。对象用于将设备上与特定功能相关的资源分组。
  • ObjectInstance” 表示要读取的对象实例。
  • ResourceID” 表示要读取的资源。

REST API的纯RPC调用示例:

1
2
3
4
{
   "method": "Read",
   "params": {"id": "/3/0/9"}
}

or

1
2
3
4
{
   "method": "Read",
   "params": {"key": "batteryLevel"}
}

调试终端中的对应输入示例:

1
Read {"id":"/3/0/9"}

键名是分配给某个属性的自定义友好名称:

RPC调试终端示例:

1
Read {"key":"batteryLevel"}

要使用键名,您需要在设备配置文件配置中将其分配给属性:

  • 进入”Device profiles”页面。
  • 点击配置文件名称打开详情。
  • 切换到”Transport configuration”选项卡。
  • 点击右上角的”铅笔”按钮编辑配置文件。
  • 在”LWM2M Model”选项卡中选择目标对象并展开”Attributes”列表。
  • 勾选所需属性的”Attribute”复选框并输入自定义键名。
  • 保存更改。

以下是ThingsBoard平台对LWM2M协议支持的命令使用示例。请注意,您的目标客户端可能不支持所有命令,请参考客户端文档了解支持的命令详情。

读取操作

“Read”操作用于访问资源、资源实例、资源实例数组、 对象实例或对象的所有对象实例的值。

示例:按ID读取资源值

REST API的RPC调用示例:

1
2
3
4
{
   "method": "Read",
   "params": {"id": "/3/0/9"}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
Read {"id":"/3/0/9"}

# Response:
{"result":"CONTENT","value":"LwM2mSingleResource [id=9, value=100, type=INTEGER]"}

更多示例:

按ID和版本读取资源值
1
2
3
4
5
# Request:
Read {"id":"/3_1.0/0/9"}

# Response:
{"result":"CONTENT","value":"LwM2mSingleResource [id=9, value=20, type=INTEGER]"}
按键名读取资源值
1
2
3
4
5
# Request:
Read {"key":"batteryLevel"}

# Response:
{"result":"CONTENT","value":"LwM2mSingleResource [id=9, value=27, type=INTEGER]"}
读取对象实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Request:
Read {"id":"/3/0"}

# Response:
{
  "result":"CONTENT","value":
    "
      LwM2mObjectInstance 
        [
          id=0, resources=
            {
              0=LwM2mSingleResource [id=0, value=Thingsboard Test Device, type=STRING], 
              1=LwM2mSingleResource [id=1, value=Model 500, type=STRING], 
              2=LwM2mSingleResource [id=2, value=TH-500-000-0001, type=STRING], 
              3=LwM2mSingleResource [id=3, value=TestThingsboard@TestMore1024_2.04, type=STRING], 
              6=LwM2mSingleResource [id=6, value=1, type=INTEGER], 
              7=LwM2mSingleResource [id=7, value=96, type=INTEGER], 
              8=LwM2mSingleResource [id=8, value=37, type=INTEGER], 
              9=LwM2mSingleResource [id=9, value=75, type=INTEGER], 
              10=LwM2mSingleResource [id=10, value=110673, type=INTEGER], 
              11=LwM2mMultipleResource 
                [
                  id=11, values=
                    {
                      0=LwM2mResourceInstance [id=0, value=1, type=INTEGER]
                    }, type=INTEGER
                ], 
              13=LwM2mSingleResource [id=13, value=Thu Jul 01 16:39:49 EEST 2021, type=TIME], 
              14=LwM2mSingleResource [id=14, value=+03, type=STRING], 
              15=LwM2mSingleResource [id=15, value=Europe/Kiev, type=STRING], 
              16=LwM2mSingleResource [id=16, value=U, type=STRING], 
              17=LwM2mSingleResource [id=17, value=smart meters, type=STRING], 
              18=LwM2mSingleResource [id=18, value=1.01, type=STRING], 
              19=LwM2mSingleResource [id=19, value=1.02, type=STRING], 
              20=LwM2mSingleResource [id=20, value=2, type=INTEGER], 
              21=LwM2mSingleResource [id=21, value=256000, type=INTEGER]
            }
        ]
    "
}

Discover操作

“Discover”操作用于发现对象或对象实例上可用的LwM2M资源。 此操作可用于发现给定对象实例中实例化了哪些资源。返回的负载是每个目标对象、对象实例或资源的application/link-format CoRE Links RFC6690列表,以及其分配或附加的属性(如有需要,包括对象版本属性)。

调试终端中的对应输入示例:

REST API的RPC调用示例:

1
2
3
4
{
   "method": "Discover",
   "params": {"id": "/3"}
}

调试终端中的对应输入示例:

1
2
3
4
5
6
# Request:
Discover {"id":"/3"}

# Response:
{"result":"CONTENT","value":"</3>,</3/0/0>,</3/0/1>,</3/0/2>,</3/0/3>,</3/0/4>,</3/0/5>,</3/0/6>,</3/0/7>,</3/0/8>,</3/0/9>,</3/0/10>,</3/0/11>,</3/0/12>,</3/0/13>,</3/0/
14>,</3/0/15>,</3/0/16>"}

更多示例:

发现附加到对象实例的资源
1
2
3
4
5
6
# Request:
Discover {"id":"/3/0"}

# Response:
{"result":"CONTENT","value":"</3/0>,</3/0/0>,</3/0/1>,</3/0/2>,</3/0/3>,</3/0/4>,</3/0/5>,</3/0/6>,</3/0/7>,</3/0/8>,</3/0/9>,</3/0/10>,</3/0/11>,</3/0/12>,</3/0/13>,</3/
0/14>,</3/0/15>,</3/0/16>"}
按ID发现资源是否已实例化
1
2
3
4
5
# Request:
Discover {"id":"/3/0/1"}

# Response:
{"result":"CONTENT","value":"</3/0/1>"}
按键名发现属性是否已实例化
1
2
3
4
5
# Request:
Discover {"key":"batteryLevel"}

# Response:
{"result":"CONTENT","value":"</3/0/9>"}

Write操作

“Write”操作用于更改资源的值、资源实例的值、资源实例数组的值或对象实例中多个资源的值。”Write”操作还可用于请求删除或分配多实例资源的特定实例。

请求中包含以7.4资源信息传输的数据格式中定义的格式编码的值:纯文本、opaque、TLV、JSON、CoRE Link、CBOR、SenML JSON和SenML CBOR。

有两种机制可以更改多个资源或资源实例数组:

替换:用”Write”操作中提供的新值替换对象实例或资源。当资源是多实例资源时,现有的资源实例数组将被替换,前提是LwM2M客户端授权该操作。

部分更新:更新新值中提供的资源,保留其他现有资源不变。当资源是多实例资源时,现有的资源实例数组会被更新,即某些实例可能被创建或覆盖,前提是LwM2M客户端授权此类操作。通过部分更新无法删除资源。

示例:WriteUpdate单对象实例资源

REST API的RPC调用示例:

1
2
3
4
{
   "method": "WriteUpdate",
   "params": {"id":"/3/0","value":{"14":"+5","15":"Kiyv/Europe"}}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
WriteUpdate  {"id":"/3/0","value":{"14":"+5","15":"Kiyv/Europe"}}

# Response:
{"result":"CHANGED"}

更多示例:

WriteUpdate多对象实例资源
1
2
3
4
5
# Request:
WriteUpdate {"id": "/19/0","value": {"0":{"0":"00ad456756", "25":"25ad456756"}}}

# Response:
{"result":"CHANGED"}
WriteUpdate多资源
1
2
3
4
5
# Request:
WriteUpdate {"id": "/19/0/0","value": {"0":"00ad456756", "25":"25ad456756"}}

# Response:
{"result":"CHANGED"}
WriteReplace单资源
1
2
3
4
5
# Request:
WriteReplace {"id":"/19/0/0","value":"0081"}

# Response:
{"result":"CHANGED"}
按键名WriteReplace单资源
1
2
3
4
5
# Request:
WriteReplace {"key":"timezone","value":"+10"}

# Response:
{"result":"CHANGED"}
WriteReplace多资源
1
2
3
4
5
# Request:
WriteReplace {"id": "/19_1.1/0/0","value": {"0":"00ad456797", "25":"25ad456700"}}

# Response:
{"result":"CHANGED"}

Write-Attributes操作

只有NOTIFICATION类的属性才可以通过”Write-Attributes”操作进行更改。 对象和资源属性章节提供了”Write-Attributes”操作支持的属性说明: Minimum Period、Maximum Period、Greater Than、Less Than、Step。 该操作允许在同一操作中修改多个属性。

示例:写入多个属性

REST API的RPC调用示例:

1
2
3
4
{
   "method": "WriteAttributes",
   "params": {"id":"/19/0/0","attributes":{"pmax":120, "pmin":10}}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
WriteAttributes {"id":"/19/0/0","attributes":{"pmax":120, "pmin":10}}

# Response:
{"result":"CHANGED"}

Read-Composite操作

LwM2M客户端可以支持”Read-Composite”操作。 “Read-Composite”操作可用于LwM2M服务器在单个请求中有选择地读取不同或相同对象的任意组合,包括对象、对象实例、资源和/或资源实例。要读取的元素列表以SenML Pack格式提供,其中记录包含Base Name和/或Name字段,但不包含Value字段。Read-Composite操作被视为非原子操作,客户端尽力处理。即如果请求的任何资源没有有效值可返回,则不会包含在响应中。

示例:读取多个对象

REST API的RPC调用示例:

1
2
3
4
{
   "method": "ReadComposite",
   "params": {"ids":["/3/0/9", "/1_1.2"]}
}

调试终端中的对应输入示例:

1
2
3
4
5
6
7
# Request:
ReadComposite {"ids":["/3/0/9", "/1_1.2"]}

# Response:
{"result":"CONTENT","value":"{/3/0/9=LwM2mSingleResource [id=9, value=75, type=INTEGER], /1=LwM2mObject [id=1, instances={0=LwM2mObjectInstance [id=0, resources={0=LwM2mSingleResource [id=0, value=123, t
ype=INTEGER], 1=LwM2mSingleResource [id=1, value=300, type=INTEGER], 6=LwM2mSingleResource [id=6, value=false, type=BOOLEAN], 22=LwM2mSingleResource [id=22, value=U, type=STRING], 7=LwM2mSingleResource [
id=7, value=U, type=STRING]}]}]}"

更多示例:

按键名ReadComposite多资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Request:
ReadComposite {"keys":["state", "updateResult", "pkgversion", "batteryLevel"]}

# Response:
{
  "result":"CONTENT","value":
    "
      {
        /5/0/7=LwM2mSingleResource [id=7, value=, type=STRING], 
        /5/0/5=LwM2mSingleResource [id=5, value=0, type=INTEGER], 
        /5/0/3=LwM2mSingleResource [id=3, value=0, type=INTEGER], 
        /3/0/9=LwM2mSingleResource [id=9, value=81, type=INTEGER]
      }
    "
}
ReadComposite多对象实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# Request:
ReadComposite {"ids":["/3/0", "/1_1.2/0"]}

# Response:
{
  "result":"CONTENT","value":
  "
    {
      /3/0=LwM2mObjectInstance 
        [id=0, resources=
          {
            0=LwM2mSingleResource [id=0, value=Thingsboard Test Device, type=STRING], 
            1=LwM2mSingleResource [id=1, value=Model 500, type=STRING], 
            2=LwM2mSingleResource [id=2, value=TH-500-000-0001, type=STRING], 
            3=LwM2mSingleResource [id=3, value=TestThingsboard@TestMore1024_2.04, type=STRING], 
            6=LwM2mSingleResource [id=6, value=1, type=INTEGER], 
            7=LwM2mSingleResource [id=7, value=2, type=INTEGER], 
            8=LwM2mSingleResource [id=8, value=61, type=INTEGER], 
            9=LwM2mSingleResource [id=9, value=25, type=INTEGER], 
            10=LwM2mSingleResource [id=10, value=102044, type=INTEGER], 
            11=LwM2mMultipleResource 
              [
                id=11, values=
                  {
                    0=LwM2mResourceInstance [id=0, value=1, type=INTEGER]
                  }, 
                type=INTEGER
              ], 
            13=LwM2mSingleResource [id=13, value=Thu Jul 01 16:49:25 EEST 2021, type=TIME], 
            14=LwM2mSingleResource [id=14, value=+03, type=STRING], 
            15=LwM2mSingleResource [id=15, value=Europe/Kiev, type=STRING], 
            16=LwM2mSingleResource [id=16, value=U, type=STRING], 
            17=LwM2mSingleResource [id=17, value=smart meters, type=STRING], 
            18=LwM2mSingleResource [id=18, value=1.01, type=STRING], 
            19=LwM2mSingleResource [id=19, value=1.02, type=STRING], 
                    20=LwM2mSingleResource [id=20, value=1, type=INTEGER], 
            21=LwM2mSingleResource [id=21, value=256000, type=INTEGER]
          }
        ], 
      /1/0=LwM2mObjectInstance 
        [id=0, resources=
          {
            0=LwM2mSingleResource [id=0, value=123, type=INTEGER], 
            1=LwM2mSingleResource [id=1, value=300, type=INTEGER], 
            6=LwM2mSingleResource [id=6, value=false, type=BOOLEAN],
            7=LwM2mSingleResource [id=7, value=U, type=STRING],
            22=LwM2mSingleResource [id=22, value=U, type=STRING]
          }
        ]
    }
  "
}

Write-Composite操作

LwM2M客户端可以支持”Write-Composite”操作。 与”Write”操作不同(其范围仅限于单个对象的单个实例的资源),”Write-Composite”操作可用于服务器更新跨一个或多个对象的不同实例的多个不同资源的值。Write-Composite操作使用SenML JSON/CBOR格式提供所有要更新的资源及其新值的列表。与Write操作不同,未提供的资源不受该操作影响。

“Write-Composite”操作是原子操作,不能部分成功。即如果客户端支持此操作,当无法成功写入所有请求的值时,必须拒绝服务器请求。因此,在处理Write-Composite之前,客户端必须确保所有寻址的对象都存在,并且服务器对这些对象和资源具有写入权限。

示例:WriteComposite到多个对象

REST API的RPC调用示例:

1
2
3
4
{
   "method": "WriteComposite",
   "params": {"nodes":{"/19_1.1/0":{"0":{"0":"00ad45675600", "25":"25ad45675600cdef"}}, "UtfOffset":"+04", "/3_1.0/0/15":"Kiyv/Europe"}}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
WriteComposite {"nodes":{"/19_1.1/0":{"0":{"0":"00ad45675600", "25":"25ad45675600cdef"}}, "UtfOffset":"+04", "/3_1.0/0/15":"Kiyv/Europe"}}

# Response:
{"result":"CHANGED"}

Execute操作

“Execute”操作用于LwM2M服务器发起某些操作,且只能在单个资源上执行。

示例:执行资源

REST API的RPC调用示例:

1
2
3
4
{
   "method": "Execute",
   "params": {"id":"5/0/2"}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
Execute {"id":"5/0/2"}

# Response:
{"result":"CHANGED"}
  • 替代命令输入可通过设备凭证界面使用:

“client registration update”(调试终端中:Execute {“id”:”1/0/8”}): Device → Details → Manage credentials → Device credential → Client security config → 按钮:Registration Update Trigger

“bootstrap-reboot”(调试终端中:Execute {“id”:”1/0/9”}): Device → Details → Manage credentials → Device credential → Bootstrap client → 按钮:Bootstrap-Request Trigger

Delete操作

“Delete”操作用于LwM2M服务器删除LwM2M客户端中的对象实例。 被LwM2M服务器删除的对象实例必须是LwM2M客户端通过客户端注册接口的”Register”和”Update”操作向LwM2M服务器声明的对象实例。

唯一的例外是强制Device对象(ID:3)的单一实例,它不受任何Delete操作影响。

示例:删除对象实例

REST API的RPC调用示例:

1
2
3
4
{
   "method": "Delete",
   "params": {"id":"/19/1"}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
Delete {"id":"/19/1"}

# Response:
{"result":"DELETE"}

Observe操作

LwM2M服务器对LwM2M客户端中特定资源、对象实例内的资源或对象的所有对象实例发起变化观测请求。 “Observe”操作的相关参数在通知属性 Write-Attributes操作中描述,这些参数通过”Write-Attributes”操作配置。

示例:观测资源

REST API的RPC调用示例:

1
2
3
4
{
   "method": "Observe",
   "params": {"id":"/3/0/9"}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
Observe {"id":"/3/0/9"}

# Response:
{"result":"CONTENT","value":"LwM2mSingleResource [id=9, value=28, type=INTEGER]"}

更多示例:

观测对象实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Request:
Observe {"id":"/3/0"}
 
# Response:
{"result":"CONTENT","value":
  "LwM2mObjectInstance 
    [id=0, resources=
      {
        0=LwM2mSingleResource [id=0, value=Thingsboard Test Device, type=STRING], 
        1=LwM2mSingleResource [id=1, value=Model 500, type=STRING], 
        2=LwM2mSingleResource [id=2, value=TH-500-000-0001, type=STRING], 
        3=LwM2mSingleResource [id=3, value=TestThingsboard@TestMore1024_2.04, type=STRING], 
        6=LwM2mSingleResource [id=6, value=1, type=INTEGER], 
        7=LwM2mSingleResource [id=7, value=90, type=INTEGER], 
        8=LwM2mSingleResource [id=8, value=29, type=INTEGER], 
        9=LwM2mSingleResource [id=9, value=19, type=INTEGER], 
        10=LwM2mSingleResource [id=10, value=76962, type=INTEGER], 
        11=LwM2mMultipleResource 
          [
            id=11, values=
            {
              0=LwM2mResourceInstance [id=0, value=1, type=INTEGER]
            }, type=INTEGER
          ], 
        13=LwM2mSingleResource [id=13, value=Wed Jul 31 22:49:45 EET 1940, type=TIME], 
        14=LwM2mSingleResource [id=14, value=+5, type=STRING], 
        15=LwM2mSingleResource [id=15, value=Kiyv/Europe, type=STRING], 
        16=LwM2mSingleResource [id=16, value=U, type=STRING], 
        17=LwM2mSingleResource [id=17, value=smart meters, type=STRING], 
        18=LwM2mSingleResource [id=18, value=1.01, type=STRING], 
        19=LwM2mSingleResource [id=19, value=1.02, type=STRING], 
        20=LwM2mSingleResource [id=20, value=6, type=INTEGER], 
        21=LwM2mSingleResource [id=21, value=256000, type=INTEGER]
      }
    ]
  "
}

Observe-Composite操作

LwM2M客户端可以支持”Observe-Composite”操作。 LwM2M服务器可以使用”Observe-Composite”操作对客户端中跨多个对象实例的一组资源和/或资源实例发起观测。与”Read-Composite”操作类似,要观测的元素列表以SenML JSON/CBOR格式作为单独参数提供给操作。

示例:ObserveComposite多个资源

REST API的RPC调用示例:

1
2
3
4
{
   "method": "ObserveComposite",
   "params": {"ids":["/5/0/7", "/5/0/5", "/5/0/3", "/3/0/9", "/19/1/0/0"]}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
ObserveComposite {"ids":["/5/0/7", "/5/0/5", "/5/0/3", "/3/0/9", "/19/1/0/0"]}

# Response:
{"result":"CONTENT","value":"{/5/0/7=LwM2mSingleResource [id=7, value=1.0.0, type=STRING], /5/0/5=LwM2mSingleResource [id=5, value=0, type=INTEGER], /19/1/0/0=LwM2mResourceInstance [id=0, value=1Bytes, type=OPAQUE], /5/0/3=LwM2mSingleResource [id=3, value=0, type=INTEGER], /3/0/9=LwM2mSingleResource [id=9, value=50, type=INTEGER]}"}

更多示例:

ObserveComposite多资源和对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# Request:
ObserveComposite {"ids":["/5_1.2/0/7", "/5_1.2/0/5", "/5_1.2/0/3", "/3", "/19_1.1/1/0/0"]}

# Response:
{"result":"CONTENT","value":
  "{
    /3=LwM2mObject [id=3, instances={0=LwM2mObjectInstance [id=0, 
      resources={
        0=LwM2mSingleResource [id=0, value=Thingsboard Demo Lwm2mDevice, type=STRING], 
        1=LwM2mSingleResource [id=1, value=Model 500, type=STRING], 
        2=LwM2mSingleResource [id=2, value=Thingsboard-500-000-0001, type=STRING], 
        3=LwM2mSingleResource [id=3, value=1.0.2, type=STRING], 
        6=LwM2mMultipleResource [id=6, values={
          0=LwM2mResourceInstance [id=0, value=0, type=INTEGER], 
          1=LwM2mResourceInstance [id=1, value=1, type=INTEGER], 
          2=LwM2mResourceInstance [id=2, value=7, type=INTEGER]}, type=INTEGER], 
        7=LwM2mMultipleResource [id=7, values={
          0=LwM2mResourceInstance [id=0, value=12000, type=INTEGER], 
          1=LwM2mResourceInstance [id=1, value=12400, type=INTEGER], 
          7=LwM2mResourceInstance [id=7, value=14600, type=INTEGER]}, type=INTEGER], 
        8=LwM2mMultipleResource [id=8, values={
          0=LwM2mResourceInstance [id=0, value=72000, type=INTEGER], 
          1=LwM2mResourceInstance [id=1, value=2000, type=INTEGER], 
          7=LwM2mResourceInstance [id=7, value=25000, type=INTEGER]}, type=INTEGER], 
        9=LwM2mSingleResource [id=9, value=22, type=INTEGER], 
        10=LwM2mSingleResource [id=10, value=207895, type=INTEGER], 
        11=LwM2mMultipleResource [id=11, values={
          0=LwM2mResourceInstance [id=0, value=0, type=INTEGER]}, type=INTEGER], 
        14=LwM2mSingleResource [id=14, value=+03, type=STRING], 
        15=LwM2mSingleResource [id=15, value=Europe/Kiev, type=STRING], 
        16=LwM2mSingleResource [id=16, value=U, type=STRING], 
        17=LwM2mSingleResource [id=17, value=Demo, type=STRING], 
        18=LwM2mSingleResource [id=18, value=1.0.1, type=STRING], 
        19=LwM2mSingleResource [id=19, value=1.0.2, type=STRING], 
        20=LwM2mSingleResource [id=20, value=3, type=INTEGER], 
        21=LwM2mSingleResource [id=21, value=638976, type=INTEGER]}]}], 
    /5/0/7=LwM2mSingleResource [id=7, value=1.0.0, type=STRING], 
    /5/0/5=LwM2mSingleResource [id=5, value=0, type=INTEGER], 
    /19/1/0/0=LwM2mResourceInstance [id=0, value=1Bytes, type=OPAQUE], 
    /5/0/3=LwM2mSingleResource [id=3, value=0, type=INTEGER]
  }"
}

取消观测操作

“Cancel Observation”操作由LwM2M服务器发送给LwM2M客户端,用于结束之前通过”Observe”操作创建的观测关系。

示例:按ID取消资源观测

REST API的RPC调用示例:

1
2
3
4
{
   "method": "ObserveCancel",
   "params": {"id":"/5/0/7"}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
ObserveCancel {"id":"/5/0/7"}

# Response:
{"result":"CONTENT","value":"1"}

更多示例:

示例:按键名取消资源观测
1
2
3
4
5
# Request:
ObserveCancel {"key":"updateResult"}

# Response:
{"result":"CONTENT","value":"1"}

取消复合观测操作

如果客户端支持”Observe-Composite”操作,则必须支持”Cancel Observation-Composite”操作。 “Cancel Observation-Composite”操作由LwM2M服务器发送给LwM2M客户端,用于结束之前设置的复合观测关系。

示例:ObserveComposite多个对象或资源

REST API的RPC调用示例:

1
2
3
4
{
   "method": "ObserveCompositeCancel",
   "params": {"ids":["/5/0/7", "/5/0/5", "/5/0/3", "/3/0/9", "/19/1/0/0"]}
}

调试终端中的对应输入示例:

1
2
3
4
5
6
7
8
# Request:
ObserveComposite {"ids":["/5/0/7", "/5/0/5", "/5/0/3", "/3/0/9", "/19/1/0/0"]}

# Request:
ObserveCompositeCancel {"ids":["/5/0/7", "/5/0/5", "/5/0/3", "/3/0/9", "/19/1/0/0"]} 

# Response:
{"result":"CONTENT","value":"1"}

取消所有观测操作

“Cancel All Observations”操作是ThingsBoard特有操作,允许一次性取消设备上的所有观测。

示例:取消所有观测

REST API的RPC调用示例:

1
2
3
4
{
   "method": "ObserveCancelAll",
   "params": {}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
ObserveCancelAll

# Response:
{"result":"CONTENT","value":"8"} // - cancelled 8 observations

读取所有观测操作

“Read All Observations”操作是ThingsBoard特有操作,允许获取设备上设置的所有观测。

示例:读取所有观测

REST API的RPC调用示例:

1
2
3
4
{
   "method": "ObserveReadAll",
   "params": {}
}

调试终端中的对应输入示例:

1
2
3
4
5
# Request:
ObserveReadAll

# Response:
{"result":"CONTENT","value":"[\"/5/0/7\",\"/3/0/3\",\"/5/0/3\",\"/5/0/5\"]"}

发现所有操作

“Discover All”操作是ThingsBoard特有操作,允许获取客户端上实例化的对象和资源层次结构。执行DiscoverAll时,不会向客户端设备发送任何请求,而是返回设备连接服务器时创建的客户端LwM2M模型。 此命令对设备设置和故障排查非常有用,因为它可以查看可用的对象及其版本。

示例:发现所有资源

REST API的RPC调用示例:

1
2
3
4
{
   "method": "DiscoverAll",
   "params": {}
}

调试终端中的对应输入示例:

1
2
3
4
5
6
7
8
9
10
# Request:
DiscoverAll

# Response:
{"result":"CONTENT","value":"[{\"url\":\"/\",\"attributes\":{\"ct\":\"110\",\"rt\":\"\\\"oma.lwm2m\\\"\"}},
{\"url\":\"/1\",\"attributes\":{\"ver\":\"1.1\"}},{\"url\":\"/1/0\",\"attributes\":{}},{\"url\":\"/2/0\",
\"attributes\":{}},{\"url\":\"/3/0\",\"attributes\":{}},{\"url\":\"/4/0\",\"attributes\":{}},{\"url\":\"/5/0\",
\"attributes\":{}},{\"url\":\"/6/0\",\"attributes\":{}},{\"url\":\"/7/0\",\"attributes\":{}},{\"url\":\"/31024\",
\"attributes\":{\"ver\":\"1.0\"}},{\"url\":\"/31024/10\",\"attributes\":{}},{\"url\":\"/31024/11\",\"attributes\":{}},
{\"url\":\"/31024/12\",\"attributes\":{}}]"}

固件空中升级

LwM2M协议支持对已连接设备执行空中(OTA)固件更新。 在继续之前,请参阅OTA更新指南了解如何上传、管理和分发固件包,以及了解更新流程。

LwM2M定义了对象5:固件更新对象,专门用于OTA目的。此对象支持:

  • 上传和管理固件镜像
  • 安装固件包
  • 跟踪更新进度和更新后行为

⚠️ 注意:对象5是可选的,某些设备可能不支持。

要使用对象5运行更新,您需要确保对象5存在于设备配置文件LwM2M模型中,并在设备上设置以下属性的观测,服务器通过这些属性获取设备的更新进度反馈:

1
2
3
4
"/3/0/3" - Firmware Version
"/5/0/3" - State
"/5/0/5" - Update Result
"/5/0/7" - PkgVersion

固件更新策略

ThingsBoard提供多种策略通过LwM2M传输层运行OTA固件更新:

  • 使用对象5和资源0(Package)以二进制文件形式推送固件更新
  • 自动生成唯一的CoAP URL下载包,通过对象5和资源1(Package URI)推送固件更新
  • 使用对象19和资源0(Data)以二进制文件形式推送固件更新

所选策略在设备配置文件中配置,并应用于与该配置文件关联的所有设备。

要选择固件更新策略:

  1. 打开设备配置文件设置
  2. 进入”Transport configuration“选项卡的”Other settings“。
  3. 进入编辑模式并从下拉菜单中选择固件更新策略
  4. 保存更改。

使用对象19传递OTA文件元数据(可选)

ThingsBoard还支持对象19,用于传递固件元数据

⚠️ 此功能是对象5的补充,而非替代。

要启用对象19:

  1. 打开设备配置文件设置
  2. 进入”Transport configuration“选项卡的”Other settings“。
  3. 进入编辑模式并勾选”Use Object 19 for OTA file metadata (checksum, size, version, name)“选项。
  4. 保存更改。

启用此选项后,ThingsBoard将:

  1. 在设备连接时,ThingsBoard验证设备是否支持对象19
  2. 如果存在,ThingsBoard创建一个InstanceId = 65534的对象19实例(用于固件元数据)。
  3. FOTA元数据以Base64编码的JSON对象形式发送到此实例。

FOTA元数据JSON结构:

1
2
3
4
5
6
7
{
  "Checksum": "SHA256 hash of the firmware file",
  "Title": "OTA package name",
  "Version": "Firmware version",
  "File Name": "File name for storing the OTA on the client",
  "File Size": "Size of the firmware file in bytes"
}

⚠️ 注意:对象19仅用于元数据传递。实际的固件更新逻辑根据设备配置文件中定义的固件更新策略来处理。

固件更新流程

固件更新机制如固件更新机制UML图所示。 状态图由圆角矩形表示的状态和连接状态的箭头表示的转换组成。

使用对象5和资源0以二进制文件形式推送固件更新

固件包通过块传输从服务器直接推送到设备的对象5的资源0。下载完成后,应使用可执行资源”/5/0/2”触发更新过程。完整流程请参见:LwM2M服务器向LwM2M客户端推送固件镜像的示例

自动生成唯一CoAP URL下载包并通过对象5和资源1推送固件包

此选项允许使用位于第三方存储上的镮像文件来运行固件更新。此情况下,服务器生成CoAP-URL并发送给客户端,客户端直接从外部资源下载固件镜像,无需将镜像传输到服务器。下载完成后,应使用可执行资源”/5/0/2”触发更新过程。完整流程请参见:客户端获取固件镜像的示例

软件空中升级

LwM2M协议允许通过空中(OTA)机制远程分发软件更新。 在继续之前,请阅读OTA更新指南了解如何上传、管理和分发软件包。

与固件更新不同,LwM2M中的软件管理过程分为两个独立阶段:

  • 软件包安装过程
  • 软件激活过程

LwM2M定义了对象9用于软件管理。它支持:

  • 远程传递软件包
  • 执行安装和激活程序
  • 报告软件状态转换和结果

⚠️ 注意:对象9是可选的,某些设备可能不支持。

要使用对象9运行更新,您需要确保对象9存在于设备配置文件的LwM2M模型中,并在设备上设置以下属性的观测,服务器通过这些属性获取设备的更新进度反馈:

1
2
3
4
5
6
7
"/3/0/19" - Software Version
"/9/0/0" - PkgName
"/9/0/1" - PkgVersion
"/9/0/2" - Package ID
"/9/0/3" - Package URI
"/9/0/7" - Update State
"/9/0/9" - Update result

软件更新策略

ThingsBoard支持多种方式通过LwM2M传输层发起软件更新:

  • 使用对象9和资源2(Package)以二进制文件形式推送
  • 自动生成唯一的CoAP URL下载包,通过对象9和资源3(Package URI)推送软件更新

所选策略在设备配置文件中配置,并应用于与该配置文件关联的所有设备。

要选择软件更新策略:

  1. 打开设备配置文件设置。
  2. 进入”Transport configuration“选项卡的”Other settings“。
  3. 进入编辑模式并从下拉菜单中选择软件更新策略
  4. 保存更改。

使用对象19传递OTA文件元数据(可选)

ThingsBoard还支持对象19来传递软件更新元数据(SOTA):

⚠️ 此功能是对象5的补充,而非替代。

要启用对象19:

  1. 打开设备配置文件设置
  2. 进入”Transport configuration“选项卡的”Other settings“。
  3. 进入编辑模式并勾选”Use Object 19 for OTA file metadata (checksum, size, version, name)“选项。
  4. 保存更改。

启用此选项后,ThingsBoard将:

  1. 在设备连接时,ThingsBoard验证设备是否支持对象19
  2. 如果存在,ThingsBoard创建一个InstanceId = 65535的对象19实例(用于固件元数据)。
  3. SOTA元数据以Base64编码的JSON对象形式发送到此实例。

SOTA元数据JSON结构:

1
2
3
4
5
6
7
{
  "Checksum": "SHA256 hash of the software file",
  "Title": "OTA package name",
  "Version": "Software version",
  "File Name": "File name for storing the OTA on the client",
  "File Size": "Size of the software file in bytes"
}

⚠️ 注意:对象19仅用于元数据传递。实际的软件更新逻辑根据设备配置文件中定义的软件更新策略来处理。

软件更新流程:LwM2M软件更新状态转换

成功的软件更新场景

步骤 SoftwareUpdateState SoftwareUpdateResult 描述
1 INITIAL (0) INITIAL (0) 任何下载开始前的初始状态
2 DOWNLOAD_STARTED (1) DOWNLOADING (1) 下载过程已开始
3 DOWNLOADED (2) DOWNLOADING (1) 软件包已下载并完成完整性验证
4 DELIVERED (3) SUCCESSFULLY_DOWNLOADED_VERIFIED (3) 软件包已准备好安装
5 INSTALLED (4) SOFTWARE_SUCCESSFULLY_INSTALLED (2) 软件已成功安装
6 INITIAL (0) INITIAL (0) 卸载后返回初始状态

可以通过多种方式使用LwM2M传输层触发OTA软件更新。 可以在设备配置文件中选择软件更新策略,该策略定义此配置文件下所有设备的更新执行方式。

使用对象9和资源2以二进制文件形式推送软件更新

软件包通过块传输从服务器直接推送到设备的对象9的资源2。

自动生成唯一CoAP URL下载包并通过对象9和资源3推送软件包

此选项允许使用位于第三方存储上的镜像文件来运行软件更新。此情况下,服务器生成CoAP-URL并发送给客户端,客户端直接从外部资源下载软件镜像,无需将镜像传输到服务器。

使用ThingsBoard LwM2M演示客户端测试OTA

ThingsBoard LwM2M演示客户端是一个命令行工具,用于模拟LwM2M客户端并将其连接到ThingsBoard服务器。

此客户端可用于测试OTA固件和软件更新,支持以下功能:

1
2
3
4
5
6
7
8
9
可配置的服务器连接参数(主机、端口、端点名称)
使用NoSec的无安全通信
使用DTLS的安全通信(支持PSK、RPK、x509)
动态和静态LwM2M对象模型定义
模拟各种LwM2M对象,包括:
    对象5(固件更新)
    对象9(软件管理)
    对象19(OTA元数据)
更新和状态转换日志记录

此客户端特别适用于验证设备配置文件中设置的”固件更新策略”和”软件更新策略”,以及确保通过对象19的OTA元数据传递的兼容性。

开始使用:

  1. 克隆项目:
1
git clone https://github.com/thingsboard/thingsboard.lwm2m.demo.client
  1. 使用Maven构建客户端:
1
mvn clean install
  1. 使用自定义参数运行客户端:
1
java -jar thingsboard-lwm2m-demo-client-{version}.jar -u coap://localhost -n MyClientNoSec -tota

完整的使用说明和高级配置选项请参阅README

高级主题

对象和资源属性

请注意,LwM2M上下文中的属性与ThingsBoard平台上的服务器、客户端或共享属性不同且无关。

在LwM2M协议中,属性是可以附加到对象、对象实例或资源的元数据。这些属性可以承担多种角色,从仅携带信息到携带用于在LwM2M客户端上设置某些操作(例如通知)的参数。

附加到对象、对象实例、资源的属性分别称为O-Attribute、OI-Attribute、R-Attribute。

这些属性可以在注册和Discover操作的消息负载中携带;当可写时,也可以通过Write-Attributes操作进行更新。

属性分为两种类型:

PROPERTIES类属性,或对象属性

这些属性的作用是提供元数据,例如向LwM2M服务器传达有用信息,简化数据管理。ThingsBoard支持对象版本属性,该属性指示关联对象的版本,并显示在DiscoverAll命令的结果中。

关于LwM2M对象属性的更多详情请参见:PROPERTIES类属性

NOTIFICATION类属性,或资源属性

这些R-Attribute的作用是为”Notify”操作提供参数,用于资源观测。任何可读资源都可以具有此类R-Attribute。

以下是ThingsBoard平台上可用于配置观测参数的NOTIFICATION属性:

  • “pmin” - 最小周期 - 表示LwM2M客户端在两次通知之间必须等待的最小时间(秒)。 如果观测资源的通知应该生成但尚未到达pmin过期时间,则必须在pmin过期后立即发送通知。如果缺少此参数,最小周期由LwM2M服务器账户中设置的默认最小周期定义。

  • “pmax” - 最大周期 - 表示LwM2M客户端在两次通知之间可以等待的最大时间(秒)。 当上次通知后”最大周期”过期时,必须发送新通知。如果缺少此参数,”最大周期”由LwM2M服务器账户中设置的默认最大周期定义,否则视为0。值为0表示pmax必须被忽略。最大周期参数必须大于最小周期参数,否则对于应用了此类不一致时间条件的资源,pmax将被忽略。

  • “gt” - 大于 - 定义一个高阈值。当此属性存在时,LwM2M客户端必须在观测资源值跨越此阈值时通知服务器,同时遵守pmin参数和有效的”值变化条件”(参见上述通知条件)。

  • “lt” - 小于 - 定义一个低阈值。当此属性存在时,LwM2M客户端必须在观测资源值跨越此阈值时通知服务器,同时遵守pmin参数和有效的”值变化条件”(参见上述通知条件)。

  • “st” - 步长 - 定义一个阈值。当此属性存在时,LwM2M客户端必须在观测资源值跨越此阈值时通知服务器,同时遵守pmin参数和有效的”值变化条件”(参见上述通知条件)。

关于LwM2M NOTIFICATION属性的更多详情请参见此处

通知属性可以在设备配置文件中配置,请按以下指南操作:

DTLS配置

ThingsBoard平台支持使用DTLS的安全连接。DTLS(数据报传输层安全协议)基于传输层安全(TLS)协议,构建在用户数据报协议(UDP)之上。 ThingsBoard允许在LwM2M传输连接中为设备使用DTLS。

关于LWM2M DTLS安全的详细信息请参见此处

ThingsBoard为LwM2M DTLS提供三种认证方法:使用预共享密钥(PSK)、使用原始公钥(RPK)和使用X.509证书。

要使用DTLS,终端用户设备需要使用安全端口5686连接ThingsBoard服务器。

演示目的,我们将使用ThingsBoard LwM2M演示客户端,下载和配置请参见此处

1. 预共享密钥模式(PSK)

预共享密钥配置文件为将DTLS集成到LwM2M提供了最节省资源的解决方案,因为[RFC7925]中推荐的预共享密码套件在设备上需要最少的闪存空间和RAM。

对称加密算法仅需最小的计算开销。交换消息的大小也保持在最小。但也有缺点:对称密钥需要预先配置到两个通信端点。

只需三个字符串即可在设备配置文件中配置连接:

  • Endpoint client name:用于标识设备,可以是任意文本字符串。
  • Client identity(PSK identity)key:任意文本字符串。
  • PSK key(安全密钥):应为HexDec格式的随机序列,长度32、64或128个字符。

使用ThingsBoard LwM2M演示客户端的示例:

1
2
3
Endpoint client name= "MyClientPsk";
Client identity (PSK identity) = "myIdentity";
Client key (PSK key or PSK security key) = "01020304050607080A0B0C0D0F010203";

使用PSK模式启动ThingsBoard LwM2M演示客户端的示例命令:

1
java -jar thingsboard-lwm2m-demo-client-{version}.jar -u coaps://localhost -n MyClientPsk --psk-identity myIdentity --psk-key 01020304050607080A0B0C0D0F010203 

or

1
java -jar thingsboard-lwm2m-demo-client-4.1.0.jar -u coaps://localhost -n MyClientPsk --psk-identity myIdentity --psk-key 01020304050607080A0B0C0D0F010203

or

1
docker run --rm -it thingsboard/tb-lwm2m-demo-client:latest -u coaps://localhost -n 	MyClientPsk -i myIdentity -p 01020304050607080A0B0C0D0F010203

ThingsBoard演示客户端交互控制台:

1
2
3
4
DefaultRegistrationEngine 2021-09-30 19:09:52,789 [INFO] Trying to register to coaps://192.168.1.81:5686 ...
thingsboard-demo-client 2021-09-30 19:09:52,830 [INFO] DTLS Full Handshake initiated by client : STARTED ...
thingsboard-demo-client 2021-09-30 19:09:52,949 [INFO] DTLS Full Handshake initiated by client : SUCCEED
DefaultRegistrationEngine 2021-09-30 19:09:52,990 [INFO] Registered with location '/rd/vXMGfVFgQi'.

2. 原始公钥(RPK)模式

原始公钥配置提供了介于预共享密钥和证书模式之间的功能,结合了两者的优势。使用非对称加密提供了更高的安全性,同时避免了证书和公钥基础设施的开销。

要配置连接,需要执行以下步骤:

  • 生成客户端密钥,并将客户端公钥复制粘贴到ThingsBoard平台的设备凭据 - Client Key字段。
  • 生成服务器密钥并将其添加到服务器密钥存储文件lwm2mserver.jks中,然后将文件复制回服务器安装目录。
  • 配置客户端连接。

我们将使用OpenSSL工具,并遵循Leshan的指南:请参考此处

注意:此步骤需要已安装Java的Linux操作系统。

  1. 创建一个单独的文件夹用于保存所有生成的密钥。
  2. ThingsBoard将服务器密钥保存在密钥存储文件”lwm2mserver.jks”中,请找到并将此文件复制到我们的文件夹。 默认密钥文件位置为:
1
/common/transport/lwm2m/src/main/resources/credentials/lwm2mserver.jks
  1. 我们将使用以下脚本生成所有必要的密钥。只需用任意文本编辑器创建一个文本文件,将脚本复制到其中,以*.sh扩展名保存,例如’generate-rpk.sh’

generate-rpk.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/bash
#
# Copyright © 2016-2021 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# RPK. Generation of the keys.
echo "====START RPK ========"
echo "Generating client keys..."

# Create EC key pair (private and public) using default openssl pem encoding:
openssl ecparam -out keysClient.pem -name prime256v1 -genkey

# Convert Client Private Key to PKCS#8 format (DER encoding):
openssl pkcs8 -topk8 -inform PEM -outform DER -in keysClient.pem -out cprik.der -nocrypt

# Output Client Public Key portion in SubjectPublicKeyInfo format (DER encoding):
openssl ec -in keysClient.pem -pubout -outform DER -out cpubk.der

echo "Client public key in base64 format. Copy this key to the Thingsboard - Client Key field in Device Credentials"
base64 cpubk.der

# get server keys

# Importing keystore lwm2mserver.jks alias="server" to scertServer.p12
keytool -importkeystore -srckeystore lwm2mserver.jks -alias server -destkeystore scertServer.p12 -deststoretype PKCS12

# Importing keystore lwm2mserver.jks to scert.p12...
# Enter destination keystore password:  server_ks_password
# Re-enter new password: server_ks_password
# Enter source keystore password:  server_ks_password

# Generating scertServer.pem:
openssl pkcs12 -in scertServer.p12  -nodes -nocerts -out scertServer.pem
echo Enter Import Password: server_ks_password

# Server public key in base64 format  (spubk.pem):
openssl ec -in scertServer.pem -pubout -outform DER -out spubk.der

请注意,脚本使用”lwm2mserver.jks”文件的默认密码。如果您要使用其他密码,请同时在“thingsboard.yml”配置文件中更新:

1
2
3
4
5
6
7
8
9
10
11
...
lwm2m:
    ...
    server:
      ...
      security:
        ...
        key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}"
        key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}"
        ...

  1. 要运行脚本,使用以下命令:
1
2
chmod +x generate-rpk.sh
sudo ./generate-rpk.sh

此脚本将运行keytool和ssh实用程序。它将生成以下输出文件:

  • lwm2mserver.jks - 服务器密钥存储文件。
  • keysClient.pem - 客户端公钥/私钥对
  • cprik.der - DER格式的客户端私钥(Base64加密)
  • cpubk.der - DER格式的客户端公钥
  • spubk.der - DER格式的服务器公钥
  • scertServer.pem - 无加密的服务器公钥
  1. 在ThingsBoard平台上配置设备:
  • Endpoint name:输入用于标识客户端设备的唯一文本字符串
  • Client key: 在终端中,使用Base64命令打开cprik.der文件,并将代码复制到Client key字段:
1
2
3
4
5
#command:
$ Base64 cpubk.der

#Output:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdvBZZ2vQRK9wgDhctj6B1c7bxR3Z0wYg1+YdoYFnVUKWb+rIfTTyYK9tmQJx5Vlb5fxdLnVv1RJOPiwsLIQbAA==
  1. 使用RPK模式启动ThingsBoard LwM2M演示客户端的示例命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#command:
$ java -jar thingsboard-lwm2m-demo-client-{version}.jar -u coaps://localhost -n MyClientRpk -cpubk ./clietPubK.der -cprik ./clientKey.der -spubk ./serverPubK.der

#Output:
ThingsBoard LwM2M演示客户端交互控制台:
 
Commands:
  help	Displays help information about the specified command
  create  Enable a new Object
  delete  Disable a new object
  update  Trigger a registration update.
  send	Send data to server
  move	Simulate client mouvement.
 
Press Ctl-C to exit.

ThingsBoard Demo Client 2021-10-27 10:37:21,196 [INFO] Starting Leshan client ...
CaliforniumEndpointsManager 2021-10-27 10:37:21,470 [INFO] New endpoint created for server coaps://18.184.200.162:5686 at coaps://[0:0:0:0:0:0:0:0]:10004
ThingsBoard Demo Client 2021-10-27 10:37:21,472 [INFO] Leshan client[endpoint:leshan-rpkz] started.
DefaultRegistrationEngine 2021-10-27 10:37:21,474 [INFO] Trying to register to coaps://18.184.200.162:5686 ...
ThingsBoard Demo Client 2021-10-27 10:37:21,549 [INFO] DTLS Full Handshake initiated by client : STARTED ...
ThingsBoard Demo Client 2021-10-27 10:37:21,729 [INFO] DTLS Full Handshake initiated by client : SUCCEED
DefaultRegistrationEngine 2021-10-27 10:37:21,771 [INFO] Registered with location '/rd/yyIIQFyg6H'.
DefaultRegistrationEngine 2021-10-27 10:37:21,773 [INFO] Next registration update to coaps://18.184.200.162:5686 in 53s...