产品定价 立即试用
社区版
入门 文档 指南 安装 架构 API 常见问题

保存 attributes

将入站消息数据存储为消息originator的attribute数据。

前置条件

该节点接受类型为 POST_ATTRIBUTES_REQUEST 的消息,并要求消息数据为JSON对象,每个属性名表示attribute的key,对应的值表示attribute的value。例如:

1
2
3
4
{
  "firmware_version": "1.0.1",
  "serial_number": "SN-001"
}

配置

处理设置

save attributes节点可执行三种操作,每种操作由可配置的处理策略控制:

  • Attributes:将attribute数据保存到数据库。
  • WebSockets:向WebSocket订阅通知attribute数据更新。
  • Calculated fields:向calculated fields通知attribute数据更新。

对于每种操作,您可选择下列 processing strategies(处理策略):

  • 每条消息均执行:对每条入站消息执行操作。
  • 去重:将来自同一来源实体的消息按时间间隔分组,仅在每个间隔内的第一条消息上执行操作。 间隔时长由去重间隔设置指定。 系统使用以下公式计算消息所属的去重间隔编号:
    1
    
    long intervalNumber = ts / deduplicationIntervalMillis;
    

    其中:

    • ts 为用于去重的时间戳(毫秒)。
    • deduplicationIntervalMillis 为配置的去重间隔(自动转换为毫秒)。
    • intervalNumber 决定消息所属的逻辑时间桶。

    时间戳 ts 按以下优先级确定:

    1. 若消息元数据中包含 ts 属性(UNIX毫秒),则使用该值。
    2. 否则,使用消息创建时间。

    所有时间戳均为UNIX毫秒(UTC)。

    示例

    60秒去重间隔下:

    • 设备在10:00:15、10:00:45、10:01:10发送消息
    • 前两条消息(10:00:15和10:00:45)落在同一间隔内,仅处理10:00:15的消息
    • 10:01:10的消息落在下一间隔,会被处理
  • 跳过:永不执行操作。
文档信息图标

说明:Processing strategies自TB 4.0起可用。

processing strategies可通过 BasicAdvanced processing settings 设置。

image

  • Basic processing settings — 为所有操作提供预设策略:
    • On every message:对所有操作应用 On every message 策略。所有消息都会执行所有操作。
    • Deduplicate:对所有操作应用 Deduplicate 策略(指定间隔)。
    • WebSockets only:除WebSocket通知外,所有操作应用 Skip 策略,WebSocket通知使用 On every message 策略。 实际效果为:不写入数据库;数据仅通过WebSocket订阅实时可用。

image

  • Advanced processing settings — 允许为每个操作独立配置处理策略。

image

在高级模式下配置处理策略时,某些组合可能导致意外行为。请考虑以下场景:

  • 跳过数据库存储

    选择禁用一个或多个持久化操作(例如跳过Time series或Latest values的数据库存储,同时保持WebSocket更新启用)会带来仅部分数据可用的风险:

    • 若消息仅用于实时通知(WebSocket)且未存入数据库,历史查询可能与仪表板上的数据不一致。
    • 当Time series和Latest values的处理策略不同步时,遥测数据可能只存入一个表(如Time series),而另一个表(如Latest values)中缺失相同数据。
  • 禁用WebSocket (WS) 更新

    若禁用WS更新,对时序数据的任何更改不会推送到仪表板(或其他WS订阅)。 这意味着即使数据库已更新,仪表板可能不会显示更新后的数据,直到重新加载浏览器页面。

  • 跳过字段计算重新计算

    若遥测数据保存到数据库但绕过了字段计算重新计算,汇总值可能不会更新以反映最新数据。 反之,若字段计算用新数据重新计算但对应的遥测值未持久化到数据库,字段计算的值可能包含未存储的数据。

  • 跨操作的不同去重间隔

    为各操作配置不同的去重间隔时,同一传入消息可能对不同操作进行不同处理。 例如,消息可能立即存入Time series表(若设为 On every message),而因Latest values表的去重间隔未到而不存入。 若WebSocket更新配置了不同间隔,仪表板显示的更新可能与数据库中的存储内容不一致。

  • 去重缓存清理

    去重机制使用内存缓存按间隔跟踪已处理消息。此缓存最多保留100个间隔,最长2天,但因其使用软引用,条目可能随时被清理。 因此,即使负载较轻,去重也不保证。例如,去重间隔为一天且每小时收到一条消息,若在两次到达之间缓存被清理,每条消息仍可能被处理。 去重应视为性能优化,而非每间隔单次处理的严格保证。

  • 整条消息去重

    需注意的是,去重应用于整条传入消息,而非单个时序key。 例如,若第一条消息包含key A并已处理,第二条消息(在去重间隔内收到)包含key B,第二条消息将被跳过——尽管包含新key。 要安全利用去重,请确保消息保持一致结构,使所有必需key出现在同一条消息中,避免意外数据丢失。

