iot_race 现场手册
水循环竞赛 · 规则链与 TBEL
53 节点

01出发前只看这一页

十条结论,读完你就知道现场怎么下手。

核心一句:现场不需要你写 TBEL

规则链 53 个节点已经跑通,状态机、看门狗、四维告警都在线上工作。你的任务是用它、调它,不是重写它。

  1. 改阈值、目标温度、输送量 → 改属性,和 TBEL 毫无关系,下一帧即生效。
  2. 改节点参数(延迟秒数、RPC 超时、读哪些属性)→ 点表单,拖拽和填数字而已。
  3. 只有"判断条件"才需要脚本,而脚本往往只是 1~5 行 if。
  4. 你有 C 基础就够了。TBEL 是 Java 风格,for/while/switch/函数/复合赋值都能用。只需记住 4 处不一样。
  5. 危险的是它比 C 宽松。写错常常不报错,只静默返回 false。所以改完必须实测,别靠肉眼。
  6. 本地自带 TBEL 引擎,不用外网。try-tbel.cjs 能在不碰设备、不改平台的前提下试跑脚本。
  7. 三个数字是命门:遥测 5 秒新鲜度、每次启动换新 runId、STOPPING ≠ 已停机。
  8. 最稳的退路是手动模式。面板上的泵/加热器开关不依赖状态机,任何时候能用。
  9. 数据和告警与自动控制独立。自动控制关掉,遥测曲线和告警照常工作。
  10. 没把握就别让自动任务控制加热器。过温干烧的硬保护应该在 STM32 本地。

本页所有 TBEL 结论都在本机引擎上实测过(34 条),点「TBEL」标签可逐条查看。命令、参数、故障原因都来自实际代码与验证记录,不是推测。

↑ 回目录

02命令卡

C:\thingsboard-4.3.1.2 下执行。前 5 条会真动设备,其余只读,随便跑。

用途命令
看模式/状态/累计量node competition/iot_race/automation/control.cjs status
只存参数,不启动设备node competition/iot_race/automation/control.cjs configure --target 38 --hysteresis 1.5
启动自动温控(先手动开泵)node competition/iot_race/automation/control.cjs temperature --sensor temp2 --target 35 --hysteresis 1
启动定量输送(先关泵和加热器)node competition/iot_race/automation/control.cjs dose --liters 1 --duty 50
请求停止node competition/iot_race/automation/control.cjs stop
列出可试场景node competition/iot_race/automation/try-tbel.cjs --list
体检规则链所有脚本语法node competition/iot_race/automation/try-tbel.cjs --check
实测 34 条 TBEL 规则node competition/iot_race/automation/try-tbel.cjs --selftest
试跑一段脚本node competition/iot_race/automation/try-tbel.cjs "msg.x=1; return {msg:msg,metadata:metadata,msgType:msgType};"
只读核对线上与文件是否一致node competition/iot_race/automation/check-live.cjs
状态机 25 项验证,不碰设备node competition/iot_race/automation/verify.cjs

现场操作顺序:status 看状态 → 确认设备当前输出 → 再启动或停止。启动失败时不要反复点,先读面板或 status 给的 reason。

可试的输入场景

配合 try-tbel.cjs -f 你的片段 --scenario 名字 使用,用来观察脚本在某一种输入下返回什么。

--scenario normal
普通遥测、自动模式关闭,用于看“什么都不做”的那条路
--scenario temperature
温控请求启动:温度低于目标−回差,应产生开加热器指令
--scenario temperature-arrive
温控运行中且已到目标:应产生关加热器指令
--scenario temperature-noflow
泵没开或流量不足:应报 NO_CIRCULATION
--scenario dosing
定量输送启动:泵与加热器都已关,应产生 setPump 指令
--scenario dosing-arrive
定量输送已到量:应报 TARGET_REACHED 并进入 STOPPING
--scenario alarm
温度越界 + 传感器缺失,用于测试告警分支的判断脚本
--scenario watchdog
看门狗消息:设备超时未上报,应报 TELEMETRY_TIMEOUT
--scenario attribute-command
面板写入扩展命令(controlCommand),用于测试扩展命令入口的判断
--scenario manual-rpc
面板手动开泵的 RPC(Pump),用于测试手动接管判断
↑ 回目录

03TBEL:会这 10 条就够

每一条都有"错→对"对照和出错时会看到的报错。全部实测。

