4. ThingWorx 服务脚本实现数据清洗

ThingWorx 服务脚本实现数据清洗 学习卡片

为什么清洗要放在服务脚本里

设备侧上报的原始值往往同时存在三类问题:抖动噪声(如温度在 25.1/25.3/25.0 之间跳变)、单位不统一(有的设备上报 ℉,有的上报 ℃)、越界异常(如 -999 表示传感器故障、或 200℃ 的明显错误值)。把清洗逻辑写成 ThingWorx 服务脚本,可以让所有订阅该属性的下游逻辑(告警、存储、看板)拿到的是同一份"干净值",避免每个消费方各写一套判断。

清洗服务的输入与输出约定

本案例定义一个名为 CleanDeviceData 的服务,输入参数如下:

  • rawValue(NUMBER):设备上报的原始数值
  • rawUnit(STRING):原始单位,取值 "C" 或 "F"
  • minValid(NUMBER):有效下限,示例取 -40
  • maxValid(NUMBER):有效上限,示例取 125

输出为 InfoTable,包含 cleanValue(清洗后摄氏度值)、status(OK / FILTERED / CONVERTED)、reason(说明文本)。

原始输入 rawValue rawUnit minValid / maxValid ① 单位换算 若 rawUnit = "F" C = (F - 32) × 5/9 否则保持原值 ② 异常值过滤 C < minValid 或 C > maxValid → 标记 FILTERED ③ 去噪 滑动窗口 均值平滑 N=3 输出 InfoTable cleanValue : NUMBER (℃) status : "OK" | "FILTERED" | "CONVERTED" reason : STRING 异常分支 越界 → 丢弃并记录 单位非法 → 拒绝 非数值 → 拒绝 示例数据流(教学示例) 输入: rawValue=77.0, rawUnit="F" → 换算: (77-32)×5/9 = 25.0℃ → 范围检查: -40 ≤ 25.0 ≤ 125 ✓ → 输出: cleanValue=25.0, status="CONVERTED" 输入: rawValue=-999, rawUnit="C" → 范围检查: -999 < -40 ✗ → 输出: status="FILTERED", reason="below minValid" 输入: rawValue=25.1/25.3/25.0 (连续3次) → 滑动均值 = 25.13℃ → 输出: cleanValue=25.13, status="OK"

服务脚本实现

在 ThingWorx Composer 中新建服务,脚本类型选择 JavaScript,粘贴以下代码。脚本使用 ThingWorx 内置的 InfoTable 与 logger 对象,无需额外引入库。

// 服务名: CleanDeviceData
// 输入: rawValue(NUMBER), rawUnit(STRING), minValid(NUMBER), maxValid(NUMBER)
// 输出: InfoTable(cleanValue, status, reason)

var params = {
    infoTableName: "CleanResult",
    dataShapeName: "CleanResultShape"
};

// 1. 单位换算
var celsius = rawValue;
var converted = false;
if (rawUnit === "F") {
    celsius = (rawValue - 32) * 5 / 9;
    converted = true;
} else if (rawUnit !== "C") {
    // 非法单位,直接拒绝
    var t = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params);
    t.AddRow({ cleanValue: 0, status: "FILTERED", reason: "invalid unit: " + rawUnit });
    return t;
}

// 2. 异常值过滤
if (celsius < minValid || celsius > maxValid) {
    logger.warn("CleanDeviceData filtered out-of-range value: " + celsius);
    var t2 = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params);
    t2.AddRow({ cleanValue: 0, status: "FILTERED", reason: "out of range [" + minValid + "," + maxValid + "]" });
    return t2;
}

// 3. 去噪:使用 Thing 属性保存最近3次值做滑动均值
var history = me.lastThreeValues; // INFOTABLE 或 STRING,示例用逗号分隔字符串
var arr = [];
if (history && history.length > 0) {
    arr = history.split(",").map(Number);
}
arr.push(celsius);
if (arr.length > 3) { arr.shift(); }
me.lastThreeValues = arr.join(",");

var sum = 0;
for (var i = 0; i < arr.length; i++) { sum += arr[i]; }
var smoothed = sum / arr.length;

// 4. 组装输出
var result = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape(params);
result.AddRow({
    cleanValue: smoothed,
    status: converted ? "CONVERTED" : "OK",
    reason: "window=" + arr.length + ", raw=" + rawValue + rawUnit
});
return result;
去噪部分用 Thing 属性 lastThreeValues 保存历史值,避免引入外部存储。首次调用时该属性为空,脚本会自动跳过历史拼接,只对当前值做均值。

验证与结果判读

在 Composer 的服务测试面板中依次输入以下三组参数,观察返回 InfoTable 的 status 与 cleanValue:

rawValuerawUnit预期 status预期 cleanValue判读依据
77.0FCONVERTED25.0单位换算后落在有效区间
-999CFILTERED0低于 minValid=-40
25.1 / 25.3 / 25.0COK≈25.13三次滑动均值
若返回的 status 始终为 FILTERED,先检查 minValid/maxValid 是否被误传为字符串;ThingWorx 服务参数类型不匹配时不会报错,而是按字符串比较,导致所有值都被判为越界。

接入订阅链路的建议

清洗服务本身不绑定订阅。若要让设备属性变化时自动清洗,可在 Thing 的 Temperature 属性上添加订阅,事件触发时调用 CleanDeviceData,并把返回的 cleanValue 写回另一个属性 TemperatureClean。这样告警与存储逻辑只需读取 TemperatureClean,无需重复实现换算与过滤。