鉴于上述场景,将每个持久化操作独立配置(包括设置不同的去重间隔)的能力应视为性能优化,而非严格的处理保证。

Scope

image

节点支持三种属性范围:客户端属性共享属性服务端属性。可在节点配置中设置默认范围,也可通过在消息metadata中指定有效的 scope 属性进行覆盖。

支持的 scope 值:

  • CLIENT_SCOPE 对应 客户端属性
  • SHARED_SCOPE 对应 共享属性
  • SERVER_SCOPE 对应 服务端属性

若消息metadata包含无效的 scope 值(如 INVALID_SCOPE),消息处理将失败。

Advanced settings

image

  • Save attributes only if the value changes — 若启用,节点将入站属性与当前存储值比较,仅保存新增、值已变更或数据类型不同的属性。
文档信息图标

注意:避免对同一属性进行并发写入,因为变更检测非事务性,此类情况下可能产生意外结果。

文档信息图标

注意:若因值未变更而跳过属性保存,属性的最后更新时间戳将不会更新。

  • Send attributes updated notification — 若启用,节点对 SHARED_SCOPESERVER_SCOPE 属性向来源实体的默认规则链发送 Attributes Updated 事件。
  • Force notification to the device — 若启用,节点始终向有活跃订阅的设备发送属性更新通知。若禁用,设备通知由消息metadata中的 notifyDevice 属性控制(缺失时默认为 true)。

JSON Schema

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "TbMsgAttributesNodeConfiguration",
  "type": "object",
  "properties": {
    "scope": {
      "type": "string",
      "description": "Scope for attribute processing (e.g., SERVER_SCOPE, CLIENT_SCOPE, SHARED_SCOPE)"
    },
    "notifyDevice": {
      "type": "boolean",
      "description": "Whether to notify the device about attribute changes"
    },
    "sendAttributesUpdatedNotification": {
      "type": "boolean",
      "description": "Whether to send 'ATTRIBUTES_UPDATED' event to the originator's default rule chain"
    },
    "updateAttributesOnlyOnValueChange": {
      "type": "boolean",
      "description": "Whether to update attributes only when values change"
    },
    "processingSettings": {
      "$ref": "#/$defs/ProcessingSettings"
    }
  },
  "required": [
    "scope",
    "notifyDevice",
    "sendAttributesUpdatedNotification",
    "updateAttributesOnlyOnValueChange",
    "processingSettings"
  ],
  "additionalProperties": false,
  "$defs": {
    "ProcessingSettings": {
      "description": "Polymorphic processing settings for attributes data",
      "discriminator": {
        "propertyName": "type"
      },
      "oneOf": [
        {
          "$ref": "#/$defs/OnEveryMessage"
        },
        {
          "$ref": "#/$defs/WebSocketsOnly"
        },
        {
          "$ref": "#/$defs/Deduplicate"
        },
        {
          "$ref": "#/$defs/Advanced"
        }
      ]
    },
    "OnEveryMessage": {
      "type": "object",
      "properties": {
        "type": {
          "const": "ON_EVERY_MESSAGE"
        }
      },
      "required": [
        "type"
      ],
      "additionalProperties": false
    },
    "WebSocketsOnly": {
      "type": "object",
      "properties": {
        "type": {
          "const": "WEBSOCKETS_ONLY"
        }
      },
      "required": [
        "type"
      ],
      "additionalProperties": false
    },
    "Deduplicate": {
      "type": "object",
      "properties": {
        "type": {
          "const": "DEDUPLICATE"
        },
        "deduplicationIntervalSecs": {
          "type": "integer",
          "minimum": 1,
          "maximum": 86400,
          "description": "Deduplication interval in seconds (1 second to 1 day)"
        }
      },
      "required": [
        "type",
        "deduplicationIntervalSecs"
      ],
      "additionalProperties": false
    },
    "Advanced": {
      "type": "object",
      "properties": {
        "type": {
          "const": "ADVANCED"
        },
        "attributes": {
          "$ref": "#/$defs/ProcessingStrategy"
        },
        "webSockets": {
          "$ref": "#/$defs/ProcessingStrategy"
        },
        "calculatedFields": {
          "$ref": "#/$defs/ProcessingStrategy"
        }
      },
      "required": [
        "type",
        "attributes",
        "webSockets",
        "calculatedFields"
      ],
      "additionalProperties": false
    },
    "ProcessingStrategy": {
      "description": "Polymorphic processing strategy",
      "discriminator": {
        "propertyName": "type"
      },
      "oneOf": [
        {
          "$ref": "#/$defs/OnEveryMessageStrategy"
        },
        {
          "$ref": "#/$defs/DeduplicateStrategy"
        },
        {
          "$ref": "#/$defs/SkipStrategy"
        }
      ]
    },
    "OnEveryMessageStrategy": {
      "type": "object",
      "properties": {
        "type": {
          "const": "ON_EVERY_MESSAGE"
        }
      },
      "required": [
        "type"
      ],
      "additionalProperties": false
    },
    "DeduplicateStrategy": {
      "type": "object",
      "properties": {
        "type": {
          "const": "DEDUPLICATE"
        },
        "deduplicationIntervalSecs": {
          "type": "integer",
          "minimum": 1,
          "maximum": 86400,
          "description": "Deduplication interval in seconds (1 second to 1 day)"
        }
      },
      "required": [
        "type",
        "deduplicationIntervalSecs"
      ],
      "additionalProperties": false
    },
    "SkipStrategy": {
      "type": "object",
      "properties": {
        "type": {
          "const": "SKIP"
        }
      },
      "required": [
        "type"
      ],
      "additionalProperties": false
    }
  }
}