01每个脚本最后必须 return,返回三件套
msg.tempAvg = (msg.temp1 + msg.temp2) / 2;
msg.tempAvg = (msg.temp1 + msg.temp2) / 2;
return {msg:msg, metadata:metadata, msgType:msgType};

转换节点只写 msg.x=1 不 return,引擎直接拒绝。这是头号坑。

出错时会看到:Wrong result type: Double

02服务端属性在 metadata 里叫 ss_<属性名>
var limit = metadata.ss_tempHighLimit;

节点读属性时自动加 ss_ 前缀,漏了会拿到 null。

出错时会看到:null pointer

03属性读出来是字符串,比大小前必须 parseDouble
if (metadata.ss_tempHighLimit > 40) { }
var limit = parseDouble(metadata.ss_tempHighLimit);
if (limit > 40) { }

属性的值都是字符串,"45" 跟数字比会得出字符串比较的怪结果。

出错时会看到:不报错,但判断结果错——最危险

04判断“有没有值”用 == null,判断布尔用 == true
if (msg.pump) { }
if (msg.pump == true) { }
if (msg.temp1 == null) { }

泵状态可能是 1 / true / "true",统一用 == true,与现有代码一致。

出错时会看到:不报错,但漏判

05数字是 Number,字符串是 String,别混着用
if (msg.temp1 == "25.4") { }
var t = msg.temp1;
if (t != null && t > 25) { }

设备发字符串数字会被当成“缺失”,还会触发 SENSOR_DATA_MISSING。

出错时会看到:不报错,但静默走错分支

06数组用 add / size,不是 push / length
var out = [];
out.push('A');
if (out.length > 0) { }
var out = [];
out.add('A');
if (out.size() > 0) { }

TBEL 是 Java 风格,写 push/length 直接报错。

出错时会看到:编译报错

07JSON 字符串用 JSON.parse 变对象再取字段
var n = metadata.autoPlan.commands.size();
var p = JSON.parse(metadata.autoPlan);
var n = p.commands.size();

显式 parse 更稳妥,意图也更清楚。

出错时会看到:null pointer 或取不到值

08过滤器节点只 return true 或 false
return {msg:msg, metadata:metadata, msgType:msgType};
return msg.pump == true;

过滤器只有两个出口,返回对象会被拒绝。

出错时会看到:Wrong result type

09分流节点要 return 一组出口名称
return msg.temperatureAlarm == true;
var out = [];
if (msg.temperatureAlarm == true) out.add('TEMP_ALARM');
return out;

出口名称必须与连线的关系类型逐字一致,否则消息走不出去,看起来像“没反应”。

出错时会看到:消息无输出

10字符串拼接和包含判断
if (metadata.extensionRoute == 'FEATURE' + id) { }
var route = 'FEATURE_' + msg.featureId;
if (route.contains('FEATURE_')) { }

+ 拼字符串,contains 判包含。布尔一律 == true,别依赖隐式转换。

出错时会看到:不报错但结果不符预期

↑ 回目录

04C 基础对照表

你会的 C 已经覆盖 TBEL 九成语法。这里只讲一样的、不一样的、和会坑你的。

跟 C 一样,直接用

  • var i = 0; / Integer n = 5; / Long l = 10L; —— 变量声明
  • for (var i=0; i<5; i++) { }while (i<3) { } —— 循环
  • if / else if / elsebreak —— 分支与跳出循环
  • switch (m) { case 'A': ... break; } —— 支持,但 case 和它的语句要分行,同行会报 unknown class or illegal statement
  • += -= *= /=++--% —— 运算符
  • && || !、三元 a ? b : c
  • function f(v) { ... } —— 可以定义在文件任意位置,也能互相调用(现有代码顶部那些 number()、valid() 就是)
  • arr[i] 下标取值;长度用 arr.size()arr.length

跟 C 不一样,必须记住四条

  1. continue 不支持。unresolvable property or identifier: continue。把循环体反过来用 if 包住即可。
  2. 顶层不能写裸大括号代码块。放进函数里,或用 if 块。
  3. 数组用 add() 不是 push();判断包含用 contains()字符串长度 .length(),子串判断 .contains(),没有 strstr
  4. 没有指针、没有 typeof、没有显式类型转换语法。转字符串 .toString(),转数字 parseDouble() / parseInt()

最关键的一节:TBEL 比 C 宽松,很多错不报错,只静默算错。C 里指针写错立刻段错误;TBEL 里写错会安静返回 false,要等设备不动作才发现。

