4. 自定义属性与属性读写扩展

ThingWorx 平台内置的属性类型覆盖了大多数常规场景:NUMBER、STRING、BOOLEAN、DATETIME、LOCATION、BLOB 等。但当设备上报的数据本身是结构化对象,例如一段包含温度、湿度、电量、告警码的 JSON 报文时,若仍用 STRING 承载,平台侧就无法对字段做类型校验、无法在 Mashup 中直接绑定子字段,也无法在属性变更事件中按字段粒度做判断。本节要解决的核心问题,就是通过 Java 扩展为 Thing 增加一种“自定义 DataShape 属性”,并配套实现属性读取、写入服务以及属性变更事件处理。

一、自定义属性类型的本质:把 DataShape 作为属性载体

在 ThingWorx 中,属性(Property)由 PropertyDefinition 描述,其关键字段包括 name、baseType、aspects 等。baseType 决定了属性值的存储与序列化方式。所谓“自定义属性类型”,并不是修改平台元模型,而是选择一种可承载结构化数据的 baseType(通常是 JSON 或 INFOTABLE),再通过 DataShape 定义其字段结构,从而在语义上形成一种新的属性类型。DataShape 在这里扮演“类型契约”的角色:它规定了该属性必须包含哪些字段、每个字段的类型与可选性。

因此,扩展一个支持 JSON 格式属性的设备 Thing,实际包含三层工作:第一层是定义 DataShape(字段契约);第二层是定义属性并绑定该 DataShape;第三层是用 Java 扩展实现属性的读写逻辑与变更事件处理。三者缺一不可,只定义 DataShape 而不写读写服务,属性就只是一个空壳。

自定义 DataShape 属性的读写与事件链路 设备 / 数据源 JSON 报文 {temp:23.5, humi:61, batt:88} 示例数据 DataShape 契约 temp : NUMBER humi : NUMBER batt : NUMBER 字段类型校验 Thing 属性定义 name: deviceStatus baseType: JSON dataShape: DeviceDS 绑定契约 Java 扩展服务层 readProperty 读取 JSON 属性 解析为 InfoTable 返回字段值 writeProperty 校验字段完整性 写入并持久化 触发变更事件 PropertyChange 监听属性变更 按字段判断阈值 触发告警/订阅 读写服务与事件处理共同构成自定义属性的完整生命周期

二、定义 DataShape 与属性

在 ThingWorx Composer 中新建一个 DataShape,命名为 DeviceStatusDS,字段定义如下:temp(NUMBER)、humi(NUMBER)、batt(NUMBER)、alarmCode(STRING)。随后在目标 Thing 上新增属性 deviceStatus,baseType 选择 JSON,dataShape 选择 DeviceStatusDS,并勾选“持久化”与“记录变更”。这一步完成后,Thing 已经具备承载结构化属性的能力,但读写行为仍由平台默认逻辑处理,无法满足字段级校验与事件响应需求,因此需要 Java 扩展介入。

三、Java 属性读取服务实现

读取服务的职责是把属性值从平台存储中取出,并按 DataShape 契约解析为可用的结构。核心思路是获取属性值对象,判断其类型,再转换为 InfoTable 或 JSONObject 返回。以下为示例实现(示例代码,非实测运行结果):

@ThingworxServiceDefinition(name = "readDeviceStatus",
    description = "读取设备 JSON 属性")
@ThingworxServiceResult(name = "result", baseType = "JSON")
public JSONObject readDeviceStatus(
    @ThingworxServiceParameter(name = "thingName",
        baseType = "STRING") String thingName) throws Exception {

    Thing thing = ThingUtilities.findThing(thingName);
    Object raw = thing.getPropertyValue("deviceStatus");
    JSONObject result = new JSONObject();

    if (raw instanceof JSONObject) {
        JSONObject src = (JSONObject) raw;
        result.put("temp", src.optDouble("temp", -1));
        result.put("humi", src.optDouble("humi", -1));
        result.put("batt", src.optDouble("batt", -1));
        result.put("alarmCode", src.optString("alarmCode", "NONE"));
    } else {
        result.put("error", "属性为空或类型不匹配");
    }
    return result;
}

读取服务的关键判断点在于:属性值可能为 null、可能为字符串形式的 JSON、也可能已是 JSONObject。工程上应先做类型归一化,再按字段取值,并对缺失字段给出默认值,避免上层 Mashup 因字段缺失而报错。

四、Java 属性写入服务实现

写入服务承担校验与持久化两项职责。校验包括:必填字段是否存在、数值是否在合理区间、alarmCode 是否属于允许集合。校验通过后再写入属性,写入动作会自然触发平台的属性变更事件。

@ThingworxServiceDefinition(name = "writeDeviceStatus")
@ThingworxServiceResult(name = "result", baseType = "BOOLEAN")
public Boolean writeDeviceStatus(
    @ThingworxServiceParameter(name = "thingName", baseType = "STRING") String thingName,
    @ThingworxServiceParameter(name = "payload", baseType = "JSON") JSONObject payload)
    throws Exception {

    if (!payload.has("temp") || !payload.has("humi")) {
        throw new Exception("缺少必填字段 temp 或 humi");
    }
    double temp = payload.getDouble("temp");
    if (temp < -40 || temp > 125) {
        throw new Exception("temp 超出合理范围: " + temp);
    }
    Thing thing = ThingUtilities.findThing(thingName);
    thing.setPropertyValue("deviceStatus", payload);
    return true;
}

写入时需注意:setPropertyValue 会触发属性变更事件,因此不要在事件处理器中再次调用写入服务,否则会形成递归。若确实需要在事件中回写,应设置一个标志位或使用平台提供的抑制机制。

五、属性变更事件处理

属性变更事件是自定义属性真正产生业务价值的地方。通过订阅 deviceStatus 的 PropertyChange 事件,可以在每次写入后按字段做判断。例如:当 batt 低于 20 时触发低电量告警;当 alarmCode 不为 NONE 时推送工单。事件处理器中应只做轻量判断与转发,避免耗时操作阻塞属性写入线程。

事件处理器中读取的是变更后的新值。若需要对比新旧值,可在事件对象中获取 oldValue 与 newValue,但并非所有 baseType 都提供 oldValue,JSON 类型通常只保证 newValue 可用。

六、实战任务:扩展支持 JSON 属性的设备 Thing

综合以上内容,实战任务可按以下顺序推进:第一步,创建 DeviceStatusDS,字段为 temp、humi、batt、alarmCode;第二步,在设备 Thing 上新增 JSON 属性 deviceStatus 并绑定该 DataShape;第三步,导入 Java 扩展,注册 readDeviceStatus 与 writeDeviceStatus 两个服务;第四步,为 deviceStatus 订阅 PropertyChange 事件,在事件中实现低电量与告警码判断;第五步,用一段示例 JSON 调用写入服务,再调用读取服务确认字段完整,最后观察事件是否按预期触发。

环节关键对象判断依据
契约定义DeviceStatusDS字段名与类型齐全
属性绑定deviceStatus (JSON)baseType 与 dataShape 匹配
写入校验writeDeviceStatus必填字段与范围检查通过
读取解析readDeviceStatus返回字段与契约一致
事件响应PropertyChange阈值条件命中并触发动作

需要强调的是,自定义属性的难点不在“能不能存 JSON”,而在于契约、读写、事件三者是否形成闭环。契约决定数据形状,读写服务决定数据进出是否受控,事件处理决定数据变化能否被业务感知。三者对齐后,设备 Thing 才真正具备结构化属性的完整能力。