消息处理算法

  1. 节点验证消息类型是否为 POST_ATTRIBUTES_REQUEST。若不是,处理失败并将消息路由到 Failure 连接。

  2. 节点解析消息数据以提取属性键值对。若数据为空,处理结束并将消息路由到 Success 连接。

  3. 节点按下列优先级确定属性作用域:
    • 消息metadata中的 scope 属性值(若存在且有效:CLIENT_SCOPESHARED_SCOPESERVER_SCOPE
    • 若metadata中提供无效的scope(如 INVALID_SCOPE),处理失败并将消息路由到 Failure 连接
    • 若metadata未提供scope,则使用节点配置的默认scope
  4. 若启用 Save attributes only if the value changes(仅当值变更时保存属性),节点执行变更检测:
    • 从数据库获取所有入站属性key的当前值
    • 将每个入站属性与其当前存储值比较
    • 仅保留下列属性:
      • 新增(当前未存储)
      • 与当前存储值不同
      • 与当前存储数据类型不同
    • 若未检测到变更,处理结束并将消息路由到 Success 连接
  5. 节点按下列优先级确定设备通知设置:
    • 若配置中启用 Force notification to the device,始终发送通知
    • 否则检查消息metadata中的 notifyDevice 属性:
      • 若缺失或为空字符串:默认发送通知
      • 若存在:仅当值为 true(不区分大小写)时发送通知
  6. 按配置的处理策略将属性保存到数据库。保存完成时:
    • 成功:处理成功,消息路由到 Success 连接
    • 失败:处理失败,消息路由到 Failure 连接
    • 若启用 Send attributes updated notification,会向originator的默认规则链发布 ATTRIBUTES_UPDATED 事件(针对 SHARED_SCOPESERVER_SCOPE 属性)

输出连接

  • Success
    • 入站消息被成功处理时
  • Failure
    • 入站消息类型不是 POST_ATTRIBUTES_REQUEST
    • 入站消息payload无法解析为属性键值对时
    • 入站消息metadata包含非空 scope 属性且其值不是有效作用域(即 CLIENT_SCOPESHARED_SCOPESERVER_SCOPE)时
    • 消息处理过程中发生意外错误时

示例

示例1 — On every message策略

入站消息

类型:POST_ATTRIBUTES_REQUEST

Originator:DEVICE

数据:

1
2
3
4
5
{
  "firmware_version": "1.0.1",
  "serial_number": "SN-001",
  "last_maintenance": "2025-01-15"
}

节点配置

1
2
3
4
5
6
7
8
9
{
  "scope": "SERVER_SCOPE",
  "notifyDevice": false,
  "sendAttributesUpdatedNotification": true,
  "updateAttributesOnlyOnValueChange": false,
  "processingSettings": {
    "type": "ON_EVERY_MESSAGE"
  }
}

出站消息

出站消息与入站消息相同。经 Success 连接路由。

结果

保存了三个服务器属性:

  • firmware_version: “1.0.1”
  • serial_number: “SN-001”
  • last_maintenance: “2025-01-15”

WebSocket订阅和calculated fields收到通知。由于scope为 SERVER_SCOPE,会向originator的默认规则链发送 ATTRIBUTES_UPDATED 事件。

示例2 — 使用Deduplicate策略的属性

入站消息

类型:POST_ATTRIBUTES_REQUEST

Originator:DEVICE

数据:

1
2
3
4
5
{
  "battery_level": 85,
  "signal_strength": -65,
  "location": "Building A, Floor 2"
}

节点配置

1
2
3
4
5
6
7
8
9
10
{
  "scope": "CLIENT_SCOPE",
  "notifyDevice": true,
  "sendAttributesUpdatedNotification": false,
  "updateAttributesOnlyOnValueChange": false,
  "processingSettings": {
    "type": "DEDUPLICATE",
    "deduplicationIntervalSecs": 120
  }
}

系统状态

该设备在当前120秒间隔内的消息已处理过。

出站消息

出站消息与入站消息相同。经 Success 连接路由。

结果

由于启用了120秒去重间隔且该消息已处理过,属性不会保存到数据库。也不会触发WebSocket通知和calculated fields。

示例3 — 混合策略的高级处理

入站消息

类型:POST_ATTRIBUTES_REQUEST

Originator:DEVICE

数据:

1
2
3
4
5
{
  "cpu_usage": 45.7,
  "memory_usage": 78.2,
  "disk_usage": 62.5
}

节点配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "scope": "SHARED_SCOPE",
  "notifyDevice": false,
  "sendAttributesUpdatedNotification": true,
  "updateAttributesOnlyOnValueChange": false,
  "processingSettings": {
    "type": "ADVANCED",
    "attributes": {
      "type": "DEDUPLICATE",
      "deduplicationIntervalSecs": 300
    },
    "webSockets": {
      "type": "ON_EVERY_MESSAGE"
    },
    "calculatedFields": {
      "type": "SKIP"
    }
  }
}