按 C 习惯写TBEL 实际正确做法
if (msg.temp1 > 30)缺失时返回 false先判 != null,别指望"缺失会崩"
if (t > 30 && t != null)返回 false,不报错保护写反也不报错。必须先判 null:t != null && t > 30
if (x == '1') / if (pump == 1)'1'==1、true==1 都为真布尔一律 == true;数字先 parseDouble
parseDouble(metadata.ss_missing)报错 Value: "null" is not numeric属性可能是空字符串,转换前判空
5 / 0得到 Infinity不报错。除法前确认分母非 0
5 % 0报错 / by zero取模前确认非 0

实测清单(34 条)

跟 C 一样,已验证可用10 条

C 风格 for 循环可用

var s=0; for(var i=0;i<5;i++){ s=s+i; } msg.r=s; return {msg:msg,metadata:metadata,msgType:msgType};

while 循环可用

var i=0; while(i<3){ i++; } msg.r=i; return {msg:msg,metadata:metadata,msgType:msgType};

复合赋值 += -= *= /= 可用

var x=10; x+=5; x-=2; x*=2; x/=4; msg.r=x; return {msg:msg,metadata:metadata,msgType:msgType};

自增 ++ 自减 -- 可用

var i=0; i++; i++; i--; msg.r=i; return {msg:msg,metadata:metadata,msgType:msgType};

取余 % 可用(现用于判断整数占空比)

msg.r = (50.5 % 1 != 0); return {msg:msg,metadata:metadata,msgType:msgType};

函数可以在文件任意位置定义

msg.r=twice(msg.n);
function twice(v){ return v*2; }
return {msg:msg,metadata:metadata,msgType:msgType};

函数里可以写 if 和 return

function pick(t){ if(t==null){ return -1; } if(t>30){ return 1; } return 0; }
msg.r=pick(msg.n); return {msg:msg,metadata:metadata,msgType:msgType};

switch/case 可用(case 与语句必须换行)

var m='B'; var r=0;
switch(m){
  case 'A':
    r=1;
    break;
  case 'B':
    r=2;
    break;
}
msg.r=r; return {msg:msg,metadata:metadata,msgType:msgType};

for 里 break 可用

var s=0;
for(var i=0;i<9;i++){
  if(i==4){ break; }
  s=s+i;
}
msg.r=s; return {msg:msg,metadata:metadata,msgType:msgType};

数组有 .size() 和 .length 两种写法

var a=[1,2,3]; msg.r = a.size() + a.length; return {msg:msg,metadata:metadata,msgType:msgType};
跟 C 不一样,已验证9 条

continue 不支持(用 if 包住循环体替代)

var s=0;
for(var i=0;i<5;i++){
  if(i==2){ continue; }
  s=s+i;
}

顶层裸大括号代码块不合法

var a=1;
{
  msg.r=a+1;
}

空值与数字比较不报错,静默返回 false

msg.r = (msg.missing > 30);

短路保护写反了不会报错,只会算错

var t=msg.missing; msg.r = (t > 30 && t != null);

字符串与数字宽松相等(1=="1" 为真)

msg.r = ('1' == 1);

布尔与数字宽松相等(true==1 为真)

msg.r = (true == 1);

parseDouble(null) 会报错,要先判空

msg.r = parseDouble(metadata.ss_missing);

除以 0 得到 Infinity,不是报错

msg.r = 5 / 0;

对 0 取模会报错(/ by zero)

msg.r = 5 % 0;
↑ 回目录

05哪些能点、哪些要写码

直接回答"TB 不是可以可视化配置吗":结构和参数能点,判断条件是代码。

要做的事方式
新建/删除节点、拖线、改关系类型可视化
改节点参数(看门狗延迟、RPC 超时、读哪些属性、存到哪个作用域)可视化
告警等级、类型、是否传播可视化
告警阈值、目标温度、输送量、回差改属性
"温度低于目标减回差"、"泵开了但没流量"写码 · 几行 if
状态机(自动温控、定量输送的全部决策)写码 · 已写好

结论:现场九成需求用"可视化 + 改属性"就能解决。真正要动脚本的只有三种:加一个现在没有的判断条件(在现有脚本末尾加一行 if)、吸收设备新字段(其实不用改,遥测键会自动保存)、改自动控制行为(走文件重新生成)。

↑ 回目录

06怎么改:按需求查

每一条都标了"改哪里"。标"改属性"的完全不用碰规则链。

改属性改告警阈值

