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 与属性
在 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 时推送工单。事件处理器中应只做轻量判断与转发,避免耗时操作阻塞属性写入线程。
六、实战任务:扩展支持 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 才真正具备结构化属性的完整能力。