系统状态

该设备在当前去重间隔内的消息已处理过。

出站消息

出站消息与入站消息相同。经 Success 连接路由。

结果

  • 由于去重,属性未保存到数据库
  • WebSocket订阅已收到通知
  • Calculated fields未触发(SKIP策略)
  • 由于属性未保存,不发送 ATTRIBUTES_UPDATED 事件

示例4 — 仅当值变更时保存属性

入站消息

类型:POST_ATTRIBUTES_REQUEST

Originator:DEVICE

数据:

1
2
3
4
5
{
  "temperature_threshold": 25.0,
  "humidity_threshold": 70.0,
  "maintenance_scheduled": true
}

节点配置

1
2
3
4
5
6
7
8
9
{
  "scope": "SERVER_SCOPE",
  "notifyDevice": false,
  "sendAttributesUpdatedNotification": true,
  "updateAttributesOnlyOnValueChange": true,
  "processingSettings": {
    "type": "ON_EVERY_MESSAGE"
  }
}

系统状态

当前已存储属性:

  • temperature_threshold: 25.0(值相同)
  • humidity_threshold: 65.0(值不同)
  • maintenance_scheduled: 未存储(新属性)

出站消息

出站消息与入站消息相同。经 Success 连接路由。

结果

因变更检测仅保存两个属性:

  • humidity_threshold: 70.0(值从65.0变为70.0)
  • maintenance_scheduled: true(新属性)

temperature_threshold 未保存,因其值未变更。WebSocket订阅和calculated fields仅对被变更属性收到通知。

示例5 — 从消息metadata覆盖scope

入站消息

类型:POST_ATTRIBUTES_REQUEST

Originator:DEVICE

数据:

1
2
3
{
  "power_setting": "low"
}

Metadata:

1
2
3
{
  "scope": "CLIENT_SCOPE"
}

节点配置

1
2
3
4
5
6
7
8
9
{
  "scope": "SERVER_SCOPE",
  "notifyDevice": false,
  "sendAttributesUpdatedNotification": true,
  "updateAttributesOnlyOnValueChange": false,
  "processingSettings": {
    "type": "ON_EVERY_MESSAGE"
  }
}

出站消息

出站消息与入站消息相同。经 Success 连接路由。

结果

保存了一个客户端属性(使用metadata中的scope):

  • power_setting: “low”

由于客户端属性不触发该通知,不会发送 ATTRIBUTES_UPDATED 事件。