改哪里:设备服务端属性

  1. 打开设备 iot_race → 属性 → 服务端属性
  2. 改 tempLowLimit / tempHighLimit / maxTempDiff / minFlow / minPressure / maxPressure
  3. 下一帧遥测立即生效,不用部署

告警阈值 tempHighLimit 与自动控制的过温上限 hardMaxTemp 是两套独立参数。想让自动任务比告警更早收手,应保持 hardMaxTemp ≤ tempHighLimit。

改属性改目标温度 / 回差 / 测温通道

改哪里:autoConfig

  1. control.cjs configure --sensor temp1 --target 38 --hysteresis 1.5
  2. 或面板「自动温控」页签填表后点保存

任务启动时会锁定参数快照。改参数必须:stop → 等停机确认 → 用新 runId 重新启动。

改属性改输送量 / 占空比 / 流量上限

改哪里:autoConfig

  1. 先把泵和加热器都关掉并确认遥测
  2. control.cjs dose --liters 2 --duty 60 --maxFlow 20

定量输送启动前泵和加热器必须都是关闭的,这是状态机里的硬检查(START_REQUIRES_STOPPED)。

改属性校准流量系数 flowScale

改哪里:autoConfig.flowScale

  1. 做一次已知体积的输送,比如用量杯接 1 L
  2. 比较 autoRuntime.totalL 与实测体积
  3. 新 flowScale = 旧值 × (实测 ÷ 平台累计)

默认值 1 是未校准的。约 1 秒采样加网络延迟,不要承诺毫升级精度。

可视化改看门狗延迟

改哪里:「等待遥测超时检查」节点

  1. 规则链编辑器里双击该节点
  2. 改 periodInSeconds(当前 6)

这是纯表单参数,不用写脚本。但“多久算失联”的 5 秒判定在脚本里,两者要搭配合理。

界面改脚本新增一个告警维度

改哪里:统一键名节点 + 安全状态分流节点

  1. 在「统一键名并计算状态」脚本末尾加一行:msg.tempJumpAlarm = msg.tempDiff != null && msg.tempDiff > 3;
  2. 在「安全状态分流」脚本里追加出口:result.push(msg.tempJumpAlarm ? 'TEMPJUMP_ALARM' : 'TEMPJUMP_CLEAR');
  3. 新建一个创建告警节点和一个清除告警节点,告警类型字符串必须完全一致
  4. 从分流节点连出 TEMPJUMP_ALARM 和 TEMPJUMP_CLEAR 两条线
  5. 改完把规则链导出覆盖回 iot_race_rule_chain.json

阈值想可配的话,要在「读取安全阈值」节点的 serverAttributeNames 数组里补上新属性名,否则脚本读到的是空值。

可视化告警时发邮件

改哪里:告警节点后面

  1. 把一个发送邮件节点拖到「温度异常告警」的下游
  2. 连接类型选 Created
  3. 配置收件人和正文模板

要接在告警节点后面,不要接在分流节点后面——接错会在每次遥测(包括清除分支)都发一次通知。

改文件重建改停机重试间隔

改哪里:controller.tbel 里的 8000

  1. 改 controller.tbel 中停机段的 now - r.lastCommandAt >= 8000
  2. build.cjs → verify.cjs → sync-dashboard.cjs

自动控制的脚本必须在文件里改再重新生成。在平台上直接改会在下次同步时被覆盖。

改属性 / 可视化临时停用自动控制但保留告警

改哪里:autoConfig

  1. 方式一:control.cjs stop,自动模式变 OFF,告警完全不受影响
  2. 方式二:把 controlCapabilities 里对应功能的 enabled 改成 false(handled 保持 true)
  3. 方式三:删掉「保存遥测与派生状态」到「读取自动控制参数」的连线(排障用,记得改回来)

日常做法是方式一。方式三会留下一条难查的隐式差异。

发布流程(改脚本时)

node competition/iot_race/automation/build.cjs
node competition/iot_race/automation/verify.cjs
node competition/iot_race/automation/verify-dashboard.cjs
node competition/iot_race/automation/sync-dashboard.cjs

最后一步才真正写平台,且要求当前没有运行中的任务、mode=OFF,同步前会自动备份。

最高频的翻车方式:在平台上直接改自动控制的脚本,当时生效,但下次 sync-dashboard.cjs 会覆盖掉,看起来像"昨天还好好的,今天自己变了"。自动控制相关的脚本一律改文件 + 重新生成。

↑ 回目录

07故障速查

自动控制的故障几乎都写在 autoRuntime.reason 里。先跑 status 看一眼。

