前言

物模型指将物理空间中的实体数字化,并在云端构建该实体的数据模型。在物联网平台中,定义物模型即定义产品功能。完成功能定义后,系统将自动生成该产品的物模型。物模型描述产品是什么、能做什么、可以对外提供哪些服务。
物模型TSL(Thing Specification Language)。是一个JSON格式的文件。它是物理空间中的实体,如传感器、车载装置、楼宇、工厂等在云端的数字化表示,从属性、服务和事件三个维度,分别描述了该实体是什么、能做什么、可以对外提供哪些信息。定义了这三个维度,即完成了产品功能的定义。
物模型将产品功能类型分为三类:属性、服务和事件。定义了这三类功能,即完成了物模型的定义。

功能类型 说明
属性(Property) 一般用于描述设备运行时的状态,如环境监测设备所读取的当前环境温度等。属性支持GET和SET请求方式。应用系统可发起对属性的读取和设置请求。
服务(Service) 设备可被外部调用的能力或方法,可设置输入参数和输出参数。相比于属性,服务可通过一条指令实现更复杂的业务逻辑,如执行某项特定的任务。
事件(Event) 设备运行时的事件。事件一般包含需要被外部感知和处理的通知信息,可包含多个输出参数。如,某项任务完成的信息,或者设备发生故障或告警时的温度等,事件可以被订阅和推送。

以上名词来自阿里云物联网平台,解释原文: https://www.alibabacloud.com/help/zh/doc-detail/73727.htm

物模型格式

由于阿里物联网平台的物模型比较的复杂,我拿下面这个字符串来进行技术解析。

{ “stringKey”:”value1”, “booleanKey”:true, “doubleKey”:42.0, “longKey”:73, “jsonKey”: { “someNumber”: 42, “someArray”: [1,2,3], “someNestedObject”: {“key”: “value”} }}

最终能达到的效果:

  • 识别JSON中的键值内容,默认情况下,Key始终是一个字符串,而value可以是字符串类型(String)、布尔类型(Boolean)、浮点数类型(Double)、长整数类型(Long)和Json字符串。
  • 解析识别JSON字符串和JSON数组类型的字符串
  • 解析识别带有毫秒精度的unix时间戳的JSON字符串

效果如下:
Thingsboard源码分析-物模型 - 图1

引入依赖

使用序列化框架GSON对JSON格式的键值对进行识别解析,可以通过引入com.google.code.gson来配置关系。

com.google.code.gson gson

键值属性

Thingsboard源码分析-物模型 - 图2

KvEntry

KvEntry中提供了获取键值对属性的基本接口,例如获取字符属性的键,值和获取字符串,布尔型和数字类型的接口方法。BasicKvEntry定义了键只能为字符串类型,LongDataEntry,BooleanDataEntry,DoubleDataEntry和StringDataEntry分别定义了相应属性的值。

  1. public interface KvEntry extends Serializable {
  2. String getKey();
  3. DataType getDataType();
  4. Optional<String> getStrValue();
  5. Optional<Long> getLongValue();
  6. Optional<Boolean> getBooleanValue();
  7. Optional<Double> getDoubleValue();
  8. String getValueAsString();
  9. Object getValue();
  10. }

Thingsboard源码分析-物模型 - 图3

属性和上传数据

通过将来自设备的消息根据类型划分为设备属性(AttributesUpdateRequest)和设备上传数据(TelemetryUploadRequest),
其中TelemetryUploadRequest包含了Long型的unix时间戳。

Json识别解析

属性识别解析

属性识别解析如下,上传数据解析识别类似

UML 时序图如下:
Thingsboard源码分析-物模型 - 图4

  1. public class JsonConverter {
  2. private static final Gson GSON = new Gson();
  3. public static final String CAN_T_PARSE_VALUE = "Can't parse value: ";
  4. //遍历键值属性,对相应键值进行处理
  5. public static List<KvEntry> parseValues(JsonObject valuesObject) {
  6. List<KvEntry> result = new ArrayList<>();
  7. for (Map.Entry<String, JsonElement> valueEntry : valuesObject.entrySet()) {
  8. JsonElement element = valueEntry.getValue();
  9. if (element.isJsonPrimitive()) {
  10. JsonPrimitive value = element.getAsJsonPrimitive();
  11. //如果值为字符串
  12. if (value.isString()) {
  13. //新建StringDataEntry
  14. result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString()));
  15. //如果值为布尔型
  16. } else if (value.isBoolean()) {
  17. //新建BooleanDataEntry
  18. result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean()));
  19. //如果值为数值类型
  20. } else if (value.isNumber()) {
  21. parseNumericValue(result, valueEntry, value);
  22. } else {
  23. throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value);
  24. }
  25. } else {
  26. throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element);
  27. }
  28. }
  29. return result;
  30. }
  31. private static void parseNumericValue(List<KvEntry> result, Map.Entry<String, JsonElement> valueEntry, JsonPrimitive value) {
  32. //数值转化为字符串类型,并判断是不是包含".",来判断是Long,还是Double
  33. if (value.getAsString().contains(".")) {
  34. result.add(new DoubleDataEntry(valueEntry.getKey(), value.getAsDouble()));
  35. } else {
  36. try {
  37. long longValue = Long.parseLong(value.getAsString());
  38. result.add(new LongDataEntry(valueEntry.getKey(), longValue));
  39. } catch (NumberFormatException e) {
  40. throw new JsonSyntaxException("Big integer values are not supported!");
  41. }
  42. }
  43. }
  44. public static AttributesUpdateRequest convertToAttributes(JsonElement element) {
  45. return convertToAttributes(element, BasicRequest.DEFAULT_REQUEST_ID);
  46. }
  47. public static AttributesUpdateRequest convertToAttributes(JsonElement element, int requestId) {
  48. if (element.isJsonObject()) {
  49. BasicAttributesUpdateRequest request = new BasicAttributesUpdateRequest(requestId);
  50. long ts = System.currentTimeMillis();
  51. //将JSON字符串解析为键值属性的集合
  52. request.add(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList()));
  53. return request;
  54. } else {
  55. throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element);
  56. }
  57. }
  58. }

运行

准备工作: 安装Docker 我已经将此工程制作成镜像,并上传到DockerHub上。

:star2: :star2::star2:
源代码地址IOT-Guide-TSL

  1. 从DockerHub下载sanshengshui/iot-guide-tsl镜像 | docker pull sanshengshui/iot-gui-tsl | | :—- |

    1. 后台运行iot-guide-tsl,并将镜像端口80080映射到本机的8080
docker run -d -p 8080:8080 sanshengshui/iot-guide-tsl
  1. 利用curl测试接口 | curl -v -X POST -d ‘{“key1”:”value1”, “key2”:true, “key3”: 3.0, “key4”: 4}’ http://localhost:8080/ | | :—- |