现象原因处理
一直 IDLE 不起来mode 还是 OFF / runId 与上次相同 / 遥测不新鲜 / 参数越界核对 autoConfig 的 mode 和 runId
FAULT: NO_CIRCULATION泵没开、流量低于 minFlow、流量键名不对先手动开泵并确认流量数字在动
FAULT: INVALID_TEMPERATURE温度读到 -127 或超出 -20~80查传感器接线,脚本没错
FAULT: TELEMETRY_GAP上报间隔接近或超过 5 秒加快上报;或改脚本里的 5 秒判定
FAULT: COMMAND_NOT_CONFIRMED设备没回 heater/pump 字段或回的是字符串让设备遥测带上布尔值
FAULT: START_REQUIRES_STOPPED定量启动时泵或加热器还开着先关掉并确认遥测
停在 STOPPING设备没回报执行器已关闭看遥测里 heater 是否 false(定量还要 pump false)
改参数没生效运行中锁定了启动时的参数快照stop → 等确认 → 新 runId 重启
面板点启动没反应遥测超 5 秒 / 泵未开(温控)/ 泵未关(定量)/ 有任务没结束面板会给拒绝原因
告警不清除创建与清除节点的告警类型拼写不一致逐字比对类型字符串
一直报传感器缺失设备发了字符串数字,如 "25.1"设备端改成 JSON 数字
输送量偏差大flowScale 没校准 / 单位不是 L/min用量杯标定
规则链改了没生效改的是平台脚本没同步文件 / 改完没部署check-live.cjs 核对一致性
扩展命令一直 REJECTED没有注册扩展处理器,设计如此不用处理

最好用的定位手段:规则链右上角打开调试(Debug),设备发一条数据,就能看到消息在每个节点的输入输出,包括 autoPlanautoWriteautoCommands 的实际值。用完关掉。

↑ 回目录

08自动化不行了怎么退

按顺序退,不要现场硬撑。

1
先停止任务,回到已知状态。
node competition/iot_race/automation/control.cjs stop
status 确认:STOPPING 不等于已停机,要看到 STOPPED / COMPLETED / FAULT。
2
停不下来就手动接管。面板上的泵/加热器开关走"手动接管"路径,会先把自动模式置 OFF 再执行你的指令,不依赖状态机。
3
退回上一个可用规则链。备份在 competition/iot_race/backups/,按时间戳分目录,before.json 是改动前。回滚前先确认设备输出已关闭。
4
放弃自动化,只保数据与告警。遥测曲线和四维告警是完全独立的另一条线,自动控制关掉照常工作。演示重点放在这里。

硬底线:没把握时不要让自动任务控制加热器。过温、干烧、失联的最终保护应在 STM32 本地实现;服务端看门狗在设备断网时无法保证命令送达。宁可手动,不要赌。

时间不够时的优先级

  1. 设备上报正常 + 遥测曲线能看 —— 不依赖任何脚本改动
  2. 告警能触发和清除 —— 改阈值即可,效果直观
  3. 手动控制泵和加热器 —— 点一下就有反应
  4. 自动温控 —— 先手动开泵,参数用默认 35°C
  5. 定量输送 —— 依赖流量精度,最容易偏差,放最后
↑ 回目录

09规则链全链路

iot_race 通用水循环控制 · 53 节点 / 56 连线。共 7 组,点开看每组在干什么。

三条入口路径:

  • 遥测(Post telemetry)→ 统一键名 → 存遥测 → 一分为二:① 四维告警 ② 自动控制状态机
  • RPC 请求(RPC Request to Device)→ 手动接管判断 → 是 Pump/Heater/setPump 就退出自动模式 → 原样下发
  • 属性更新(Attributes Updated)→ 扩展命令入口 → 校验协议 → 分发(当前无注册处理器,一律拒绝)
通用数据与告警13 个

原有模板部分。遥测统一键名、存时序、跑四个维度的告警。这条线跟自动控制完全独立,自动控制关掉它照常工作。

消息类型分流
唯一入口。按消息类型分三路:遥测走主数据路径,RPC 请求先过手动接管判断,属性更新进扩展命令入口。

读取安全阈值
从设备服务端属性读 6 个阈值放进 metadata。所以改阈值不用动规则链。

统一键名并计算状态
把 temperature1/t1/flow/press 等别名统一成标准键,并算出 tempAvg、tempDiff、sensorFault、temperatureAlarm、pressureAlarm、flowAlarm。

保存遥测与派生状态
存所有键为遥测,用设备时间戳。自动控制判断“数据是否新鲜”就靠它。

安全状态分流
按 4 个独立维度输出最多 8 个分支,互不压制。

温度异常告警

清除温度告警

压力异常告警

清除压力告警

开泵无流量告警

清除无流量告警

传感器缺失告警

清除传感器告警

自动控制:状态机6 个

读意图和事实 → TBEL 状态机算决策 → 存状态 → 存遥测。

读取自动控制参数
读 autoConfig(意图)和 autoRuntime(事实)。

计算自动控制状态
整条链的核心:TBEL 状态机。判新鲜度、认新 runId、校验参数、累计输送量、决定是否发指令、决定是否停机、判断停机是否已确认。

需要保存自动状态
只有 autoWrite=true 才往下走。不新鲜或重复的帧在这里被丢掉。

保存自动控制状态
把 autoRuntime 存回服务端属性。先记账、后动作。

生成自动控制遥测
把 autoMode/autoStatus/autoReason/deliveredLiters 变成遥测,供仪表板显示。

保存自动控制遥测

自动控制:指令下发10 个

有指令时逐条下发并记录设备反馈。停机是“先关加热器、再停泵”,串行执行。

自动控制动作分流
按 metadata 分流:COMMAND=有指令要发,WATCH=要安排看门狗。

生成第一条自动指令
把 autoPlan.commands[0] 还原成 RPC。停机时第一条通常是关加热器。

下发第一条自动指令
下发 RPC,超时 3 秒。Success 和 Failure 都接同一结果节点,超时也留痕。

记录第一条指令结果
比对设备真实反馈与期望值,写出 autoRpcConfirmed。

保存第一条指令结果

还有第二条停机指令
commands 超过 1 条时继续,通常意味着“关加热器之后还要停泵”。

生成第二条停机指令

下发第二条停机指令

记录第二条指令结果

保存第二条指令结果

看门狗与超时分支9 个

每帧运行任务安排一次 6 秒后的检查。设备失联就锁定任务并尝试停机。这套节点是复制出来的,脚本内容由 build.cjs 自动同步。

准备遥测看门狗
生成空消息,类型改为 AUTO_WATCHDOG。6 秒后重新读状态,不用现在的快照。

等待遥测超时检查
延迟 6 秒放行。这个秒数在节点表单里改,不用写脚本。

超时读取自动控制参数

超时计算自动控制状态
同一个状态机走 watch 分支:任务还在跑且超过 5 秒没遥测,就判 TELEMETRY_TIMEOUT 并要求停机。

超时需要保存自动状态

超时保存自动控制状态

超时生成自动控制遥测

超时保存自动控制遥测

需要超时停机指令

手动接管6 个

面板点泵/加热器开关会走这条。先把自动模式置 OFF,再执行你的指令。不依赖状态机,任何时候可用。

手动控制接管判断
判断 RPC 方法是不是 Pump/Heater/setPump。是就进接管流程。

保存原始手动请求

读取手动接管前状态

手动接管退出自动模式
把 autoConfig.mode 置 OFF,autoRuntime 置 MANUAL / MANUAL_TAKEOVER。

保存手动接管状态

恢复手动控制请求
把用户原始请求原样还原并下发。接管只关自动模式,不动别的执行器。

RPC 落地与属性保存2 个

手动指令与自动指令最终都汇总到这里发给设备。

下发泵和加热器 RPC
手动指令真正落地的地方,只在这里下发一次。

保存客户端属性
保存设备上报的客户端属性,仅在值变化时更新。

扩展功能命令入口7 个

给未来功能预留的协议化通道。当前没有注册处理器,未知功能会被拒绝并回写原因。

扩展功能命令入口
只有带 controlCommand 的属性更新才继续。

读取扩展命令回执

校验扩展命令协议
校验版本、requestId、时间戳、operation,并按已注册处理器判断功能是否支持。

扩展命令去重

保存扩展命令回执

恢复扩展命令参数

后续功能分发接口
按 extensionRoute 输出分支。当前没有注册任何 FEATURE_ 分支,所以扩展命令一律被拒绝。

两个看起来奇怪的地方:

  • 看门狗分支里有 9 个节点和正常分支几乎重名。这是刻意的:延迟节点之后如果回到原来的状态机节点,规则链里会出现环路。脚本内容由 build.cjs 自动复制,不是手写两遍。
  • 告警分支是终点,没有回到主干的线。创建/清除告警后消息就结束了。
↑ 回目录