Browse Source

Merge remote-tracking branch 'upstream/develop/3.6' into improvement/gateway-dashboard/change

pull/9242/head
Vladyslav_Prykhodko 3 years ago
parent
commit
ba5f7ee321
  1. 267
      application/src/main/data/json/demo/dashboards/firmware.json
  2. 234
      application/src/main/data/json/demo/dashboards/gateways.json
  3. 82
      application/src/main/data/json/demo/dashboards/rule_engine_statistics.json
  4. 267
      application/src/main/data/json/demo/dashboards/software.json
  5. 229
      application/src/main/data/json/demo/dashboards/thermostats.json
  6. 2
      application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
  7. 2
      application/src/main/data/json/system/widget_types/compass.json
  8. 2
      application/src/main/data/json/system/widget_types/radial_gauge.json
  9. 2
      application/src/main/data/json/system/widget_types/speed_gauge.json
  10. 2
      application/src/main/data/json/system/widget_types/temperature_radial_gauge.json
  11. 2
      application/src/main/data/json/system/widget_types/thermometer_scale.json
  12. 2
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  13. 2
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  14. 2
      application/src/main/data/upgrade/3.5.1/schema_update.sql
  15. 17
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  16. 5
      application/src/main/java/org/thingsboard/server/service/component/RuleNodeClassInfo.java
  17. 49
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  18. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  19. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
  20. 54
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  21. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
  22. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java
  23. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
  24. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java
  25. 21
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  26. 13
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  27. 48
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  28. 68
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  29. 4
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  30. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java
  31. 5
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java
  32. 5
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  33. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  34. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java
  35. 2
      application/src/main/resources/logback.xml
  36. 4
      application/src/main/resources/thingsboard.yml
  37. 4
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  38. 144
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java
  39. 13
      application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java
  40. 3
      application/src/test/java/org/thingsboard/server/edge/WidgetEdgeTest.java
  41. 306
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java
  42. 92
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AbstractAssetProcessorTest.java
  43. 44
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorTest.java
  44. 43
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessorTest.java
  45. 100
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java
  46. 43
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorTest.java
  47. 45
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessorTest.java
  48. 119
      application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java
  49. 53
      application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
  50. 25
      build_proto.sh
  51. 23
      common/cluster-api/src/main/proto/queue.proto
  52. 9
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  53. 6
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java
  54. 4
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java
  55. 18
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java
  56. 3
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java
  57. 3
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java
  58. 29
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/ToServerRpcRequestSnmpCommunicationConfig.java
  59. 32
      common/data/src/main/java/org/thingsboard/server/common/data/util/TypeCastUtil.java
  60. 2
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  61. 15
      common/edge-api/src/main/proto/edge.proto
  62. 4
      common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java
  63. 65
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DateTimeFormatOptions.java
  64. 81
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java
  65. 2
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  66. 182
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java
  67. 74
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java
  68. 97
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java
  69. 221
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java
  70. 31
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java
  71. 23
      common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java
  72. 43
      common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java
  73. 7
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  74. 49
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  75. 21
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  76. 7
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java
  77. 2
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java
  78. 23
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  79. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java
  80. 13
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
  81. 5
      dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java
  82. 3
      dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java
  83. 158
      dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java
  84. 2
      docker/docker-compose.yml
  85. 1
      docker/tb-snmp-transport.env
  86. 2
      pom.xml
  87. 16
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java
  88. 35
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbVersionedNode.java
  89. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java
  90. 30
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java
  91. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java
  92. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  93. 150
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java
  94. 4
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  95. 13
      ui-ngx/src/app/core/translate/missing-translate-handler.ts
  96. 8
      ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html
  97. 10
      ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts
  98. 2
      ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html
  99. 5
      ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts
  100. 10
      ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts

267
application/src/main/data/json/demo/dashboards/firmware.json

@ -1,24 +1,34 @@
{ {
"title": "Firmware", "title": "Firmware",
"image": null, "image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": { "configuration": {
"description": "", "description": "",
"widgets": { "widgets": {
"cd03188e-cd9d-9601-fd57-da4cb95fc016": { "cd03188e-cd9d-9601-fd57-da4cb95fc016": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -299,16 +309,11 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "cd03188e-cd9d-9601-fd57-da4cb95fc016" "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016",
"typeFullFqn": "system.cards.entities_table"
}, },
"100b756c-0082-6505-3ae1-3603e6deea48": { "100b756c-0082-6505-3ae1-3603e6deea48": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "timeseries_table",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 8, "sizeX": 8,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
@ -461,14 +466,11 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "100b756c-0082-6505-3ae1-3603e6deea48" "id": "100b756c-0082-6505-3ae1-3603e6deea48",
"typeFullFqn": "system.cards.timeseries_table"
}, },
"17543c57-af4a-2c1e-bf12-53a7b46791e6": { "17543c57-af4a-2c1e-bf12-53a7b46791e6": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -496,8 +498,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -545,14 +566,11 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "17543c57-af4a-2c1e-bf12-53a7b46791e6" "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6",
"typeFullFqn": "system.cards.html_value_card"
}, },
"6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -580,8 +598,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -629,14 +666,11 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6" "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6",
"typeFullFqn": "system.cards.html_value_card"
}, },
"e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -664,8 +698,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -713,14 +766,11 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81" "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81",
"typeFullFqn": "system.cards.html_value_card"
}, },
"77b10144-b904-edd5-8c7c-8fb75616c6d8": { "77b10144-b904-edd5-8c7c-8fb75616c6d8": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -748,8 +798,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -797,23 +866,32 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "77b10144-b904-edd5-8c7c-8fb75616c6d8" "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8",
"typeFullFqn": "system.cards.html_value_card"
}, },
"21be08bb-ec90-f760-ad6f-e7678f12c401": { "21be08bb-ec90-f760-ad6f-e7678f12c401": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1094,23 +1172,32 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "21be08bb-ec90-f760-ad6f-e7678f12c401" "id": "21be08bb-ec90-f760-ad6f-e7678f12c401",
"typeFullFqn": "system.cards.entities_table"
}, },
"e8280043-d3dc-7acb-c2ff-a4522972ff91": { "e8280043-d3dc-7acb-c2ff-a4522972ff91": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1391,23 +1478,32 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "e8280043-d3dc-7acb-c2ff-a4522972ff91" "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91",
"typeFullFqn": "system.cards.entities_table"
}, },
"3624013b-378c-f110-5eba-ae95c25a4dcc": { "3624013b-378c-f110-5eba-ae95c25a4dcc": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1688,23 +1784,32 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "3624013b-378c-f110-5eba-ae95c25a4dcc" "id": "3624013b-378c-f110-5eba-ae95c25a4dcc",
"typeFullFqn": "system.cards.entities_table"
}, },
"d2d13e0d-4e71-889f-9343-ad2f0af9f176": { "d2d13e0d-4e71-889f-9343-ad2f0af9f176": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085363133,
"endTimeMs": 1694171763133
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1985,7 +2090,8 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176" "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176",
"typeFullFqn": "system.cards.entities_table"
} }
}, },
"states": { "states": {
@ -2035,7 +2141,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": true, "mobileAutoFillHeight": true,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2062,7 +2169,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2089,7 +2197,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2116,7 +2225,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2143,7 +2253,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2170,7 +2281,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2488,5 +2600,6 @@
"showUpdateDashboardImage": false "showUpdateDashboardImage": false
} }
}, },
"externalId": null,
"name": "Firmware" "name": "Firmware"
} }

234
application/src/main/data/json/demo/dashboards/gateways.json

@ -1,20 +1,33 @@
{ {
"title": "Gateways", "title": "Gateways",
"image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": { "configuration": {
"widgets": { "widgets": {
"94715984-ae74-76e4-20b7-2f956b01ed80": { "94715984-ae74-76e4-20b7-2f956b01ed80": {
"isSystemType": true,
"bundleAlias": "entity_admin_widgets",
"typeAlias": "device_admin_table",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 24, "sizeX": 24,
"sizeY": 12, "sizeY": 12,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085270425,
"endTimeMs": 1694171670425
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -187,14 +200,11 @@
] ]
} }
}, },
"id": "94715984-ae74-76e4-20b7-2f956b01ed80" "id": "94715984-ae74-76e4-20b7-2f956b01ed80",
"typeFullFqn": "system.entity_admin_widgets.device_admin_table"
}, },
"eadabbc7-519e-76fc-ba10-b3fe8c18da10": { "eadabbc7-519e-76fc-ba10-b3fe8c18da10": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "timeseries_table",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 14, "sizeX": 14,
"sizeY": 13, "sizeY": 13,
"config": { "config": {
@ -255,14 +265,11 @@
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "eadabbc7-519e-76fc-ba10-b3fe8c18da10" "id": "eadabbc7-519e-76fc-ba10-b3fe8c18da10",
"typeFullFqn": "system.cards.timeseries_table"
}, },
"f928afc4-30d1-8d0c-e3cf-777f9f9d1155": { "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 17, "sizeX": 17,
"sizeY": 4, "sizeY": 4,
"config": { "config": {
@ -375,6 +382,15 @@
"xaxisSecond": { "xaxisSecond": {
"axisPosition": "top", "axisPosition": "top",
"showLabels": true "showLabels": true
},
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "right",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": true
} }
}, },
"title": "Real time information", "title": "Real time information",
@ -393,25 +409,13 @@
"widgetStyle": {}, "widgetStyle": {},
"useDashboardTimewindow": false, "useDashboardTimewindow": false,
"displayTimewindow": true, "displayTimewindow": true,
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "right",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": true
},
"actions": {} "actions": {}
}, },
"id": "f928afc4-30d1-8d0c-e3cf-777f9f9d1155" "id": "f928afc4-30d1-8d0c-e3cf-777f9f9d1155",
"typeFullFqn": "system.charts.basic_timeseries"
}, },
"2a95b473-042d-59d0-2da2-40d0cccb6c8a": { "2a95b473-042d-59d0-2da2-40d0cccb6c8a": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "timeseries_table",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 7, "sizeX": 7,
"sizeY": 7, "sizeY": 7,
"config": { "config": {
@ -491,14 +495,11 @@
"showTotal": false "showTotal": false
} }
}, },
"id": "2a95b473-042d-59d0-2da2-40d0cccb6c8a" "id": "2a95b473-042d-59d0-2da2-40d0cccb6c8a",
"typeFullFqn": "system.cards.timeseries_table"
}, },
"aaa69366-aacc-9028-65aa-645c0f8533ec": { "aaa69366-aacc-9028-65aa-645c0f8533ec": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 17, "sizeX": 17,
"sizeY": 4, "sizeY": 4,
"config": { "config": {
@ -604,6 +605,15 @@
"xaxisSecond": { "xaxisSecond": {
"axisPosition": "top", "axisPosition": "top",
"showLabels": true "showLabels": true
},
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "right",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": true
} }
}, },
"title": "History information", "title": "History information",
@ -622,25 +632,13 @@
"widgetStyle": {}, "widgetStyle": {},
"useDashboardTimewindow": true, "useDashboardTimewindow": true,
"displayTimewindow": true, "displayTimewindow": true,
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "right",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": true
},
"actions": {} "actions": {}
}, },
"id": "aaa69366-aacc-9028-65aa-645c0f8533ec" "id": "aaa69366-aacc-9028-65aa-645c0f8533ec",
"typeFullFqn": "system.charts.basic_timeseries"
}, },
"ce5c7d01-a3ef-5cf0-4578-8505135c23a0": { "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 17, "sizeX": 17,
"sizeY": 4, "sizeY": 4,
"config": { "config": {
@ -751,6 +749,15 @@
"xaxisSecond": { "xaxisSecond": {
"axisPosition": "top", "axisPosition": "top",
"showLabels": true "showLabels": true
},
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "right",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": true
} }
}, },
"title": "Real time information", "title": "Real time information",
@ -769,25 +776,13 @@
"widgetStyle": {}, "widgetStyle": {},
"useDashboardTimewindow": false, "useDashboardTimewindow": false,
"displayTimewindow": true, "displayTimewindow": true,
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "right",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": true
},
"actions": {} "actions": {}
}, },
"id": "ce5c7d01-a3ef-5cf0-4578-8505135c23a0" "id": "ce5c7d01-a3ef-5cf0-4578-8505135c23a0",
"typeFullFqn": "system.charts.basic_timeseries"
}, },
"466f046d-6005-a168-b107-60fcb2469cd5": { "466f046d-6005-a168-b107-60fcb2469cd5": {
"isSystemType": true,
"bundleAlias": "gateway_widgets",
"typeAlias": "attributes_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 7, "sizeX": 7,
"sizeY": 5, "sizeY": 5,
"config": { "config": {
@ -799,8 +794,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085270425,
"endTimeMs": 1694171670425
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": true, "showTitle": true,
@ -832,14 +846,11 @@
"showLegend": false, "showLegend": false,
"actions": {} "actions": {}
}, },
"id": "466f046d-6005-a168-b107-60fcb2469cd5" "id": "466f046d-6005-a168-b107-60fcb2469cd5",
"typeFullFqn": "system.gateway_widgets.attributes_card"
}, },
"8fc32225-164f-3258-73f7-e6b6d959cf0b": { "8fc32225-164f-3258-73f7-e6b6d959cf0b": {
"isSystemType": true,
"bundleAlias": "gateway_widgets",
"typeAlias": "config_form_latest",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 10, "sizeX": 10,
"sizeY": 9, "sizeY": 9,
"config": { "config": {
@ -851,8 +862,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085270425,
"endTimeMs": 1694171670425
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": true, "showTitle": true,
@ -881,14 +911,11 @@
"showLegend": false, "showLegend": false,
"actions": {} "actions": {}
}, },
"id": "8fc32225-164f-3258-73f7-e6b6d959cf0b" "id": "8fc32225-164f-3258-73f7-e6b6d959cf0b",
"typeFullFqn": "system.gateway_widgets.config_form_latest"
}, },
"063fc179-c9fd-f952-e714-f24e9c43c05c": { "063fc179-c9fd-f952-e714-f24e9c43c05c": {
"isSystemType": true,
"bundleAlias": "control_widgets",
"typeAlias": "rpcbutton",
"type": "rpc", "type": "rpc",
"title": "New widget",
"sizeX": 4, "sizeX": 4,
"sizeY": 2, "sizeY": 2,
"config": { "config": {
@ -930,14 +957,11 @@
"b2487e75-2fa4-f211-142c-434dfd50c70c" "b2487e75-2fa4-f211-142c-434dfd50c70c"
] ]
}, },
"id": "063fc179-c9fd-f952-e714-f24e9c43c05c" "id": "063fc179-c9fd-f952-e714-f24e9c43c05c",
"typeFullFqn": "system.control_widgets.rpcbutton"
}, },
"3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": { "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": {
"isSystemType": true,
"bundleAlias": "control_widgets",
"typeAlias": "rpcbutton",
"type": "rpc", "type": "rpc",
"title": "New widget",
"sizeX": 4, "sizeX": 4,
"sizeY": 2, "sizeY": 2,
"config": { "config": {
@ -979,14 +1003,11 @@
"b2487e75-2fa4-f211-142c-434dfd50c70c" "b2487e75-2fa4-f211-142c-434dfd50c70c"
] ]
}, },
"id": "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7" "id": "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7",
"typeFullFqn": "system.control_widgets.rpcbutton"
}, },
"6770b6ba-eff8-df05-75f8-c1f9326d4842": { "6770b6ba-eff8-df05-75f8-c1f9326d4842": {
"isSystemType": true,
"bundleAlias": "input_widgets",
"typeAlias": "markers_placement_openstreetmap",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 6, "sizeX": 6,
"sizeY": 4, "sizeY": 4,
"config": { "config": {
@ -1015,8 +1036,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085270425,
"endTimeMs": 1694171670425
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -1083,7 +1123,8 @@
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "6770b6ba-eff8-df05-75f8-c1f9326d4842" "id": "6770b6ba-eff8-df05-75f8-c1f9326d4842",
"typeFullFqn": "system.input_widgets.markers_placement_openstreetmap"
} }
}, },
"states": { "states": {
@ -1108,7 +1149,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70, "mobileRowHeight": 70,
"margin": 10 "margin": 10,
"outerMargin": true
} }
} }
} }
@ -1158,7 +1200,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70, "mobileRowHeight": 70,
"margin": 10 "margin": 10,
"outerMargin": true
} }
} }
} }
@ -1211,7 +1254,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"mobileAutoFillHeight": true, "mobileAutoFillHeight": true,
"mobileRowHeight": 70, "mobileRowHeight": 70,
"margin": 10 "margin": 10,
"outerMargin": true
} }
} }
} }
@ -1224,8 +1268,10 @@
"filter": { "filter": {
"type": "deviceType", "type": "deviceType",
"resolveMultiple": true, "resolveMultiple": true,
"deviceType": "gateway", "deviceNameFilter": "",
"deviceNameFilter": "" "deviceTypes": [
"gateway"
]
} }
}, },
"b2487e75-2fa4-f211-142c-434dfd50c70c": { "b2487e75-2fa4-f211-142c-434dfd50c70c": {
@ -1261,7 +1307,9 @@
"showDashboardExport": true, "showDashboardExport": true,
"toolbarAlwaysOpen": true, "toolbarAlwaysOpen": true,
"titleColor": "rgba(0,0,0,0.870588)" "titleColor": "rgba(0,0,0,0.870588)"
} },
"filters": {}
}, },
"externalId": null,
"name": "Gateways" "name": "Gateways"
} }

82
application/src/main/data/json/demo/dashboards/rule_engine_statistics.json

@ -1,13 +1,12 @@
{ {
"title": "Rule Engine Statistics", "title": "Rule Engine Statistics",
"image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": { "configuration": {
"widgets": { "widgets": {
"81987f19-3eac-e4ce-b790-d96e9b54d9a0": { "81987f19-3eac-e4ce-b790-d96e9b54d9a0": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 12, "sizeX": 12,
"sizeY": 7, "sizeY": 7,
"config": { "config": {
@ -151,6 +150,15 @@
"xaxisSecond": { "xaxisSecond": {
"axisPosition": "top", "axisPosition": "top",
"showLabels": true "showLabels": true
},
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": false,
"showTotal": true
} }
}, },
"title": "Queue Stats", "title": "Queue Stats",
@ -169,25 +177,13 @@
"widgetStyle": {}, "widgetStyle": {},
"useDashboardTimewindow": false, "useDashboardTimewindow": false,
"displayTimewindow": true, "displayTimewindow": true,
"showLegend": true, "actions": {}
"actions": {},
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": false,
"showTotal": true
}
}, },
"id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0" "id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0",
"typeFullFqn": "system.charts.basic_timeseries"
}, },
"5eb79712-5c24-3060-7e4f-6af36b8f842d": { "5eb79712-5c24-3060-7e4f-6af36b8f842d": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "timeseries_table",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 24, "sizeX": 24,
"sizeY": 5, "sizeY": 5,
"config": { "config": {
@ -272,14 +268,11 @@
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "5eb79712-5c24-3060-7e4f-6af36b8f842d" "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d",
"typeFullFqn": "system.cards.timeseries_table"
}, },
"ad3f1417-87a8-750e-fc67-49a2de1466d4": { "ad3f1417-87a8-750e-fc67-49a2de1466d4": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 12, "sizeX": 12,
"sizeY": 7, "sizeY": 7,
"config": { "config": {
@ -393,6 +386,15 @@
"xaxisSecond": { "xaxisSecond": {
"axisPosition": "top", "axisPosition": "top",
"showLabels": true "showLabels": true
},
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": false,
"showTotal": true
} }
}, },
"title": "Processing Failures and Timeouts", "title": "Processing Failures and Timeouts",
@ -411,18 +413,10 @@
"widgetStyle": {}, "widgetStyle": {},
"useDashboardTimewindow": false, "useDashboardTimewindow": false,
"displayTimewindow": true, "displayTimewindow": true,
"showLegend": true, "actions": {}
"actions": {},
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": false,
"showTotal": true
}
}, },
"id": "ad3f1417-87a8-750e-fc67-49a2de1466d4" "id": "ad3f1417-87a8-750e-fc67-49a2de1466d4",
"typeFullFqn": "system.charts.basic_timeseries"
} }
}, },
"states": { "states": {
@ -457,14 +451,12 @@
"backgroundColor": "#eeeeee", "backgroundColor": "#eeeeee",
"color": "rgba(0,0,0,0.870588)", "color": "rgba(0,0,0,0.870588)",
"columns": 24, "columns": 24,
"margins": [
10,
10
],
"backgroundSizeMode": "100%", "backgroundSizeMode": "100%",
"autoFillHeight": true, "autoFillHeight": true,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"margin": 10,
"outerMargin": true
} }
} }
} }
@ -477,8 +469,10 @@
"filter": { "filter": {
"type": "assetType", "type": "assetType",
"resolveMultiple": true, "resolveMultiple": true,
"assetType": "TbServiceQueue", "assetNameFilter": "",
"assetNameFilter": "" "assetTypes": [
"TbServiceQueue"
]
} }
} }
}, },
@ -514,7 +508,9 @@
"showDashboardTimewindow": true, "showDashboardTimewindow": true,
"showDashboardExport": true, "showDashboardExport": true,
"toolbarAlwaysOpen": true "toolbarAlwaysOpen": true
} },
"filters": {}
}, },
"externalId": null,
"name": "Rule Engine Statistics" "name": "Rule Engine Statistics"
} }

267
application/src/main/data/json/demo/dashboards/software.json

@ -1,24 +1,34 @@
{ {
"title": "Software", "title": "Software",
"image": null, "image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": { "configuration": {
"description": "", "description": "",
"widgets": { "widgets": {
"cd03188e-cd9d-9601-fd57-da4cb95fc016": { "cd03188e-cd9d-9601-fd57-da4cb95fc016": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343279,
"endTimeMs": 1694171743279
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -299,16 +309,11 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "cd03188e-cd9d-9601-fd57-da4cb95fc016" "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016",
"typeFullFqn": "system.cards.entities_table"
}, },
"100b756c-0082-6505-3ae1-3603e6deea48": { "100b756c-0082-6505-3ae1-3603e6deea48": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "timeseries_table",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 8, "sizeX": 8,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
@ -461,14 +466,11 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "100b756c-0082-6505-3ae1-3603e6deea48" "id": "100b756c-0082-6505-3ae1-3603e6deea48",
"typeFullFqn": "system.cards.timeseries_table"
}, },
"17543c57-af4a-2c1e-bf12-53a7b46791e6": { "17543c57-af4a-2c1e-bf12-53a7b46791e6": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -496,8 +498,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -545,14 +566,11 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "17543c57-af4a-2c1e-bf12-53a7b46791e6" "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6",
"typeFullFqn": "system.cards.html_value_card"
}, },
"6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -580,8 +598,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -629,14 +666,11 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6" "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6",
"typeFullFqn": "system.cards.html_value_card"
}, },
"e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -664,8 +698,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -713,14 +766,11 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81" "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81",
"typeFullFqn": "system.cards.html_value_card"
}, },
"77b10144-b904-edd5-8c7c-8fb75616c6d8": { "77b10144-b904-edd5-8c7c-8fb75616c6d8": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "html_value_card",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 8, "sizeX": 8,
"sizeY": 3, "sizeY": 3,
"config": { "config": {
@ -748,8 +798,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -797,23 +866,32 @@
"enableDataExport": false, "enableDataExport": false,
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "77b10144-b904-edd5-8c7c-8fb75616c6d8" "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8",
"typeFullFqn": "system.cards.html_value_card"
}, },
"21be08bb-ec90-f760-ad6f-e7678f12c401": { "21be08bb-ec90-f760-ad6f-e7678f12c401": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1094,23 +1172,32 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "21be08bb-ec90-f760-ad6f-e7678f12c401" "id": "21be08bb-ec90-f760-ad6f-e7678f12c401",
"typeFullFqn": "system.cards.entities_table"
}, },
"e8280043-d3dc-7acb-c2ff-a4522972ff91": { "e8280043-d3dc-7acb-c2ff-a4522972ff91": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1391,23 +1478,32 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "e8280043-d3dc-7acb-c2ff-a4522972ff91" "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91",
"typeFullFqn": "system.cards.entities_table"
}, },
"3624013b-378c-f110-5eba-ae95c25a4dcc": { "3624013b-378c-f110-5eba-ae95c25a4dcc": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1688,23 +1784,32 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "3624013b-378c-f110-5eba-ae95c25a4dcc" "id": "3624013b-378c-f110-5eba-ae95c25a4dcc",
"typeFullFqn": "system.cards.entities_table"
}, },
"d2d13e0d-4e71-889f-9343-ad2f0af9f176": { "d2d13e0d-4e71-889f-9343-ad2f0af9f176": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"image": null,
"description": null,
"sizeX": 7.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 6.5,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085343280,
"endTimeMs": 1694171743280
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -1985,7 +2090,8 @@
}, },
"row": 0, "row": 0,
"col": 0, "col": 0,
"id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176" "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176",
"typeFullFqn": "system.cards.entities_table"
} }
}, },
"states": { "states": {
@ -2035,7 +2141,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": true, "mobileAutoFillHeight": true,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2062,7 +2169,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2089,7 +2197,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2116,7 +2225,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2143,7 +2253,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2170,7 +2281,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"backgroundImageUrl": null, "backgroundImageUrl": null,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70 "mobileRowHeight": 70,
"outerMargin": true
} }
} }
} }
@ -2488,5 +2600,6 @@
"showUpdateDashboardImage": false "showUpdateDashboardImage": false
} }
}, },
"externalId": null,
"name": "Software" "name": "Software"
} }

229
application/src/main/data/json/demo/dashboards/thermostats.json

@ -1,20 +1,33 @@
{ {
"title": "Thermostats", "title": "Thermostats",
"image": null,
"mobileHide": false,
"mobileOrder": null,
"configuration": { "configuration": {
"widgets": { "widgets": {
"f33c746c-0dfc-c212-395b-b448c8a17209": { "f33c746c-0dfc-c212-395b-b448c8a17209": {
"isSystemType": true,
"bundleAlias": "cards",
"typeAlias": "entities_table",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 11, "sizeX": 11,
"sizeY": 11, "sizeY": 11,
"config": { "config": {
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"realtimeType": 1,
"interval": 1000, "interval": 1000,
"timewindowMs": 86400000 "timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085177686,
"endTimeMs": 1694171577686
},
"quickInterval": "CURRENT_DAY"
}, },
"aggregation": { "aggregation": {
"type": "NONE", "type": "NONE",
@ -184,14 +197,11 @@
"rowClick": [] "rowClick": []
} }
}, },
"id": "f33c746c-0dfc-c212-395b-b448c8a17209" "id": "f33c746c-0dfc-c212-395b-b448c8a17209",
"typeFullFqn": "system.cards.entities_table"
}, },
"7943196b-eedb-d422-f9c3-b32d379ad172": { "7943196b-eedb-d422-f9c3-b32d379ad172": {
"isSystemType": true,
"bundleAlias": "alarm_widgets",
"typeAlias": "alarms_table",
"type": "alarm", "type": "alarm",
"title": "New widget",
"sizeX": 13, "sizeX": 13,
"sizeY": 5, "sizeY": 5,
"config": { "config": {
@ -230,7 +240,7 @@
"titleStyle": { "titleStyle": {
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
"padding": "5px 10px 5px 10px" "padding": "5px 10px 5px 2px"
}, },
"useDashboardTimewindow": false, "useDashboardTimewindow": false,
"showLegend": false, "showLegend": false,
@ -290,7 +300,6 @@
} }
] ]
}, },
"alarmSearchStatus": "ANY",
"alarmsPollingInterval": 5, "alarmsPollingInterval": 5,
"showTitleIcon": false, "showTitleIcon": false,
"titleIcon": null, "titleIcon": null,
@ -302,16 +311,22 @@
"actions": {}, "actions": {},
"datasources": [], "datasources": [],
"alarmsMaxCountLoad": 0, "alarmsMaxCountLoad": 0,
"alarmsFetchSize": 100 "alarmsFetchSize": 100,
"alarmFilterConfig": {
"statusList": [],
"severityList": [],
"typeList": [],
"searchPropagatedAlarms": true
},
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": ""
}, },
"id": "7943196b-eedb-d422-f9c3-b32d379ad172" "id": "7943196b-eedb-d422-f9c3-b32d379ad172",
"typeFullFqn": "system.alarm_widgets.alarms_table"
}, },
"14a19183-f0b2-d6be-0f62-9863f0a51111": { "14a19183-f0b2-d6be-0f62-9863f0a51111": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 18, "sizeX": 18,
"sizeY": 6, "sizeY": 6,
"config": { "config": {
@ -395,7 +410,16 @@
"axisPosition": "top", "axisPosition": "top",
"showLabels": true "showLabels": true
}, },
"smoothLines": true "smoothLines": true,
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": false
}
}, },
"title": "Temperature", "title": "Temperature",
"dropShadow": true, "dropShadow": true,
@ -413,25 +437,13 @@
"widgetStyle": {}, "widgetStyle": {},
"useDashboardTimewindow": false, "useDashboardTimewindow": false,
"displayTimewindow": true, "displayTimewindow": true,
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": false
},
"actions": {} "actions": {}
}, },
"id": "14a19183-f0b2-d6be-0f62-9863f0a51111" "id": "14a19183-f0b2-d6be-0f62-9863f0a51111",
"typeFullFqn": "system.charts.basic_timeseries"
}, },
"07f49fd5-a73b-d74c-c220-362c20af81f4": { "07f49fd5-a73b-d74c-c220-362c20af81f4": {
"isSystemType": true,
"bundleAlias": "charts",
"typeAlias": "basic_timeseries",
"type": "timeseries", "type": "timeseries",
"title": "New widget",
"sizeX": 18, "sizeX": 18,
"sizeY": 6, "sizeY": 6,
"config": { "config": {
@ -515,7 +527,16 @@
"axisPosition": "top", "axisPosition": "top",
"showLabels": true "showLabels": true
}, },
"smoothLines": true "smoothLines": true,
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": false
}
}, },
"title": "Humidity", "title": "Humidity",
"dropShadow": true, "dropShadow": true,
@ -533,25 +554,13 @@
"widgetStyle": {}, "widgetStyle": {},
"useDashboardTimewindow": false, "useDashboardTimewindow": false,
"displayTimewindow": true, "displayTimewindow": true,
"showLegend": true,
"legendConfig": {
"direction": "column",
"position": "bottom",
"showMin": true,
"showMax": true,
"showAvg": true,
"showTotal": false
},
"actions": {} "actions": {}
}, },
"id": "07f49fd5-a73b-d74c-c220-362c20af81f4" "id": "07f49fd5-a73b-d74c-c220-362c20af81f4",
"typeFullFqn": "system.charts.basic_timeseries"
}, },
"c4631f94-2db3-523b-4d09-2a1a0a75d93f": { "c4631f94-2db3-523b-4d09-2a1a0a75d93f": {
"isSystemType": true,
"bundleAlias": "input_widgets",
"typeAlias": "update_multiple_attributes",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 6, "sizeX": 6,
"sizeY": 6, "sizeY": 6,
"config": { "config": {
@ -626,8 +635,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085177686,
"endTimeMs": 1694171577686
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": true, "showTitle": true,
@ -661,14 +689,11 @@
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "c4631f94-2db3-523b-4d09-2a1a0a75d93f" "id": "c4631f94-2db3-523b-4d09-2a1a0a75d93f",
"typeFullFqn": "system.input_widgets.update_multiple_attributes"
}, },
"3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": { "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
"isSystemType": true,
"bundleAlias": "maps_v2",
"typeAlias": "openstreetmap",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 13, "sizeX": 13,
"sizeY": 6, "sizeY": 6,
"config": { "config": {
@ -725,8 +750,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085177686,
"endTimeMs": 1694171577686
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -802,14 +846,11 @@
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb" "id": "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb",
"typeFullFqn": "system.maps_v2.openstreetmap"
}, },
"00fb2742-ba1f-7e43-673f-d6c08b72ed06": { "00fb2742-ba1f-7e43-673f-d6c08b72ed06": {
"isSystemType": true,
"bundleAlias": "input_widgets",
"typeAlias": "markers_placement_openstreetmap",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 24, "sizeX": 24,
"sizeY": 12, "sizeY": 12,
"config": { "config": {
@ -864,8 +905,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085177686,
"endTimeMs": 1694171577686
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -937,14 +997,11 @@
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "00fb2742-ba1f-7e43-673f-d6c08b72ed06" "id": "00fb2742-ba1f-7e43-673f-d6c08b72ed06",
"typeFullFqn": "system.input_widgets.markers_placement_openstreetmap"
}, },
"0a430429-9078-9ae6-2b67-e4a15a2bf8bf": { "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
"isSystemType": true,
"bundleAlias": "input_widgets",
"typeAlias": "markers_placement_openstreetmap",
"type": "latest", "type": "latest",
"title": "New widget",
"sizeX": 6, "sizeX": 6,
"sizeY": 6, "sizeY": 6,
"config": { "config": {
@ -999,8 +1056,27 @@
} }
], ],
"timewindow": { "timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": { "realtime": {
"timewindowMs": 60000 "realtimeType": 1,
"interval": 1000,
"timewindowMs": 60000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1694085177686,
"endTimeMs": 1694171577686
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "AVG",
"limit": 25000
} }
}, },
"showTitle": false, "showTitle": false,
@ -1073,7 +1149,8 @@
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true
}, },
"id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf",
"typeFullFqn": "system.input_widgets.markers_placement_openstreetmap"
} }
}, },
"states": { "states": {
@ -1110,7 +1187,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70, "mobileRowHeight": 70,
"margin": 10 "margin": 10,
"outerMargin": true
} }
} }
} }
@ -1136,7 +1214,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70, "mobileRowHeight": 70,
"margin": 10 "margin": 10,
"outerMargin": true
} }
} }
} }
@ -1182,7 +1261,8 @@
"autoFillHeight": true, "autoFillHeight": true,
"mobileAutoFillHeight": false, "mobileAutoFillHeight": false,
"mobileRowHeight": 70, "mobileRowHeight": 70,
"margin": 10 "margin": 10,
"outerMargin": true
} }
} }
} }
@ -1195,8 +1275,10 @@
"filter": { "filter": {
"type": "deviceType", "type": "deviceType",
"resolveMultiple": true, "resolveMultiple": true,
"deviceType": "thermostat", "deviceNameFilter": "",
"deviceNameFilter": "" "deviceTypes": [
"thermostat"
]
} }
}, },
"12ae98c7-1ea2-52cf-64d5-763e9d993547": { "12ae98c7-1ea2-52cf-64d5-763e9d993547": {
@ -1245,5 +1327,6 @@
}, },
"filters": {} "filters": {}
}, },
"externalId": null,
"name": "Thermostats" "name": "Thermostats"
} }

2
application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json

@ -48,8 +48,8 @@
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugMode": false, "debugMode": false,
"configurationVersion": 1,
"configuration": { "configuration": {
"version": 1,
"scope": "CLIENT_SCOPE", "scope": "CLIENT_SCOPE",
"notifyDevice": "false", "notifyDevice": "false",
"sendAttributesUpdatedNotification": "false", "sendAttributesUpdatedNotification": "false",

2
application/src/main/data/json/system/widget_types/compass.json

@ -15,7 +15,7 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-analogue-compass-widget-settings", "settingsDirective": "tb-analogue-compass-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"minorTicks\":22,\"needleCircleSize\":15,\"showBorder\":true,\"borderOuterWidth\":10,\"colorPlate\":\"#222\",\"colorMajorTicks\":\"#f5f5f5\",\"colorMinorTicks\":\"#ddd\",\"colorNeedle\":\"#f08080\",\"colorNeedleCircle\":\"#e8e8e8\",\"colorBorder\":\"#ccc\",\"majorTickFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ccc\"},\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"animationTarget\":\"needle\",\"majorTicks\":[\"N\",\"NE\",\"E\",\"SE\",\"S\",\"SW\",\"W\",\"NW\"]},\"title\":\"Compass\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"minorTicks\":22,\"needleCircleSize\":15,\"showBorder\":true,\"borderOuterWidth\":10,\"colorPlate\":\"#222\",\"colorMajorTicks\":\"#f5f5f5\",\"colorMinorTicks\":\"#ddd\",\"colorNeedle\":\"#f08080\",\"colorNeedleCircle\":\"#e8e8e8\",\"colorBorder\":\"#ccc\",\"majorTickFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ccc\"},\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"animationTarget\":\"needle\",\"majorTicks\":[\"N\",\"NE\",\"E\",\"SE\",\"S\",\"SW\",\"W\",\"NW\"]},\"title\":\"Compass\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"decimals\":0,\"noDataDisplayMessage\":\"\"}"
}, },
"externalId": null "externalId": null
} }

2
application/src/main/data/json/system/widget_types/radial_gauge.json

@ -15,7 +15,7 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-analogue-radial-gauge-widget-settings", "settingsDirective": "tb-analogue-radial-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"Roboto\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"Roboto\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"decimals\":0,\"noDataDisplayMessage\":\"\"}"
}, },
"externalId": null "externalId": null
} }

2
application/src/main/data/json/system/widget_types/speed_gauge.json

@ -15,7 +15,7 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-analogue-radial-gauge-widget-settings", "settingsDirective": "tb-analogue-radial-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 220) {\\n\\tvalue = 220;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":180,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":false,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":80,\"to\":120,\"color\":\"#fdd835\"},{\"color\":\"#e57373\",\"from\":120,\"to\":180}],\"showUnitTitle\":false,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"minValue\":0,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"MPH\",\"majorTicksCount\":9,\"numbersFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"size\":32,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\",\"family\":\"Segment7Standard\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Speed gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 220) {\\n\\tvalue = 220;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":180,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":false,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":80,\"to\":120,\"color\":\"#fdd835\"},{\"color\":\"#e57373\",\"from\":120,\"to\":180}],\"showUnitTitle\":false,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"minValue\":0,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"MPH\",\"majorTicksCount\":9,\"numbersFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"size\":32,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\",\"family\":\"Segment7Standard\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Speed gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"decimals\":0,\"noDataDisplayMessage\":\"\"}"
}, },
"externalId": null "externalId": null
} }

2
application/src/main/data/json/system/widget_types/temperature_radial_gauge.json

@ -15,7 +15,7 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-analogue-radial-gauge-widget-settings", "settingsDirective": "tb-analogue-radial-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":60,\"startAngle\":67.5,\"ticksAngle\":225,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":-60,\"to\":-50,\"color\":\"#42a5f5\"},{\"from\":-50,\"to\":-40,\"color\":\"rgba(66, 165, 245, 0.83)\"},{\"from\":-40,\"to\":-30,\"color\":\"rgba(66, 165, 245, 0.66)\"},{\"from\":-30,\"to\":-20,\"color\":\"rgba(66, 165, 245, 0.5)\"},{\"from\":-20,\"to\":-10,\"color\":\"rgba(66, 165, 245, 0.33)\"},{\"from\":-10,\"to\":0,\"color\":\"rgba(66, 165, 245, 0.16)\"},{\"from\":0,\"to\":10,\"color\":\"rgba(229, 115, 115, 0.16)\"},{\"from\":10,\"to\":20,\"color\":\"rgba(229, 115, 115, 0.33)\"},{\"from\":20,\"to\":30,\"color\":\"rgba(229, 115, 115, 0.5)\"},{\"from\":30,\"to\":40,\"color\":\"rgba(229, 115, 115, 0.66)\"},{\"from\":40,\"to\":50,\"color\":\"rgba(229, 115, 115, 0.83)\"},{\"from\":50,\"to\":60,\"color\":\"#e57373\"}],\"showUnitTitle\":true,\"colorPlate\":\"#cfd8dc\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"valueDec\":1,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1000,\"animationRule\":\"bounce\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"°C\",\"majorTicksCount\":12,\"numbersFont\":{\"family\":\"Roboto\",\"size\":20,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":30,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"unitTitle\":\"Temperature\",\"minValue\":-60},\"title\":\"Temperature radial gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":60,\"startAngle\":67.5,\"ticksAngle\":225,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":-60,\"to\":-50,\"color\":\"#42a5f5\"},{\"from\":-50,\"to\":-40,\"color\":\"rgba(66, 165, 245, 0.83)\"},{\"from\":-40,\"to\":-30,\"color\":\"rgba(66, 165, 245, 0.66)\"},{\"from\":-30,\"to\":-20,\"color\":\"rgba(66, 165, 245, 0.5)\"},{\"from\":-20,\"to\":-10,\"color\":\"rgba(66, 165, 245, 0.33)\"},{\"from\":-10,\"to\":0,\"color\":\"rgba(66, 165, 245, 0.16)\"},{\"from\":0,\"to\":10,\"color\":\"rgba(229, 115, 115, 0.16)\"},{\"from\":10,\"to\":20,\"color\":\"rgba(229, 115, 115, 0.33)\"},{\"from\":20,\"to\":30,\"color\":\"rgba(229, 115, 115, 0.5)\"},{\"from\":30,\"to\":40,\"color\":\"rgba(229, 115, 115, 0.66)\"},{\"from\":40,\"to\":50,\"color\":\"rgba(229, 115, 115, 0.83)\"},{\"from\":50,\"to\":60,\"color\":\"#e57373\"}],\"showUnitTitle\":true,\"colorPlate\":\"#cfd8dc\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"valueDec\":1,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1000,\"animationRule\":\"bounce\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"°C\",\"majorTicksCount\":12,\"numbersFont\":{\"family\":\"Roboto\",\"size\":20,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":30,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"unitTitle\":\"Temperature\",\"minValue\":-60},\"title\":\"Temperature radial gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"decimals\":0,\"noDataDisplayMessage\":\"\"}"
}, },
"externalId": null "externalId": null
} }

2
application/src/main/data/json/system/widget_types/thermometer_scale.json

@ -15,7 +15,7 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-analogue-linear-gauge-widget-settings", "settingsDirective": "tb-analogue-linear-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 30 - 15;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"defaultColor\":\"#e64a19\",\"barStrokeWidth\":2.5,\"colorBar\":\"rgba(255, 255, 255, 0.4)\",\"colorBarEnd\":\"rgba(221, 221, 221, 0.38)\",\"showUnitTitle\":true,\"minorTicks\":2,\"valueBox\":true,\"valueInt\":3,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"colorNeedleShadowUp\":\"rgba(2,255,255,0.2)\",\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"highlightsWidth\":10,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"showBorder\":false,\"majorTicksCount\":8,\"numbersFont\":{\"family\":\"Arial\",\"size\":18,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#78909c\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":26,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#37474f\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":40,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#444\",\"shadowColor\":\"rgba(0,0,0,0.3)\"},\"minValue\":-60,\"highlights\":[{\"from\":-60,\"to\":-40,\"color\":\"#90caf9\"},{\"from\":-40,\"to\":-20,\"color\":\"rgba(144, 202, 249, 0.66)\"},{\"from\":-20,\"to\":0,\"color\":\"rgba(144, 202, 249, 0.33)\"},{\"from\":0,\"to\":20,\"color\":\"rgba(244, 67, 54, 0.2)\"},{\"from\":20,\"to\":40,\"color\":\"rgba(244, 67, 54, 0.4)\"},{\"from\":40,\"to\":60,\"color\":\"rgba(244, 67, 54, 0.6)\"},{\"from\":60,\"to\":80,\"color\":\"rgba(244, 67, 54, 0.8)\"},{\"from\":80,\"to\":100,\"color\":\"#f44336\"}],\"unitTitle\":\"Temperature\",\"units\":\"°C\",\"colorBarProgress\":\"#90caf9\",\"colorBarProgressEnd\":\"#f44336\",\"colorBarStroke\":\"#b0bec5\",\"valueDec\":1},\"title\":\"Thermometer scale\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 30 - 15;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"defaultColor\":\"#e64a19\",\"barStrokeWidth\":2.5,\"colorBar\":\"rgba(255, 255, 255, 0.4)\",\"colorBarEnd\":\"rgba(221, 221, 221, 0.38)\",\"showUnitTitle\":true,\"minorTicks\":2,\"valueBox\":true,\"valueInt\":3,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"colorNeedleShadowUp\":\"rgba(2,255,255,0.2)\",\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"highlightsWidth\":10,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"showBorder\":false,\"majorTicksCount\":8,\"numbersFont\":{\"family\":\"Arial\",\"size\":18,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#78909c\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":26,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#37474f\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":40,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#444\",\"shadowColor\":\"rgba(0,0,0,0.3)\"},\"minValue\":-60,\"highlights\":[{\"from\":-60,\"to\":-40,\"color\":\"#90caf9\"},{\"from\":-40,\"to\":-20,\"color\":\"rgba(144, 202, 249, 0.66)\"},{\"from\":-20,\"to\":0,\"color\":\"rgba(144, 202, 249, 0.33)\"},{\"from\":0,\"to\":20,\"color\":\"rgba(244, 67, 54, 0.2)\"},{\"from\":20,\"to\":40,\"color\":\"rgba(244, 67, 54, 0.4)\"},{\"from\":40,\"to\":60,\"color\":\"rgba(244, 67, 54, 0.6)\"},{\"from\":60,\"to\":80,\"color\":\"rgba(244, 67, 54, 0.8)\"},{\"from\":80,\"to\":100,\"color\":\"#f44336\"}],\"unitTitle\":\"Temperature\",\"units\":\"°C\",\"colorBarProgress\":\"#90caf9\",\"colorBarProgressEnd\":\"#f44336\",\"colorBarStroke\":\"#b0bec5\",\"valueDec\":1},\"title\":\"Thermometer scale\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"decimals\":0,\"noDataDisplayMessage\":\"\"}"
}, },
"externalId": null "externalId": null
} }

2
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -32,8 +32,8 @@
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugMode": false, "debugMode": false,
"configurationVersion": 1,
"configuration": { "configuration": {
"version": 1,
"scope": "CLIENT_SCOPE", "scope": "CLIENT_SCOPE",
"notifyDevice": "false", "notifyDevice": "false",
"sendAttributesUpdatedNotification": "false", "sendAttributesUpdatedNotification": "false",

2
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -31,8 +31,8 @@
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugMode": false, "debugMode": false,
"configurationVersion": 1,
"configuration": { "configuration": {
"version": 1,
"scope": "CLIENT_SCOPE", "scope": "CLIENT_SCOPE",
"notifyDevice": "false", "notifyDevice": "false",
"sendAttributesUpdatedNotification": "false", "sendAttributesUpdatedNotification": "false",

2
application/src/main/data/upgrade/3.5.1/schema_update.sql

@ -127,6 +127,8 @@ UPDATE resource
ALTER TABLE notification_request ALTER COLUMN info SET DATA TYPE varchar(1000000); ALTER TABLE notification_request ALTER COLUMN info SET DATA TYPE varchar(1000000);
DELETE FROM alarm WHERE tenant_id NOT IN (SELECT id FROM tenant);
CREATE TABLE IF NOT EXISTS alarm_types ( CREATE TABLE IF NOT EXISTS alarm_types (
tenant_id uuid NOT NULL, tenant_id uuid NOT NULL,
type varchar(255) NOT NULL, type varchar(255) NOT NULL,

17
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -30,13 +30,12 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.NodeDefinition; import org.thingsboard.rule.engine.api.NodeDefinition;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.rule.engine.api.TbVersionedNode;
import org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode; import org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode;
import org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode; import org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
@ -89,15 +88,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
try { try {
var clazz = Class.forName(clazzName); var clazz = Class.forName(clazzName);
RuleNode annotation = clazz.getAnnotation(RuleNode.class); RuleNode annotation = clazz.getAnnotation(RuleNode.class);
boolean versioned = false; ruleNodeClasses.put(clazzName, new RuleNodeClassInfo(clazz, annotation));
if (annotation.version() > 0) { // No need to process nodes that has version = 0;
if (TbVersionedNode.class.isAssignableFrom(clazz)) {
versioned = true;
} else {
log.error("RuleNode [{}] has version {} but does not implement TbVersionedNode interface! Any update procedures for this rule node will be skipped!", clazzName, annotation.version());
}
}
ruleNodeClasses.put(clazzName, new RuleNodeClassInfo(clazz, annotation, versioned));
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to create instance of rule node type: {} due to: ", clazzName, e); log.warn("Failed to create instance of rule node type: {} due to: ", clazzName, e);
} }
@ -194,7 +185,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
scannedComponent.setType(type); scannedComponent.setType(type);
Class<?> clazz = def.getClazz(); Class<?> clazz = def.getClazz();
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setConfigurationVersion(def.isVersioned() ? def.getCurrentVersion() : 0); scannedComponent.setConfigurationVersion(def.getCurrentVersion());
scannedComponent.setName(ruleNodeAnnotation.name()); scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope()); scannedComponent.setScope(ruleNodeAnnotation.scope());
scannedComponent.setClusteringMode(ruleNodeAnnotation.clusteringMode()); scannedComponent.setClusteringMode(ruleNodeAnnotation.clusteringMode());

5
application/src/main/java/org/thingsboard/server/service/component/RuleNodeClassInfo.java

@ -23,7 +23,6 @@ public class RuleNodeClassInfo {
private final Class<?> clazz; private final Class<?> clazz;
private final RuleNode annotation; private final RuleNode annotation;
private final boolean versioned;
public String getClassName(){ public String getClassName(){
return clazz.getName(); return clazz.getName();
@ -37,4 +36,8 @@ public class RuleNodeClassInfo {
return annotation.version(); return annotation.version();
} }
public boolean isVersioned() {
return annotation.version() > 0;
}
} }

49
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -33,7 +33,10 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
@ -45,6 +48,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
@ -76,18 +80,18 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
private final Lock findOrCreateDeviceProfileLock = new ReentrantLock(); private final Lock findOrCreateDeviceProfileLock = new ReentrantLock();
@Override @Override
protected void setEntityFields(Device entity, Map<BulkImportColumnType, String> fields) { protected void setEntityFields(Device device, Map<BulkImportColumnType, String> fields) {
ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(entity); ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(device);
fields.forEach((columnType, value) -> { fields.forEach((columnType, value) -> {
switch (columnType) { switch (columnType) {
case NAME: case NAME:
entity.setName(value); device.setName(value);
break; break;
case TYPE: case TYPE:
entity.setType(value); device.setType(value);
break; break;
case LABEL: case LABEL:
entity.setLabel(value); device.setLabel(value);
break; break;
case DESCRIPTION: case DESCRIPTION:
additionalInfo.set("description", new TextNode(value)); additionalInfo.set("description", new TextNode(value));
@ -96,16 +100,17 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value))); additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value)));
break; break;
} }
entity.setAdditionalInfo(additionalInfo); device.setAdditionalInfo(additionalInfo);
}); });
setUpDeviceConfiguration(device, fields);
} }
@Override @Override
@SneakyThrows @SneakyThrows
protected Device saveEntity(SecurityUser user, Device entity, Map<BulkImportColumnType, String> fields) { protected Device saveEntity(SecurityUser user, Device device, Map<BulkImportColumnType, String> fields) {
DeviceCredentials deviceCredentials; DeviceCredentials deviceCredentials;
try { try {
deviceCredentials = createDeviceCredentials(entity.getTenantId(), entity.getId(), fields); deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), fields);
deviceCredentialsService.formatCredentials(deviceCredentials); deviceCredentialsService.formatCredentials(deviceCredentials);
} catch (Exception e) { } catch (Exception e) {
throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage()); throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage());
@ -113,15 +118,15 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
DeviceProfile deviceProfile; DeviceProfile deviceProfile;
if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
deviceProfile = setUpLwM2mDeviceProfile(entity.getTenantId(), entity); deviceProfile = setUpLwM2mDeviceProfile(device.getTenantId(), device);
} else if (StringUtils.isNotEmpty(entity.getType())) { } else if (StringUtils.isNotEmpty(device.getType())) {
deviceProfile = deviceProfileService.findOrCreateDeviceProfile(entity.getTenantId(), entity.getType()); deviceProfile = deviceProfileService.findOrCreateDeviceProfile(device.getTenantId(), device.getType());
} else { } else {
deviceProfile = deviceProfileService.findDefaultDeviceProfile(entity.getTenantId()); deviceProfile = deviceProfileService.findDefaultDeviceProfile(device.getTenantId());
} }
entity.setDeviceProfileId(deviceProfile.getId()); device.setDeviceProfileId(deviceProfile.getId());
return tbDeviceService.saveDeviceWithCredentials(entity, deviceCredentials, user); return tbDeviceService.saveDeviceWithCredentials(device, deviceCredentials, user);
} }
@Override @Override
@ -136,6 +141,22 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
entity.setCustomerId(user.getCustomerId()); entity.setCustomerId(user.getCustomerId());
} }
private void setUpDeviceConfiguration(Device device, Map<BulkImportColumnType, String> fields) {
if (fields.containsKey(BulkImportColumnType.SNMP_HOST)) {
SnmpDeviceTransportConfiguration transportConfiguration = new SnmpDeviceTransportConfiguration();
transportConfiguration.setHost(fields.get(BulkImportColumnType.SNMP_HOST));
transportConfiguration.setPort(Optional.ofNullable(fields.get(BulkImportColumnType.SNMP_PORT))
.map(Integer::parseInt).orElse(161));
transportConfiguration.setProtocolVersion(Optional.ofNullable(fields.get(BulkImportColumnType.SNMP_VERSION))
.map(version -> SnmpProtocolVersion.valueOf(version.toUpperCase())).orElse(SnmpProtocolVersion.V2C));
transportConfiguration.setCommunity(fields.getOrDefault(BulkImportColumnType.SNMP_COMMUNITY_STRING, "public"));
DeviceData deviceData = new DeviceData();
deviceData.setTransportConfiguration(transportConfiguration);
device.setDeviceData(deviceData);
}
}
@SneakyThrows @SneakyThrows
private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, Map<BulkImportColumnType, String> fields) { private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, Map<BulkImportColumnType, String> fields) {
DeviceCredentials credentials = new DeviceCredentials(); DeviceCredentials credentials = new DeviceCredentials();

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -614,13 +614,13 @@ public final class EdgeGrpcSession implements Closeable {
case EDGE: case EDGE:
return ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent); return ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent);
case DEVICE: case DEVICE:
return ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent); return ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion);
case DEVICE_PROFILE: case DEVICE_PROFILE:
return ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent); return ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion);
case ASSET_PROFILE: case ASSET_PROFILE:
return ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent); return ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion);
case ASSET: case ASSET:
return ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent); return ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion);
case ENTITY_VIEW: case ENTITY_VIEW:
return ctx.getEntityViewProcessor().convertEntityViewEventToDownlink(edgeEvent); return ctx.getEntityViewProcessor().convertEntityViewEventToDownlink(edgeEvent);
case DASHBOARD: case DASHBOARD:

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java

@ -35,6 +35,13 @@ public class WidgetTypeMsgConstructor {
.setIdLSB(widgetTypeDetails.getId().getId().getLeastSignificantBits()); .setIdLSB(widgetTypeDetails.getId().getId().getLeastSignificantBits());
if (widgetTypeDetails.getFqn() != null) { if (widgetTypeDetails.getFqn() != null) {
builder.setFqn(widgetTypeDetails.getFqn()); builder.setFqn(widgetTypeDetails.getFqn());
if (widgetTypeDetails.getFqn().contains(".")) {
String[] aliases = widgetTypeDetails.getFqn().split("\\.", 2);
if (aliases.length == 2) {
builder.setBundleAlias(aliases[0]);
builder.setAlias(aliases[1]);
}
}
} }
if (widgetTypeDetails.getName() != null) { if (widgetTypeDetails.getName() != null) {
builder.setName(widgetTypeDetails.getName()); builder.setName(widgetTypeDetails.getName());

54
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -79,6 +79,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
@ -596,4 +597,57 @@ public abstract class BaseEdgeProcessor {
} }
}); });
} }
protected AssetProfile checkIfAssetProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, AssetProfile assetProfile, EdgeVersion edgeVersion) {
switch (edgeVersion) {
case V_3_3_3:
case V_3_3_0:
case V_3_4_0:
if (assetProfile.getDefaultDashboardId() != null
&& isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultDashboardId(), edgeId)) {
assetProfile.setDefaultDashboardId(null);
}
if (assetProfile.getDefaultEdgeRuleChainId() != null
&& isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultEdgeRuleChainId(), edgeId)) {
assetProfile.setDefaultEdgeRuleChainId(null);
}
break;
}
return assetProfile;
}
protected DeviceProfile checkIfDeviceProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, DeviceProfile deviceProfile, EdgeVersion edgeVersion) {
switch (edgeVersion) {
case V_3_3_3:
case V_3_3_0:
case V_3_4_0:
if (deviceProfile.getDefaultDashboardId() != null
&& isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultDashboardId(), edgeId)) {
deviceProfile.setDefaultDashboardId(null);
}
if (deviceProfile.getDefaultEdgeRuleChainId() != null
&& isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultEdgeRuleChainId(), edgeId)) {
deviceProfile.setDefaultEdgeRuleChainId(null);
}
break;
}
return deviceProfile;
}
private boolean isEntityNotAssignedToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
if (pageData.getData().contains(edgeId)) {
return false;
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
return true;
}
} }

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -37,6 +38,7 @@ import org.thingsboard.server.dao.asset.BaseAssetService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -106,7 +108,7 @@ public class AssetEdgeProcessor extends BaseAssetProcessor {
} }
} }
public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) {
AssetId assetId = new AssetId(edgeEvent.getEntityId()); AssetId assetId = new AssetId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
@ -125,6 +127,7 @@ public class AssetEdgeProcessor extends BaseAssetProcessor {
.addAssetUpdateMsg(assetUpdateMsg); .addAssetUpdateMsg(assetUpdateMsg);
if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), asset.getAssetProfileId()); AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), asset.getAssetProfileId());
assetProfile = checkIfAssetProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, assetProfile, edgeVersion);
builder.addAssetProfileUpdateMsg(assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile)); builder.addAssetProfileUpdateMsg(assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile));
} }
downlinkMsg = builder.build(); downlinkMsg = builder.build();

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
@ -36,6 +37,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -94,7 +96,7 @@ public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor {
} }
} }
public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) {
AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId()); AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
@ -103,6 +105,7 @@ public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor {
AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), assetProfileId); AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), assetProfileId);
if (assetProfile != null) { if (assetProfile != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
assetProfile = checkIfAssetProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, assetProfile, edgeVersion);
AssetProfileUpdateMsg assetProfileUpdateMsg = AssetProfileUpdateMsg assetProfileUpdateMsg =
assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile); assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
@ -49,6 +50,7 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
@ -202,7 +204,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) {
DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
@ -221,6 +223,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor {
.addDeviceUpdateMsg(deviceUpdateMsg); .addDeviceUpdateMsg(deviceUpdateMsg);
if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId()); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId());
deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion);
builder.addDeviceProfileUpdateMsg(deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile)); builder.addDeviceProfileUpdateMsg(deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile));
} }
downlinkMsg = builder.build(); downlinkMsg = builder.build();

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
@ -36,6 +37,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -94,7 +96,7 @@ public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor {
} }
} }
public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) {
DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId()); DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
@ -103,6 +105,7 @@ public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId);
if (deviceProfile != null) { if (deviceProfile != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion);
DeviceProfileUpdateMsg deviceProfileUpdateMsg = DeviceProfileUpdateMsg deviceProfileUpdateMsg =
deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile); deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()

21
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@ -104,15 +103,15 @@ public class InstallScripts {
@Autowired @Autowired
private ResourceService resourceService; private ResourceService resourceService;
private Path getTenantRuleChainsDir() { Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
} }
private Path getDeviceProfileDefaultRuleChainTemplateFilePath() { Path getDeviceProfileDefaultRuleChainTemplateFilePath() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json"); return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
} }
private Path getEdgeRuleChainsDir() { Path getEdgeRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR); return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR);
} }
@ -148,8 +147,8 @@ public class InstallScripts {
} }
private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException { private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(ruleChainsPath, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) { findRuleChainsFromPath(ruleChainsPath)
dirStream.forEach( .forEach(
path -> { path -> {
try { try {
createRuleChainFromFile(tenantId, path, null); createRuleChainFromFile(tenantId, path, null);
@ -157,9 +156,15 @@ public class InstallScripts {
log.error("Unable to load rule chain from json: [{}]", path.toString()); log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e); throw new RuntimeException("Unable to load rule chain from json", e);
} }
} });
); }
List<Path> findRuleChainsFromPath(Path ruleChainsPath) throws IOException {
List<Path> paths = new ArrayList<>();
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(ruleChainsPath, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) {
dirStream.forEach(paths::add);
} }
return paths;
} }
public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException { public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException {

13
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -731,8 +731,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
break; break;
case "3.5.1": case "3.5.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3005001)) { if (isOldSchema(conn, 3005001)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn); loadSql(schemaUpdateFile, conn);
@ -756,14 +756,23 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_rule_node_type_configuration_version ON rule_node(type, configuration_version);"); conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_rule_node_type_configuration_version ON rule_node(type, configuration_version);");
} catch (Exception e) { } catch (Exception e) {
} }
try {
conn.createStatement().execute("UPDATE rule_node SET " +
"configuration = (configuration::jsonb || '{\"updateAttributesOnlyOnValueChange\": \"false\"}'::jsonb)::varchar, " +
"configuration_version = 1 " +
"WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version < 1;");
} catch (Exception e) {
}
try { try {
conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';"); conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';");
} catch (Exception e) { } catch (Exception e) {
} }
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005002;"); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005002;");
log.info("Schema updated to version 3.5.2.");
} else {
log.info("Skip schema re-update to version 3.5.2. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update.");
} }
log.info("Schema updated.");
} catch (Exception e) { } catch (Exception e) {
log.error("Failed updating schema!!!", e); log.error("Failed updating schema!!!", e);
} }

48
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -27,7 +27,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbVersionedNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
@ -78,6 +78,7 @@ import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.JpaExecutorService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -88,9 +89,7 @@ import org.thingsboard.server.service.install.SystemDataLoaderService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function; import java.util.function.Function;
@ -103,6 +102,8 @@ import static org.thingsboard.server.common.data.StringUtils.isBlank;
@Slf4j @Slf4j
public class DefaultDataUpdateService implements DataUpdateService { public class DefaultDataUpdateService implements DataUpdateService {
private static final int MAX_PENDING_SAVE_RULE_NODE_FUTURES = 100;
@Autowired @Autowired
private TenantService tenantService; private TenantService tenantService;
@ -155,6 +156,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private EdgeEventDao edgeEventDao; private EdgeEventDao edgeEventDao;
@Autowired
JpaExecutorService jpaExecutorService;
@Override @Override
public void updateData(String fromVersion) throws Exception { public void updateData(String fromVersion) throws Exception {
switch (fromVersion) { switch (fromVersion) {
@ -227,13 +231,15 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Override @Override
public void upgradeRuleNodes() { public void upgradeRuleNodes() {
try { try {
var futures = new ArrayList<ListenableFuture<?>>(100);
int totalRuleNodesUpgraded = 0;
log.info("Starting rule nodes upgrade ..."); log.info("Starting rule nodes upgrade ...");
var nodeClassToVersionMap = componentDiscoveryService.getVersionedNodes(); var nodeClassToVersionMap = componentDiscoveryService.getVersionedNodes();
log.debug("Found {} versioned nodes to check for upgrade!", nodeClassToVersionMap.size()); log.debug("Found {} versioned nodes to check for upgrade!", nodeClassToVersionMap.size());
nodeClassToVersionMap.forEach(clazz -> { for (var ruleNodeClassInfo : nodeClassToVersionMap) {
var ruleNodeType = clazz.getClassName(); var ruleNodeType = ruleNodeClassInfo.getClassName();
var ruleNodeTypeForLogs = clazz.getSimpleName(); var ruleNodeTypeForLogs = ruleNodeClassInfo.getSimpleName();
var toVersion = clazz.getCurrentVersion(); var toVersion = ruleNodeClassInfo.getCurrentVersion();
log.debug("Going to check for nodes with type: {} to upgrade to version: {}.", ruleNodeTypeForLogs, toVersion); log.debug("Going to check for nodes with type: {} to upgrade to version: {}.", ruleNodeTypeForLogs, toVersion);
var ruleNodesToUpdate = new PageDataIterable<>( var ruleNodesToUpdate = new PageDataIterable<>(
pageLink -> ruleChainService.findAllRuleNodesByTypeAndVersionLessThan(ruleNodeType, toVersion, pageLink), 1024 pageLink -> ruleChainService.findAllRuleNodesByTypeAndVersionLessThan(ruleNodeType, toVersion, pageLink), 1024
@ -248,28 +254,44 @@ public class DefaultDataUpdateService implements DataUpdateService {
log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion); ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion);
try { try {
var tbVersionedNode = (TbVersionedNode) clazz.getClazz().getDeclaredConstructor().newInstance(); var tbVersionedNode = (TbNode) ruleNodeClassInfo.getClazz().getDeclaredConstructor().newInstance();
TbPair<Boolean, JsonNode> upgradeRuleNodeConfigurationResult = tbVersionedNode.upgrade(fromVersion, oldConfiguration); TbPair<Boolean, JsonNode> upgradeRuleNodeConfigurationResult = tbVersionedNode.upgrade(fromVersion, oldConfiguration);
if (upgradeRuleNodeConfigurationResult.getFirst()) { if (upgradeRuleNodeConfigurationResult.getFirst()) {
ruleNode.setConfiguration(upgradeRuleNodeConfigurationResult.getSecond()); ruleNode.setConfiguration(upgradeRuleNodeConfigurationResult.getSecond());
} }
ruleNode.setConfigurationVersion(toVersion); ruleNode.setConfigurationVersion(toVersion);
ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode); futures.add(jpaExecutorService.submit(() -> {
log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode);
ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion); log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion);
}));
if (futures.size() >= MAX_PENDING_SAVE_RULE_NODE_FUTURES) {
log.info("{} upgraded rule nodes so far ...",
totalRuleNodesUpgraded += awaitFuturesToCompleteAndGetCount(futures));
futures.clear();
}
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {} due to: ", log.warn("Failed to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {} due to: ",
ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion, e); ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion, e);
} }
} }
} }
}); }
log.info("Finished rule nodes upgrade!"); log.info("Finished rule nodes upgrade. Upgraded rule nodes count: {}",
totalRuleNodesUpgraded + awaitFuturesToCompleteAndGetCount(futures));
} catch (Exception e) { } catch (Exception e) {
log.error("Unexpected error during rule nodes upgrade: ", e); log.error("Unexpected error during rule nodes upgrade: ", e);
} }
} }
private int awaitFuturesToCompleteAndGetCount(List<ListenableFuture<?>> futures) {
try {
return Futures.allAsList(futures).get().size();
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Failed to process save rule nodes requests due to: ", e);
}
}
private final PaginatedUpdater<String, DeviceProfileEntity> deviceProfileEntityDynamicConditionsUpdater = private final PaginatedUpdater<String, DeviceProfileEntity> deviceProfileEntityDynamicConditionsUpdater =
new PaginatedUpdater<>() { new PaginatedUpdater<>() {

68
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -24,10 +24,14 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.event.ErrorEvent;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.event.LifecycleEvent;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -45,7 +49,9 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ErrorEventProto;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.LifecycleEventProto;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto;
@ -274,6 +280,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg(); TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg();
log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg()); log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg());
forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback); forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback);
} else if (toCoreMsg.hasErrorEventMsg()) {
forwardToEventService(toCoreMsg.getErrorEventMsg(), callback);
} else if (toCoreMsg.hasLifecycleEventMsg()) {
forwardToEventService(toCoreMsg.getLifecycleEventMsg(), callback);
} }
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e); log.warn("[{}] Failed to process message: {}", id, msg, e);
@ -519,50 +529,52 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback); subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback);
} else if (msg.hasTsUpdate()) { } else if (msg.hasTsUpdate()) {
TbTimeSeriesUpdateProto proto = msg.getTsUpdate(); TbTimeSeriesUpdateProto proto = msg.getTsUpdate();
long tenantIdMSB = proto.getTenantIdMSB();
long tenantIdLSB = proto.getTenantIdLSB();
subscriptionManagerService.onTimeSeriesUpdate( subscriptionManagerService.onTimeSeriesUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(tenantIdMSB, tenantIdLSB),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback); TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback);
} else if (msg.hasAttrUpdate()) { } else if (msg.hasAttrUpdate()) {
TbAttributeUpdateProto proto = msg.getAttrUpdate(); TbAttributeUpdateProto proto = msg.getAttrUpdate();
subscriptionManagerService.onAttributesUpdate( subscriptionManagerService.onAttributesUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback); proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback);
} else if (msg.hasAttrDelete()) { } else if (msg.hasAttrDelete()) {
TbAttributeDeleteProto proto = msg.getAttrDelete(); TbAttributeDeleteProto proto = msg.getAttrDelete();
subscriptionManagerService.onAttributesDelete( subscriptionManagerService.onAttributesDelete(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), proto.getKeysList(), proto.getNotifyDevice(), callback); proto.getScope(), proto.getKeysList(), proto.getNotifyDevice(), callback);
} else if (msg.hasTsDelete()) { } else if (msg.hasTsDelete()) {
TbTimeSeriesDeleteProto proto = msg.getTsDelete(); TbTimeSeriesDeleteProto proto = msg.getTsDelete();
subscriptionManagerService.onTimeSeriesDelete( subscriptionManagerService.onTimeSeriesDelete(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getKeysList(), callback); proto.getKeysList(), callback);
} else if (msg.hasAlarmUpdate()) { } else if (msg.hasAlarmUpdate()) {
TbAlarmUpdateProto proto = msg.getAlarmUpdate(); TbAlarmUpdateProto proto = msg.getAlarmUpdate();
subscriptionManagerService.onAlarmUpdate( subscriptionManagerService.onAlarmUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class),
callback); callback);
} else if (msg.hasAlarmDelete()) { } else if (msg.hasAlarmDelete()) {
TbAlarmDeleteProto proto = msg.getAlarmDelete(); TbAlarmDeleteProto proto = msg.getAlarmDelete();
subscriptionManagerService.onAlarmDeleted( subscriptionManagerService.onAlarmDeleted(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), callback); JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), callback);
} else if (msg.hasNotificationUpdate()) { } else if (msg.hasNotificationUpdate()) {
TransportProtos.NotificationUpdateProto updateProto = msg.getNotificationUpdate(); TransportProtos.NotificationUpdateProto updateProto = msg.getNotificationUpdate();
TenantId tenantId = TenantId.fromUUID(new UUID(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB())); TenantId tenantId = toTenantId(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB());
UserId recipientId = new UserId(new UUID(updateProto.getRecipientIdMSB(), updateProto.getRecipientIdLSB())); UserId recipientId = new UserId(new UUID(updateProto.getRecipientIdMSB(), updateProto.getRecipientIdLSB()));
NotificationUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationUpdate.class); NotificationUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationUpdate.class);
subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, callback); subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, callback);
} else if (msg.hasNotificationRequestUpdate()) { } else if (msg.hasNotificationRequestUpdate()) {
TransportProtos.NotificationRequestUpdateProto updateProto = msg.getNotificationRequestUpdate(); TransportProtos.NotificationRequestUpdateProto updateProto = msg.getNotificationRequestUpdate();
TenantId tenantId = TenantId.fromUUID(new UUID(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB())); TenantId tenantId = toTenantId(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB());
NotificationRequestUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationRequestUpdate.class); NotificationRequestUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationRequestUpdate.class);
subscriptionManagerService.onNotificationRequestUpdate(tenantId, update, callback); subscriptionManagerService.onNotificationRequestUpdate(tenantId, update, callback);
} else { } else {
@ -584,7 +596,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
if (statsEnabled) { if (statsEnabled) {
stats.log(deviceActivityMsg); stats.log(deviceActivityMsg);
} }
TenantId tenantId = TenantId.fromUUID(new UUID(deviceActivityMsg.getTenantIdMSB(), deviceActivityMsg.getTenantIdLSB())); TenantId tenantId = toTenantId(deviceActivityMsg.getTenantIdMSB(), deviceActivityMsg.getTenantIdLSB());
DeviceId deviceId = new DeviceId(new UUID(deviceActivityMsg.getDeviceIdMSB(), deviceActivityMsg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(deviceActivityMsg.getDeviceIdMSB(), deviceActivityMsg.getDeviceIdLSB()));
try { try {
stateService.onDeviceActivity(tenantId, deviceId, deviceActivityMsg.getLastActivityTime()); stateService.onDeviceActivity(tenantId, deviceId, deviceActivityMsg.getLastActivityTime());
@ -595,7 +607,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} }
private void forwardToNotificationSchedulerService(TransportProtos.NotificationSchedulerServiceMsg msg, TbCallback callback) { private void forwardToNotificationSchedulerService(TransportProtos.NotificationSchedulerServiceMsg msg, TbCallback callback) {
TenantId tenantId = TenantId.fromUUID(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); TenantId tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
NotificationRequestId notificationRequestId = new NotificationRequestId(new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB())); NotificationRequestId notificationRequestId = new NotificationRequestId(new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB()));
try { try {
notificationSchedulerService.scheduleNotificationRequest(tenantId, notificationRequestId, msg.getTs()); notificationSchedulerService.scheduleNotificationRequest(tenantId, notificationRequestId, msg.getTs());
@ -627,11 +639,47 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
callback.onSuccess(); callback.onSuccess();
} }
private void forwardToEventService(ErrorEventProto eventProto, TbCallback callback) {
Event event = ErrorEvent.builder()
.tenantId(toTenantId(eventProto.getTenantIdMSB(), eventProto.getTenantIdLSB()))
.entityId(new UUID(eventProto.getEntityIdMSB(), eventProto.getEntityIdLSB()))
.serviceId(eventProto.getServiceId())
.ts(System.currentTimeMillis())
.method(eventProto.getMethod())
.error(eventProto.getError())
.build();
forwardToEventService(event, callback);
}
private void forwardToEventService(LifecycleEventProto eventProto, TbCallback callback) {
Event event = LifecycleEvent.builder()
.tenantId(toTenantId(eventProto.getTenantIdMSB(), eventProto.getTenantIdLSB()))
.entityId(new UUID(eventProto.getEntityIdMSB(), eventProto.getEntityIdLSB()))
.serviceId(eventProto.getServiceId())
.ts(System.currentTimeMillis())
.lcEventType(eventProto.getLcEventType())
.success(eventProto.getSuccess())
.error(StringUtils.isNotEmpty(eventProto.getError()) ? eventProto.getError() : null)
.build();
forwardToEventService(event, callback);
}
private void forwardToEventService(Event event, TbCallback callback) {
DonAsynchron.withCallback(actorContext.getEventService().saveAsync(event),
result -> callback.onSuccess(),
callback::onFailure,
actorContext.getDbCallbackExecutor());
}
private void throwNotHandled(Object msg, TbCallback callback) { private void throwNotHandled(Object msg, TbCallback callback) {
log.warn("Message not handled: {}", msg); log.warn("Message not handled: {}", msg);
callback.onFailure(new RuntimeException("Message not handled!")); callback.onFailure(new RuntimeException("Message not handled!"));
} }
private TenantId toTenantId(long tenantIdMSB, long tenantIdLSB) {
return TenantId.fromUUID(new UUID(tenantIdMSB, tenantIdLSB));
}
@Override @Override
protected void stopMainConsumers() { protected void stopMainConsumers() {
if (mainConsumer != null) { if (mainConsumer != null) {

4
application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java

@ -20,8 +20,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbVersionedNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode; import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode;
@ -402,7 +402,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
var ruleNodeClass = componentDiscoveryService.getRuleNodeInfo(ruleNodeType) var ruleNodeClass = componentDiscoveryService.getRuleNodeInfo(ruleNodeType)
.orElseThrow(() -> new RuntimeException("Rule node " + ruleNodeType + " is not supported!")); .orElseThrow(() -> new RuntimeException("Rule node " + ruleNodeType + " is not supported!"));
if (ruleNodeClass.isVersioned()) { if (ruleNodeClass.isVersioned()) {
TbVersionedNode tbVersionedNode = (TbVersionedNode) ruleNodeClass.getClazz().getDeclaredConstructor().newInstance(); TbNode tbVersionedNode = (TbNode) ruleNodeClass.getClazz().getDeclaredConstructor().newInstance();
int fromVersion = node.getConfigurationVersion(); int fromVersion = node.getConfigurationVersion();
int toVersion = ruleNodeClass.getCurrentVersion(); int toVersion = ruleNodeClass.getCurrentVersion();
if (fromVersion < toVersion) { if (fromVersion < toVersion) {

4
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java

@ -48,8 +48,8 @@ public abstract class BaseEntityExportService<I extends EntityId, E extends Expo
public abstract Set<EntityType> getSupportedEntityTypes(); public abstract Set<EntityType> getSupportedEntityTypes();
protected void replaceUuidsRecursively(EntitiesExportCtx<?> ctx, JsonNode node, Set<String> skipFieldsSet, Pattern includedFieldsPattern) { protected void replaceUuidsRecursively(EntitiesExportCtx<?> ctx, JsonNode node, Set<String> skippedRootFields, Pattern includedFieldsPattern) {
JacksonUtil.replaceUuidsRecursively(node, skipFieldsSet, includedFieldsPattern, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid)); JacksonUtil.replaceUuidsRecursively(node, skippedRootFields, includedFieldsPattern, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid), true);
} }
protected Stream<UUID> toExternalIds(Collection<UUID> internalIds, Function<UUID, EntityId> entityIdCreator, protected Stream<UUID> toExternalIds(Collection<UUID> internalIds, Function<UUID, EntityId> entityIdCreator,

5
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java

@ -26,8 +26,11 @@ import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import static org.thingsboard.server.service.sync.ie.importing.impl.DashboardImportService.WIDGET_CONFIG_PROCESSED_FIELDS_PATTERN;
@Service @Service
@TbCoreComponent @TbCoreComponent
public class DashboardExportService extends BaseEntityExportService<DashboardId, Dashboard, EntityExportData<Dashboard>> { public class DashboardExportService extends BaseEntityExportService<DashboardId, Dashboard, EntityExportData<Dashboard>> {
@ -43,7 +46,7 @@ public class DashboardExportService extends BaseEntityExportService<DashboardId,
replaceUuidsRecursively(ctx, entityAlias, Set.of("id"), null); replaceUuidsRecursively(ctx, entityAlias, Set.of("id"), null);
} }
for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) { for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) {
replaceUuidsRecursively(ctx, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Set.of("id"), null); replaceUuidsRecursively(ctx, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.emptySet(), WIDGET_CONFIG_PROCESSED_FIELDS_PATTERN);
} }
} }

5
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -22,6 +22,7 @@ import com.google.gson.JsonPrimitive;
import lombok.Data; import lombok.Data;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -57,7 +58,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.utils.CsvUtils; import org.thingsboard.server.utils.CsvUtils;
import org.thingsboard.server.utils.TypeCastUtil; import org.thingsboard.server.common.data.util.TypeCastUtil;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -269,7 +270,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
if (!entry.getKey().getType().isKv()) { if (!entry.getKey().getType().isKv()) {
entityData.getFields().put(entry.getKey().getType(), entry.getValue()); entityData.getFields().put(entry.getKey().getType(), entry.getValue());
} else { } else {
Map.Entry<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue()); Pair<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue());
entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey())); entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey()));
} }
}); });

6
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -388,11 +388,11 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
} }
protected void replaceIdsRecursively(EntitiesImportCtx ctx, IdProvider idProvider, JsonNode json, protected void replaceIdsRecursively(EntitiesImportCtx ctx, IdProvider idProvider, JsonNode json,
Set<String> skipFieldsSet, Pattern includedFieldsPattern, Set<String> skippedRootFields, Pattern includedFieldsPattern,
LinkedHashSet<EntityType> hints) { LinkedHashSet<EntityType> hints) {
JacksonUtil.replaceUuidsRecursively(json, skipFieldsSet, includedFieldsPattern, JacksonUtil.replaceUuidsRecursively(json, skippedRootFields, includedFieldsPattern,
uuid -> idProvider.getInternalIdByUuid(uuid, ctx.isFinalImportAttempt(), hints) uuid -> idProvider.getInternalIdByUuid(uuid, ctx.isFinalImportAttempt(), hints)
.map(EntityId::getId).orElse(uuid)); .map(EntityId::getId).orElse(uuid), true);
} }
} }

4
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java

@ -36,6 +36,7 @@ import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -44,6 +45,7 @@ import java.util.stream.Collectors;
public class DashboardImportService extends BaseEntityImportService<DashboardId, Dashboard, EntityExportData<Dashboard>> { public class DashboardImportService extends BaseEntityImportService<DashboardId, Dashboard, EntityExportData<Dashboard>> {
private static final LinkedHashSet<EntityType> HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.DASHBOARD, EntityType.DEVICE, EntityType.ASSET)); private static final LinkedHashSet<EntityType> HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.DASHBOARD, EntityType.DEVICE, EntityType.ASSET));
public static final Pattern WIDGET_CONFIG_PROCESSED_FIELDS_PATTERN = Pattern.compile(".*Id.*");
private final DashboardService dashboardService; private final DashboardService dashboardService;
@ -68,7 +70,7 @@ public class DashboardImportService extends BaseEntityImportService<DashboardId,
replaceIdsRecursively(ctx, idProvider, entityAlias, Set.of("id"), null, HINTS); replaceIdsRecursively(ctx, idProvider, entityAlias, Set.of("id"), null, HINTS);
} }
for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) { for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) {
replaceIdsRecursively(ctx, idProvider, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Set.of("id"), null, HINTS); replaceIdsRecursively(ctx, idProvider, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.emptySet(), WIDGET_CONFIG_PROCESSED_FIELDS_PATTERN, HINTS);
} }
return dashboard; return dashboard;
} }

2
application/src/main/resources/logback.xml

@ -30,6 +30,8 @@
<logger name="org.apache.kafka.clients" level="WARN"/> <logger name="org.apache.kafka.clients" level="WARN"/>
<!-- To enable the logging of scanned rule engine components--> <!-- To enable the logging of scanned rule engine components-->
<!-- <logger name="org.thingsboard.server.service.component.AnnotationComponentDiscoveryService" level="DEBUG" />--> <!-- <logger name="org.thingsboard.server.service.component.AnnotationComponentDiscoveryService" level="DEBUG" />-->
<!-- To enable the debug logging of rule node upgrade -->
<!-- <logger name="org.thingsboard.server.service.install.update.DefaultDataUpdateService" level="DEBUG"/>-->
<!-- Other useful logs --> <!-- Other useful logs -->
<!-- <logger name="org.springframework.jdbc.core" level="TRACE" />--> <!-- <logger name="org.springframework.jdbc.core" level="TRACE" />-->
<!-- <logger name="org.hibernate.SQL" level="DEBUG" />--> <!-- <logger name="org.hibernate.SQL" level="DEBUG" />-->

4
application/src/main/resources/thingsboard.yml

@ -991,11 +991,15 @@ transport:
# value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}" # value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}"
snmp: snmp:
enabled: "${SNMP_ENABLED:true}" enabled: "${SNMP_ENABLED:true}"
bind_port: "${SNMP_BIND_PORT:1620}"
response_processing: response_processing:
# parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices
parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}"
# to configure SNMP to work over UDP or TCP # to configure SNMP to work over UDP or TCP
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
response:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
stats: stats:
enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}" print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"

4
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java

@ -66,6 +66,8 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FI
@TestPropertySource(properties = { @TestPropertySource(properties = {
"device.connectivity.https.enabled=true", "device.connectivity.https.enabled=true",
"device.connectivity.http.port=8080",
"device.connectivity.https.port=444",
"device.connectivity.mqtts.enabled=true", "device.connectivity.mqtts.enabled=true",
"device.connectivity.mqtts.pem_cert_file=/tmp/" + PEM_CERT_FILE_NAME, "device.connectivity.mqtts.pem_cert_file=/tmp/" + PEM_CERT_FILE_NAME,
"device.connectivity.coaps.enabled=true", "device.connectivity.coaps.enabled=true",
@ -191,7 +193,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry " + assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"", "--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId())); credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry " + assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost:444/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"", "--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId())); credentials.getCredentialsId()));

144
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java

@ -0,0 +1,144 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS;
import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FILE_NAME;
@TestPropertySource(properties = {
"device.connectivity.https.enabled=true",
"device.connectivity.http.port=80",
"device.connectivity.mqtt.enabled=false",
"device.connectivity.mqtts.enabled=false",
"device.connectivity.coap.enabled=false",
"device.connectivity.coaps.enabled=false",
})
@ContextConfiguration(classes = {DeviceConnectivityControllerWithDefaultPortTest.Config.class})
@DaoSqlTest
public class DeviceConnectivityControllerWithDefaultPortTest extends AbstractControllerTest {
ListeningExecutorService executor;
private Tenant savedTenant;
static class Config {
@Bean
@Primary
public DeviceDao deviceDao(DeviceDao deviceDao) {
return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao));
}
}
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
executor.shutdownNow();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
}
@Test
public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
JsonNode commands =
doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {
});
DeviceCredentials credentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertThat(commands).hasSize(1);
JsonNode httpCommands = commands.get(HTTP);
assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " +
"--header Content-Type:application/json --data \"{temperature:25}\"",
credentials.getCredentialsId()));
}
}

13
application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java

@ -149,6 +149,19 @@ public class TelemetryControllerTest extends AbstractControllerTest {
doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest());
} }
@Test
public void testEmptyKeyIsProhibited() throws Exception {
loginTenantAdmin();
Device device = createDevice();
String invalidRequestBody = "{\"\": \"value\"}";
doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", invalidRequestBody, String.class, status().isBadRequest());
doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest());
String invalidRequestBody2 = "{\" \": \"value\"}";
doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", invalidRequestBody2, String.class, status().isBadRequest());
doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody2, String.class, status().isBadRequest());
}
private Device createDevice() throws Exception { private Device createDevice() throws Exception {
String testToken = "TEST_TOKEN"; String testToken = "TEST_TOKEN";

3
application/src/test/java/org/thingsboard/server/edge/WidgetEdgeTest.java

@ -58,6 +58,7 @@ public class WidgetEdgeTest extends AbstractEdgeTest {
descriptor.put("key", "value"); descriptor.put("key", "value");
widgetType.setDescriptor(descriptor); widgetType.setDescriptor(descriptor);
widgetType.setDeprecated(true); widgetType.setDeprecated(true);
widgetType.setFqn("bundle_alias.type_alias");
WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
@ -69,6 +70,8 @@ public class WidgetEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(savedWidgetType.getFqn(), widgetTypeUpdateMsg.getFqn()); Assert.assertEquals(savedWidgetType.getFqn(), widgetTypeUpdateMsg.getFqn());
Assert.assertEquals(savedWidgetType.getName(), widgetTypeUpdateMsg.getName()); Assert.assertEquals(savedWidgetType.getName(), widgetTypeUpdateMsg.getName());
Assert.assertTrue(widgetTypeUpdateMsg.getDeprecated()); Assert.assertTrue(widgetTypeUpdateMsg.getDeprecated());
Assert.assertEquals("bundle_alias", widgetTypeUpdateMsg.getBundleAlias());
Assert.assertEquals("type_alias", widgetTypeUpdateMsg.getAlias());
Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor()); Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor());
// update widget bundle // update widget bundle

306
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java

@ -0,0 +1,306 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor;
import org.junit.jupiter.params.provider.Arguments;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeSynchronizationManager;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.edge.rpc.constructor.AdminSettingsMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.AlarmMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.AssetMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.AssetProfileMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.CustomerMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DashboardMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DeviceProfileMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.EdgeMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.EntityViewMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.OtaPackageMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.QueueMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.RelationMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.RuleChainMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.TenantMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.TenantProfileMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.UUID;
import java.util.stream.Stream;
public abstract class BaseEdgeProcessorTest {
@MockBean
protected TelemetrySubscriptionService tsSubService;
@MockBean
protected TbNotificationEntityService notificationEntityService;
@MockBean
protected RuleChainService ruleChainService;
@MockBean
protected AlarmService alarmService;
@MockBean
protected DeviceService deviceService;
@MockBean
protected TbDeviceProfileCache deviceProfileCache;
@MockBean
protected TbAssetProfileCache assetProfileCache;
@MockBean
protected DashboardService dashboardService;
@MockBean
protected AssetService assetService;
@MockBean
protected EntityViewService entityViewService;
@MockBean
protected TenantService tenantService;
@MockBean
protected TenantProfileService tenantProfileService;
@MockBean
protected EdgeService edgeService;
@MockBean
protected CustomerService customerService;
@MockBean
protected UserService userService;
@MockBean
protected DeviceProfileService deviceProfileService;
@MockBean
protected AssetProfileService assetProfileService;
@MockBean
protected RelationService relationService;
@MockBean
protected DeviceCredentialsService deviceCredentialsService;
@MockBean
protected AttributesService attributesService;
@MockBean
protected TbClusterService tbClusterService;
@MockBean
protected DeviceStateService deviceStateService;
@MockBean
protected EdgeEventService edgeEventService;
@MockBean
protected WidgetsBundleService widgetsBundleService;
@MockBean
protected WidgetTypeService widgetTypeService;
@MockBean
protected OtaPackageService otaPackageService;
@MockBean
protected QueueService queueService;
@MockBean
protected PartitionService partitionService;
@MockBean
@Lazy
protected TbQueueProducerProvider producerProvider;
@MockBean
protected DataValidator<Device> deviceValidator;
@MockBean
protected DataValidator<DeviceProfile> deviceProfileValidator;
@MockBean
protected DataValidator<Asset> assetValidator;
@MockBean
protected DataValidator<AssetProfile> assetProfileValidator;
@MockBean
protected DataValidator<Dashboard> dashboardValidator;
@MockBean
protected DataValidator<EntityView> entityViewValidator;
@MockBean
protected EdgeMsgConstructor edgeMsgConstructor;
@MockBean
protected EntityDataMsgConstructor entityDataMsgConstructor;
@MockBean
protected RuleChainMsgConstructor ruleChainMsgConstructor;
@MockBean
protected AlarmMsgConstructor alarmMsgConstructor;
@SpyBean
protected DeviceMsgConstructor deviceMsgConstructor;
@SpyBean
protected AssetMsgConstructor assetMsgConstructor;
@MockBean
protected EntityViewMsgConstructor entityViewMsgConstructor;
@MockBean
protected DashboardMsgConstructor dashboardMsgConstructor;
@MockBean
protected RelationMsgConstructor relationMsgConstructor;
@MockBean
protected UserMsgConstructor userMsgConstructor;
@MockBean
protected CustomerMsgConstructor customerMsgConstructor;
@SpyBean
protected DeviceProfileMsgConstructor deviceProfileMsgConstructor;
@SpyBean
protected AssetProfileMsgConstructor assetProfileMsgConstructor;
@MockBean
protected TenantMsgConstructor tenantMsgConstructor;
@MockBean
protected TenantProfileMsgConstructor tenantProfileMsgConstructor;
@MockBean
protected WidgetsBundleMsgConstructor widgetsBundleMsgConstructor;
@MockBean
protected WidgetTypeMsgConstructor widgetTypeMsgConstructor;
@MockBean
protected AdminSettingsMsgConstructor adminSettingsMsgConstructor;
@MockBean
protected OtaPackageMsgConstructor otaPackageMsgConstructor;
@MockBean
protected QueueMsgConstructor queueMsgConstructor;
@MockBean
protected EdgeSynchronizationManager edgeSynchronizationManager;
@MockBean
protected DbCallbackExecutorService dbCallbackExecutorService;
@MockBean
protected DataDecodingEncodingService dataDecodingEncodingService;
protected EdgeId edgeId;
protected TenantId tenantId;
protected EdgeEvent edgeEvent;
protected DashboardId getDashboardId(long expectedDashboardIdMSB, long expectedDashboardIdLSB) {
DashboardId dashboardId;
if (expectedDashboardIdMSB != 0 && expectedDashboardIdLSB != 0) {
dashboardId = new DashboardId(new UUID(expectedDashboardIdMSB, expectedDashboardIdLSB));
} else {
dashboardId = new DashboardId(UUID.randomUUID());
}
return dashboardId;
}
protected RuleChainId getRuleChainId(long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
RuleChainId ruleChainId;
if (expectedRuleChainIdMSB != 0 && expectedRuleChainIdLSB != 0) {
ruleChainId = new RuleChainId(new UUID(expectedRuleChainIdMSB, expectedRuleChainIdLSB));
} else {
ruleChainId = new RuleChainId(UUID.randomUUID());
}
return ruleChainId;
}
protected static Stream<Arguments> provideParameters() {
UUID dashoboardUUID = UUID.randomUUID();
UUID ruleChaindUUID = UUID.randomUUID();
return Stream.of(
Arguments.of(EdgeVersion.V_3_3_0, 0, 0, 0, 0),
Arguments.of(EdgeVersion.V_3_3_3, 0, 0, 0, 0),
Arguments.of(EdgeVersion.V_3_4_0, 0, 0, 0, 0),
Arguments.of(EdgeVersion.V_3_6_0,
dashoboardUUID.getMostSignificantBits(),
dashoboardUUID.getLeastSignificantBits(),
ruleChaindUUID.getMostSignificantBits(),
ruleChaindUUID.getLeastSignificantBits())
);
}
}

92
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AbstractAssetProcessorTest.java

@ -0,0 +1,92 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.asset;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorTest;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.BDDMockito.willReturn;
public abstract class AbstractAssetProcessorTest extends BaseEdgeProcessorTest {
protected AssetId assetId;
protected AssetProfileId assetProfileId;
protected AssetProfile assetProfile;
@BeforeEach
public void setUp() {
edgeId = new EdgeId(UUID.randomUUID());
tenantId = new TenantId(UUID.randomUUID());
assetId = new AssetId(UUID.randomUUID());
assetProfileId = new AssetProfileId(UUID.randomUUID());
assetProfile = new AssetProfile();
assetProfile.setId(assetProfileId);
assetProfile.setName("AssetProfile");
assetProfile.setDefault(true);
Asset asset = new Asset();
asset.setAssetProfileId(assetProfileId);
asset.setId(assetId);
asset.setName("Asset");
asset.setType(assetProfile.getName());
edgeEvent = new EdgeEvent();
edgeEvent.setTenantId(tenantId);
edgeEvent.setAction(EdgeEventActionType.ADDED);
willReturn(asset).given(assetService).findAssetById(tenantId, assetId);
willReturn(assetProfile).given(assetProfileService).findAssetProfileById(tenantId, assetProfileId);
}
protected void updateAssetProfileDefaultFields(long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
DashboardId dashboardId = getDashboardId(expectedDashboardIdMSB, expectedDashboardIdLSB);
RuleChainId ruleChainId = getRuleChainId(expectedRuleChainIdMSB, expectedRuleChainIdLSB);
assetProfile.setDefaultDashboardId(dashboardId);
assetProfile.setDefaultEdgeRuleChainId(ruleChainId);
}
protected void verify(DownlinkMsg downlinkMsg, long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
AssetProfileUpdateMsg assetProfileUpdateMsg = downlinkMsg.getAssetProfileUpdateMsgList().get(0);
assertNotNull(assetProfileUpdateMsg);
Assertions.assertThat(assetProfileUpdateMsg.getDefaultDashboardIdMSB()).isEqualTo(expectedDashboardIdMSB);
Assertions.assertThat(assetProfileUpdateMsg.getDefaultDashboardIdLSB()).isEqualTo(expectedDashboardIdLSB);
Assertions.assertThat(assetProfileUpdateMsg.getDefaultRuleChainIdMSB()).isEqualTo(expectedRuleChainIdMSB);
Assertions.assertThat(assetProfileUpdateMsg.getDefaultRuleChainIdLSB()).isEqualTo(expectedRuleChainIdLSB);
}
}

44
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorTest.java

@ -0,0 +1,44 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.asset;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@SpringBootTest(classes = {AssetEdgeProcessor.class})
class AssetEdgeProcessorTest extends AbstractAssetProcessorTest {
@SpyBean
AssetEdgeProcessor assetEdgeProcessor;
@ParameterizedTest
@MethodSource("provideParameters")
public void testAssetProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
updateAssetProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
edgeEvent.setEntityId(assetId.getId());
DownlinkMsg downlinkMsg = assetEdgeProcessor.convertAssetEventToDownlink(edgeEvent, edgeId, edgeVersion);
verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
}
}

43
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessorTest.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.asset;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@SpringBootTest(classes = {AssetProfileEdgeProcessor.class})
class AssetProfileEdgeProcessorTest extends AbstractAssetProcessorTest{
@SpyBean
AssetProfileEdgeProcessor assetProfileEdgeProcessor;
@ParameterizedTest
@MethodSource("provideParameters")
public void testAssetProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
updateAssetProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
edgeEvent.setEntityId(assetProfileId.getId());
DownlinkMsg downlinkMsg = assetProfileEdgeProcessor.convertAssetProfileEventToDownlink(edgeEvent, edgeId, edgeVersion);
verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
}
}

100
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java

@ -0,0 +1,100 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.device;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorTest;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.BDDMockito.willReturn;
public abstract class AbstractDeviceProcessorTest extends BaseEdgeProcessorTest {
protected DeviceId deviceId;
protected DeviceProfileId deviceProfileId;
protected DeviceProfile deviceProfile;
@BeforeEach
public void setUp() {
edgeId = new EdgeId(UUID.randomUUID());
tenantId = new TenantId(UUID.randomUUID());
deviceId = new DeviceId(UUID.randomUUID());
deviceProfileId = new DeviceProfileId(UUID.randomUUID());
deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
deviceProfile.setName("DeviceProfile");
deviceProfile.setDefault(true);
deviceProfile.setType(DeviceProfileType.DEFAULT);
DeviceProfileData deviceProfileData = new DeviceProfileData();
deviceProfile.setProfileData(deviceProfileData);
deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
Device device = new Device();
device.setDeviceProfileId(deviceProfileId);
device.setId(deviceId);
device.setName("Device");
device.setType(deviceProfile.getName());
edgeEvent = new EdgeEvent();
edgeEvent.setTenantId(tenantId);
edgeEvent.setAction(EdgeEventActionType.ADDED);
willReturn(device).given(deviceService).findDeviceById(tenantId, deviceId);
willReturn(deviceProfile).given(deviceProfileService).findDeviceProfileById(tenantId, deviceProfileId);
willReturn(new byte[]{0x00}).given(dataDecodingEncodingService).encode(deviceProfileData);
}
protected void updateDeviceProfileDefaultFields(long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
DashboardId dashboardId = getDashboardId(expectedDashboardIdMSB, expectedDashboardIdLSB);
RuleChainId ruleChainId = getRuleChainId(expectedRuleChainIdMSB, expectedRuleChainIdLSB);
deviceProfile.setDefaultDashboardId(dashboardId);
deviceProfile.setDefaultEdgeRuleChainId(ruleChainId);
}
protected void verify(DownlinkMsg downlinkMsg, long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
DeviceProfileUpdateMsg deviceProfileUpdateMsg = downlinkMsg.getDeviceProfileUpdateMsgList().get(0);
assertNotNull(deviceProfileUpdateMsg);
Assertions.assertThat(deviceProfileUpdateMsg.getDefaultDashboardIdMSB()).isEqualTo(expectedDashboardIdMSB);
Assertions.assertThat(deviceProfileUpdateMsg.getDefaultDashboardIdLSB()).isEqualTo(expectedDashboardIdLSB);
Assertions.assertThat(deviceProfileUpdateMsg.getDefaultRuleChainIdMSB()).isEqualTo(expectedRuleChainIdMSB);
Assertions.assertThat(deviceProfileUpdateMsg.getDefaultRuleChainIdLSB()).isEqualTo(expectedRuleChainIdLSB);
}
}

43
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorTest.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.device;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@SpringBootTest(classes = {DeviceEdgeProcessor.class})
class DeviceEdgeProcessorTest extends AbstractDeviceProcessorTest {
@SpyBean
DeviceEdgeProcessor deviceEdgeProcessor;
@ParameterizedTest
@MethodSource("provideParameters")
public void testDeviceProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
updateDeviceProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
edgeEvent.setEntityId(deviceId.getId());
DownlinkMsg downlinkMsg = deviceEdgeProcessor.convertDeviceEventToDownlink(edgeEvent, edgeId, edgeVersion);
verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
}
}

45
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessorTest.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.device;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@SpringBootTest(classes = {DeviceProfileEdgeProcessor.class})
class DeviceProfileEdgeProcessorTest extends AbstractDeviceProcessorTest {
@SpyBean
DeviceProfileEdgeProcessor deviceProfileEdgeProcessor;
@ParameterizedTest
@MethodSource("provideParameters")
public void testDeviceProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB,
long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) {
updateDeviceProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
edgeEvent.setEntityId(deviceProfileId.getId());
DownlinkMsg downlinkMsg = deviceProfileEdgeProcessor.convertDeviceProfileEventToDownlink(edgeEvent, edgeId, edgeVersion);
verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB);
}
}

119
application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java

@ -0,0 +1,119 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.validator.RuleChainDataValidator;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willReturn;
@Slf4j
@SpringBootTest(classes = {InstallScripts.class, RuleChainDataValidator.class})
class InstallScriptsTest {
@MockBean
RuleChainService ruleChainService;
@MockBean
DashboardService dashboardService;
@MockBean
WidgetTypeService widgetTypeService;
@MockBean
WidgetsBundleService widgetsBundleService;
@MockBean
OAuth2ConfigTemplateService oAuth2TemplateService;
@MockBean
ResourceService resourceService;
@SpyBean
InstallScripts installScripts;
@MockBean
TenantService tenantService;
@MockBean
ApiLimitService apiLimitService;
@SpyBean
RuleChainDataValidator ruleChainValidator;
TenantId tenantId = TenantId.fromUUID(UUID.fromString("9ef79cdf-37a8-4119-b682-2e7ed4e018da"));
@BeforeEach
void setUp() {
willReturn(true).given(tenantService).tenantExists(tenantId);
willReturn(true).given(apiLimitService).checkEntitiesLimit(any(), any());
}
@Test
void testDefaultRuleChainsTemplates() throws IOException {
Path dir = installScripts.getTenantRuleChainsDir();
installScripts.findRuleChainsFromPath(dir)
.forEach(this::validateRuleChainTemplate);
}
@Test
void testDefaultEdgeRuleChainsTemplates() throws IOException {
Path dir = installScripts.getEdgeRuleChainsDir();
installScripts.findRuleChainsFromPath(dir)
.forEach(this::validateRuleChainTemplate);
}
@Test
void testDeviceProfileDefaultRuleChainTemplate() {
validateRuleChainTemplate(installScripts.getDeviceProfileDefaultRuleChainTemplateFilePath());
}
private void validateRuleChainTemplate(Path templateFilePath) {
log.warn("validateRuleChainTemplate {}", templateFilePath);
JsonNode ruleChainJson = JacksonUtil.toJsonNode(templateFilePath.toFile());
RuleChain ruleChain = JacksonUtil.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
ruleChain.setTenantId(tenantId);
ruleChainValidator.validate(ruleChain, RuleChain::getTenantId);
ruleChain.setId(new RuleChainId(UUID.randomUUID()));
RuleChainMetaData ruleChainMetaData = JacksonUtil.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
ruleChainMetaData.setRuleChainId(ruleChain.getId());
List<Throwable> throwables = RuleChainDataValidator.validateMetaData(ruleChainMetaData);
assertThat(throwables).as("templateFilePath " + templateFilePath)
.containsExactlyInAnyOrderElementsOf(Collections.emptyList());
}
}

53
application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java

@ -257,6 +257,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
Asset asset1 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 1"); Asset asset1 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 1");
Asset asset2 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 2"); Asset asset2 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 2");
Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1"); Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1");
Dashboard otherDashboard = createDashboard(tenantId1, null, "Dashboard 2");
DeviceProfile existingDeviceProfile = createDeviceProfile(tenantId2, null, null, "Existing"); DeviceProfile existingDeviceProfile = createDeviceProfile(tenantId2, null, null, "Existing");
String aliasId = "23c4185d-1497-9457-30b2-6d91e69a5b2c"; String aliasId = "23c4185d-1497-9457-30b2-6d91e69a5b2c";
@ -265,20 +266,43 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
"\"" + aliasId + "\": {\n" + "\"" + aliasId + "\": {\n" +
"\"alias\": \"assets\",\n" + "\"alias\": \"assets\",\n" +
"\"filter\": {\n" + "\"filter\": {\n" +
"\"entityList\": [\n" + " \"entityList\": [\n" +
"\"" + asset1.getId().toString() + "\",\n" + " \"" + asset1.getId() + "\",\n" +
"\"" + asset2.getId().toString() + "\",\n" + " \"" + asset2.getId() + "\",\n" +
"\"" + tenantId1.getId().toString() + "\",\n" + " \"" + tenantId1.getId() + "\",\n" +
"\"" + existingDeviceProfile.getId().toString() + "\",\n" + " \"" + existingDeviceProfile.getId() + "\",\n" +
"\"" + unknownUuid + "\"\n" + " \"" + unknownUuid + "\"\n" +
"],\n" + " ],\n" +
"\"resolveMultiple\": true\n" + " \"id\":\"" + asset1.getId() + "\",\n" +
" \"resolveMultiple\": true\n" +
"},\n" + "},\n" +
"\"id\": \"" + aliasId + "\"\n" + "\"id\": \"" + aliasId + "\"\n" +
"}\n" + "}\n" +
"}"; "}";
String widgetId = "ea8f34a0-264a-f11f-cde3-05201bb4ff4b";
String actionId = "4a8e6efa-3e68-fa59-7feb-d83366130cae";
String widgets = "{\n" +
" \"" + widgetId + "\": {\n" +
" \"config\": {\n" +
" \"actions\": {\n" +
" \"rowClick\": [\n" +
" {\n" +
" \"name\": \"go to dashboard\",\n" +
" \"targetDashboardId\": \"" + otherDashboard.getId() + "\",\n" +
" \"id\": \"" + actionId + "\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" },\n" +
" \"row\": 0,\n" +
" \"col\": 0,\n" +
" \"id\": \"" + widgetId + "\"\n" +
" }\n" +
"}";
ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode(); ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode();
dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases)); dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases));
dashboardConfiguration.set("widgets", JacksonUtil.toJsonNode(widgets));
dashboardConfiguration.set("description", new TextNode("hallo")); dashboardConfiguration.set("description", new TextNode("hallo"));
dashboard.setConfiguration(dashboardConfiguration); dashboard.setConfiguration(dashboardConfiguration);
dashboard = dashboardService.saveDashboard(dashboard); dashboard = dashboardService.saveDashboard(dashboard);
@ -288,10 +312,12 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
EntityExportData<Asset> asset1ExportData = exportEntity(tenantAdmin1, asset1.getId()); EntityExportData<Asset> asset1ExportData = exportEntity(tenantAdmin1, asset1.getId());
EntityExportData<Asset> asset2ExportData = exportEntity(tenantAdmin1, asset2.getId()); EntityExportData<Asset> asset2ExportData = exportEntity(tenantAdmin1, asset2.getId());
EntityExportData<Dashboard> dashboardExportData = exportEntity(tenantAdmin1, dashboard.getId()); EntityExportData<Dashboard> dashboardExportData = exportEntity(tenantAdmin1, dashboard.getId());
EntityExportData<Dashboard> otherDashboardExportData = exportEntity(tenantAdmin1, otherDashboard.getId());
AssetProfile importedProfile = importEntity(tenantAdmin2, profileExportData).getSavedEntity(); AssetProfile importedProfile = importEntity(tenantAdmin2, profileExportData).getSavedEntity();
Asset importedAsset1 = importEntity(tenantAdmin2, asset1ExportData).getSavedEntity(); Asset importedAsset1 = importEntity(tenantAdmin2, asset1ExportData).getSavedEntity();
Asset importedAsset2 = importEntity(tenantAdmin2, asset2ExportData).getSavedEntity(); Asset importedAsset2 = importEntity(tenantAdmin2, asset2ExportData).getSavedEntity();
Dashboard importedOtherDashboard = importEntity(tenantAdmin2, otherDashboardExportData).getSavedEntity();
Dashboard importedDashboard = importEntity(tenantAdmin2, dashboardExportData).getSavedEntity(); Dashboard importedDashboard = importEntity(tenantAdmin2, dashboardExportData).getSavedEntity();
Map.Entry<String, JsonNode> entityAlias = importedDashboard.getConfiguration().get("entityAliases").fields().next(); Map.Entry<String, JsonNode> entityAlias = importedDashboard.getConfiguration().get("entityAliases").fields().next();
@ -311,6 +337,17 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
.isEqualTo(existingDeviceProfile.getId().toString()); .isEqualTo(existingDeviceProfile.getId().toString());
assertThat(aliasEntitiesIds).element(4).as("unresolved uuid was replaced with tenant id") assertThat(aliasEntitiesIds).element(4).as("unresolved uuid was replaced with tenant id")
.isEqualTo(tenantId2.toString()); .isEqualTo(tenantId2.toString());
assertThat(entityAlias.getValue().get("filter").get("id").asText()).as("external asset 1 was replaced with imported one")
.isEqualTo(importedAsset1.getId().toString());
ObjectNode widgetConfig = importedDashboard.getWidgetsConfig().get(0);
assertThat(widgetConfig.get("id").asText()).as("widget id is not replaced")
.isEqualTo(widgetId);
JsonNode actionConfig = widgetConfig.get("config").get("actions").get("rowClick").get(0);
assertThat(actionConfig.get("id").asText()).as("action id is not replaced")
.isEqualTo(actionId);
assertThat(actionConfig.get("targetDashboardId").asText()).as("dashboard id is replaced with imported one")
.isEqualTo(importedOtherDashboard.getId().toString());
} }

25
build_proto.sh

@ -0,0 +1,25 @@
#!/bin/bash
#
# Copyright © 2016-2023 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
echo "Building ThingsBoard protobuf-containing packages..."
MAVEN_OPTS="-Xmx1024m" NODE_OPTIONS="--max_old_space_size=3072" \
mvn clean compile -T4 --also-make --projects='
common/cluster-api,
common/edge-api,
common/transport/coap,
common/transport/mqtt,
common/transport/transport-api'

23
common/cluster-api/src/main/proto/queue.proto

@ -975,6 +975,8 @@ message ToCoreMsg {
EdgeNotificationMsgProto edgeNotificationMsg = 5; EdgeNotificationMsgProto edgeNotificationMsg = 5;
DeviceActivityProto deviceActivityMsg = 6; DeviceActivityProto deviceActivityMsg = 6;
NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7; NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7;
LifecycleEventProto lifecycleEventMsg = 8;
ErrorEventProto errorEventMsg = 9;
} }
/* High priority messages with low latency are handled by ThingsBoard Core Service separately */ /* High priority messages with low latency are handled by ThingsBoard Core Service separately */
@ -1066,3 +1068,24 @@ message NotificationSchedulerServiceMsg {
message NotificationRuleProcessorMsg { message NotificationRuleProcessorMsg {
bytes trigger = 1; bytes trigger = 1;
} }
message ErrorEventProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
int64 entityIdMSB = 3;
int64 entityIdLSB = 4;
string serviceId = 5;
string method = 6;
string error = 7;
}
message LifecycleEventProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
int64 entityIdMSB = 3;
int64 entityIdLSB = 4;
string serviceId = 5;
string lcEventType = 6;
bool success = 7;
string error = 8;
}

9
common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java

@ -163,6 +163,15 @@ public class StringUtils {
return false; return false;
} }
public static boolean equalsAnyIgnoreCase(String string, String... otherStrings) {
for (String otherString : otherStrings) {
if (equalsIgnoreCase(string, otherString)) {
return true;
}
}
return false;
}
public static String substringAfterLast(String str, String sep) { public static String substringAfterLast(String str, String sep) {
return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep); return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java

@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import java.util.List; import java.util.List;
@ -45,8 +44,7 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra
private boolean isValid() { private boolean isValid() {
return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0
&& communicationConfigs != null && communicationConfigs != null
&& communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) && communicationConfigs.stream().allMatch(config -> config != null && config.isValid());
&& communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid)
.distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum();
} }
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java

@ -43,6 +43,10 @@ public enum BulkImportColumnType {
LWM2M_SERVER_SECURITY_MODE("securityMode", LwM2MSecurityMode.NO_SEC.name()), LWM2M_SERVER_SECURITY_MODE("securityMode", LwM2MSecurityMode.NO_SEC.name()),
LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"), LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"),
LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"), LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"),
SNMP_HOST,
SNMP_PORT,
SNMP_VERSION,
SNMP_COMMUNITY_STRING,
IS_GATEWAY, IS_GATEWAY,
DESCRIPTION, DESCRIPTION,
ROUTING_KEY, ROUTING_KEY,

18
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java

@ -15,11 +15,21 @@
*/ */
package org.thingsboard.server.common.data.transport.snmp; package org.thingsboard.server.common.data.transport.snmp;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum SnmpCommunicationSpec { public enum SnmpCommunicationSpec {
TELEMETRY_QUERYING,
CLIENT_ATTRIBUTES_QUERYING, TELEMETRY_QUERYING("telemetryQuerying"),
SHARED_ATTRIBUTES_SETTING, CLIENT_ATTRIBUTES_QUERYING("clientAttributesQuerying"),
SHARED_ATTRIBUTES_SETTING("sharedAttributesSetting"),
TO_DEVICE_RPC_REQUEST("toDeviceRpcRequest"),
TO_SERVER_RPC_REQUEST("toServerRpcRequest");
private final String label;
TO_DEVICE_RPC_REQUEST,
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java

@ -17,7 +17,8 @@ package org.thingsboard.server.common.data.transport.snmp;
public enum SnmpMethod { public enum SnmpMethod {
GET(-96), GET(-96),
SET(-93); SET(-93),
TRAP(-89);
// codes taken from org.snmp4j.PDU class // codes taken from org.snmp4j.PDU class
private final int code; private final int code;

3
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java

@ -37,7 +37,8 @@ import java.util.List;
@Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"), @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"),
@Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"),
@Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"),
@Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST") @Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST"),
@Type(value = ToServerRpcRequestSnmpCommunicationConfig.class, name = "TO_SERVER_RPC_REQUEST")
}) })
public interface SnmpCommunicationConfig extends Serializable { public interface SnmpCommunicationConfig extends Serializable {

29
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/ToServerRpcRequestSnmpCommunicationConfig.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.transport.snmp.config;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
public class ToServerRpcRequestSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig {
private static final long serialVersionUID = 4851028734093214L;
@Override
public SnmpCommunicationSpec getSpec() {
return SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST;
}
}

32
application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java → common/data/src/main/java/org/thingsboard/server/common/data/util/TypeCastUtil.java

@ -13,35 +13,53 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.utils; package org.thingsboard.server.common.data.util;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Map;
public class TypeCastUtil { public class TypeCastUtil {
private TypeCastUtil() {} private TypeCastUtil() {}
public static Map.Entry<DataType, Object> castValue(String value) { public static Pair<DataType, Object> castValue(String value) {
if (isNumber(value)) { if (isNumber(value)) {
String formattedValue = value.replace(',', '.'); String formattedValue = value.replace(',', '.');
try { try {
BigDecimal bd = new BigDecimal(formattedValue); BigDecimal bd = new BigDecimal(formattedValue);
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) { if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
if (bd.scale() <= 16) { if (bd.scale() <= 16) {
return Map.entry(DataType.DOUBLE, bd.doubleValue()); return Pair.of(DataType.DOUBLE, bd.doubleValue());
} }
} else { } else {
return Map.entry(DataType.LONG, bd.longValueExact()); return Pair.of(DataType.LONG, bd.longValueExact());
} }
} catch (RuntimeException ignored) {} } catch (RuntimeException ignored) {}
} else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
return Map.entry(DataType.BOOLEAN, Boolean.parseBoolean(value)); return Pair.of(DataType.BOOLEAN, Boolean.parseBoolean(value));
}
return Pair.of(DataType.STRING, value);
}
public static Pair<DataType, Number> castToNumber(String value) {
if (isNumber(value)) {
String formattedValue = value.replace(',', '.');
BigDecimal bd = new BigDecimal(formattedValue);
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
if (bd.scale() <= 16) {
return Pair.of(DataType.DOUBLE, bd.doubleValue());
} else {
return Pair.of(DataType.DOUBLE, bd);
}
} else {
return Pair.of(DataType.LONG, bd.longValueExact());
}
} else {
throw new IllegalArgumentException("'" + value + "' can't be parsed as number");
} }
return Map.entry(DataType.STRING, value);
} }
private static boolean isNumber(String value) { private static boolean isNumber(String value) {

2
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
.setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setConnectRequestMsg(ConnectRequestMsg.newBuilder()
.setEdgeRoutingKey(edgeKey) .setEdgeRoutingKey(edgeKey)
.setEdgeSecret(edgeSecret) .setEdgeSecret(edgeSecret)
.setEdgeVersion(EdgeVersion.V_3_4_0) .setEdgeVersion(EdgeVersion.V_3_6_0)
.setMaxInboundMessageSize(maxInboundMessageSize) .setMaxInboundMessageSize(maxInboundMessageSize)
.build()) .build())
.build()); .build());

15
common/edge-api/src/main/proto/edge.proto

@ -34,6 +34,7 @@ enum EdgeVersion {
V_3_3_0 = 0; V_3_3_0 = 0;
V_3_3_3 = 1; V_3_3_3 = 1;
V_3_4_0 = 2; V_3_4_0 = 2;
V_3_6_0 = 3;
} }
/** /**
@ -85,7 +86,7 @@ message ConnectResponseMsg {
} }
message SyncRequestMsg { message SyncRequestMsg {
bool syncRequired = 1; // deprecated bool syncRequired = 1 [deprecated = true];
optional bool fullSync = 2; optional bool fullSync = 2;
} }
@ -113,7 +114,7 @@ enum UpdateMsgType {
ENTITY_DELETED_RPC_MESSAGE = 2; ENTITY_DELETED_RPC_MESSAGE = 2;
ALARM_ACK_RPC_MESSAGE = 3; ALARM_ACK_RPC_MESSAGE = 3;
ALARM_CLEAR_RPC_MESSAGE = 4; ALARM_CLEAR_RPC_MESSAGE = 4;
ENTITY_MERGE_RPC_MESSAGE = 5; // deprecated ENTITY_MERGE_RPC_MESSAGE = 5 [deprecated = true];
} }
message EntityDataProto { message EntityDataProto {
@ -204,7 +205,7 @@ message DeviceUpdateMsg {
string type = 9; string type = 9;
optional string label = 10; optional string label = 10;
optional string additionalInfo = 11; optional string additionalInfo = 11;
optional string conflictName = 12; // deprecated optional string conflictName = 12 [deprecated = true];
optional int64 firmwareIdMSB = 13; optional int64 firmwareIdMSB = 13;
optional int64 firmwareIdLSB = 14; optional int64 firmwareIdLSB = 14;
optional bytes deviceDataBytes = 15; optional bytes deviceDataBytes = 15;
@ -365,8 +366,8 @@ message WidgetTypeUpdateMsg {
UpdateMsgType msgType = 1; UpdateMsgType msgType = 1;
int64 idMSB = 2; int64 idMSB = 2;
int64 idLSB = 3; int64 idLSB = 3;
optional string bundleAlias = 4; // deprecated optional string bundleAlias = 4 [deprecated = true];
optional string alias = 5; // deprecated optional string alias = 5 [deprecated = true];
optional string name = 6; optional string name = 6;
optional string descriptorJson = 7; optional string descriptorJson = 7;
bool isSystem = 8; bool isSystem = 8;
@ -447,8 +448,8 @@ message DeviceCredentialsRequestMsg {
int64 deviceIdLSB = 2; int64 deviceIdLSB = 2;
} }
// deprecated
message DeviceProfileDevicesRequestMsg { message DeviceProfileDevicesRequestMsg {
option deprecated = true;
int64 deviceProfileIdMSB = 1; int64 deviceProfileIdMSB = 1;
int64 deviceProfileIdLSB = 2; int64 deviceProfileIdLSB = 2;
} }
@ -562,7 +563,7 @@ message UplinkMsg {
repeated UserCredentialsRequestMsg userCredentialsRequestMsg = 10; repeated UserCredentialsRequestMsg userCredentialsRequestMsg = 10;
repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 11; repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 11;
repeated DeviceRpcCallMsg deviceRpcCallMsg = 12; repeated DeviceRpcCallMsg deviceRpcCallMsg = 12;
repeated DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg = 13; // deprecated repeated DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg = 13 [deprecated = true];
repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14; repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14;
repeated EntityViewsRequestMsg entityViewsRequestMsg = 15; repeated EntityViewsRequestMsg entityViewsRequestMsg = 15;
repeated AssetUpdateMsg assetUpdateMsg = 16; repeated AssetUpdateMsg assetUpdateMsg = 16;

4
common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java

@ -150,7 +150,7 @@ public abstract class AbstractScriptInvokeService implements ScriptInvokeService
} }
apiUsageReportClient.ifPresent(client -> client.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1)); apiUsageReportClient.ifPresent(client -> client.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1));
pushedMsgs.incrementAndGet(); pushedMsgs.incrementAndGet();
log.trace("InvokeScript uuid {} with timeout {}ms", scriptId, getMaxInvokeRequestsTimeout()); log.trace("[{}] InvokeScript uuid {} with timeout {}ms", tenantId, scriptId, getMaxInvokeRequestsTimeout());
var task = doInvokeFunction(scriptId, args); var task = doInvokeFunction(scriptId, args);
var resultFuture = Futures.transformAsync(task.getResultFuture(), output -> { var resultFuture = Futures.transformAsync(task.getResultFuture(), output -> {
@ -167,7 +167,7 @@ public abstract class AbstractScriptInvokeService implements ScriptInvokeService
} else { } else {
String message = "Script invocation is blocked due to maximum error count " String message = "Script invocation is blocked due to maximum error count "
+ getMaxErrors() + ", scriptId " + scriptId + "!"; + getMaxErrors() + ", scriptId " + scriptId + "!";
log.warn(message); log.warn("[{}] " + message, tenantId);
return error(message); return error(message);
} }
} else { } else {

65
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DateTimeFormatOptions.java

@ -0,0 +1,65 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api.tbel;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.StringUtils;
import java.time.format.FormatStyle;
import java.util.TimeZone;
@NoArgsConstructor
@Data
class DateTimeFormatOptions {
private static final TimeZone DEFAULT_TZ = TimeZone.getDefault();
private String timeZone;
private String dateStyle;
private String timeStyle;
@Getter
private String pattern;
public DateTimeFormatOptions(String timeZone) {
this.timeZone = timeZone;
}
TimeZone getTimeZone() {
return StringUtils.isNotEmpty(timeZone) ? TimeZone.getTimeZone(timeZone) : TimeZone.getDefault();
}
FormatStyle getDateStyle() {
return getFormatStyle(dateStyle, FormatStyle.SHORT);
}
FormatStyle getTimeStyle() {
return getFormatStyle(timeStyle, FormatStyle.MEDIUM);
}
private static FormatStyle getFormatStyle(String style, FormatStyle defaultStyle) {
if (StringUtils.isNotEmpty(style)) {
try {
return FormatStyle.valueOf(style.toUpperCase());
} catch (IllegalArgumentException e) {
return defaultStyle;
}
} else {
return defaultStyle;
}
}
}

81
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java

@ -15,6 +15,9 @@
*/ */
package org.thingsboard.script.api.tbel; package org.thingsboard.script.api.tbel;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.Instant; import java.time.Instant;
@ -22,13 +25,15 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.function.BiFunction;
public class TbDate extends Date { public class TbDate extends Date {
@ -77,15 +82,77 @@ public class TbDate extends Date {
return isoDateFormat.get().format(this); return isoDateFormat.get().format(this);
} }
public String toLocaleDateString() {
return toLocaleDateString(null, null);
}
public String toLocaleDateString(String locale) {
return toLocaleDateString(locale, null);
}
public String toLocaleDateString(String localeStr, String optionsStr) {
return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(options.getDateStyle()).withLocale(locale));
}
public String toLocaleTimeString() {
return toLocaleTimeString(null, null);
}
public String toLocaleTimeString(String locale) {
return toLocaleTimeString(locale, null);
}
public String toLocaleTimeString(String localeStr, String optionsStr) {
return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale));
}
@Override
public String toLocaleString() {
return toLocaleString(null, null);
}
public String toLocaleString(String locale) { public String toLocaleString(String locale) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); return toLocaleString(locale, null);
return formatter.format(this);
} }
public String toLocaleString(String locale, String tz) { public String toLocaleString(String localeStr, String optionsStr) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); return toLocaleString(localeStr, optionsStr, (locale, options) -> {
formatter.setTimeZone(TimeZone.getTimeZone(tz)); String formatPattern =
return formatter.format(this); DateTimeFormatterBuilder.getLocalizedDateTimePattern(
options.getDateStyle(),
options.getTimeStyle(),
IsoChronology.INSTANCE,
locale);
return DateTimeFormatter.ofPattern(formatPattern, locale);
});
}
public String toLocaleString(String localeStr, String optionsStr, BiFunction<Locale, DateTimeFormatOptions, DateTimeFormatter> formatterBuilder) {
Locale locale = StringUtils.isNotEmpty(localeStr) ? Locale.forLanguageTag(localeStr) : Locale.getDefault();
DateTimeFormatOptions options = getDateFormattingOptions(optionsStr);
ZonedDateTime zdt = this.toInstant().atZone(options.getTimeZone().toZoneId());
DateTimeFormatter formatter;
if (StringUtils.isNotEmpty(options.getPattern())) {
formatter = new DateTimeFormatterBuilder().appendPattern(options.getPattern()).toFormatter(locale);
} else {
formatter = formatterBuilder.apply(locale, options);
}
return formatter.format(zdt);
}
private static DateTimeFormatOptions getDateFormattingOptions(String options) {
DateTimeFormatOptions opt = null;
if (StringUtils.isNotEmpty(options)) {
try {
opt = JacksonUtil.fromString(options, DateTimeFormatOptions.class);
} catch (IllegalArgumentException iae) {
opt = new DateTimeFormatOptions(options);
}
}
if (opt == null) {
opt = new DateTimeFormatOptions();
}
return opt;
} }
public static long now() { public static long now() {

2
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -36,6 +36,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -63,6 +64,7 @@ public class TbUtils {
ExecutionContext.class, String.class))); ExecutionContext.class, String.class)));
parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes", parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes",
ExecutionContext.class, String.class, String.class))); ExecutionContext.class, String.class, String.class)));
parserConfig.registerNonConvertableMethods(TbUtils.class, Collections.singleton("stringToBytes"));
parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt", parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt",
String.class))); String.class)));
parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt", parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt",

182
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java

@ -21,12 +21,24 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.thingsboard.common.util.JacksonUtil;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.FormatStyle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -124,4 +136,174 @@ class TbDateTest {
.startsWith(datePrefix); .startsWith(datePrefix);
} }
@Test
void testToLocaleDateString() {
TbDate d = new TbDate(1693962245000L);
// Depends on time zone, so we just check it works;
Assert.assertNotNull(d.toLocaleDateString());
Assert.assertNotNull(d.toLocaleDateString("en-US"));
Assert.assertEquals("9/5/23", d.toLocaleDateString("en-US", "America/New_York"));
Assert.assertEquals("23. 9. 5.", d.toLocaleDateString("ko-KR", "America/New_York"));
Assert.assertEquals("06.09.23", d.toLocaleDateString( "uk-UA", "Europe/Kiev"));
Assert.assertEquals("5\u200F/9\u200F/2023", d.toLocaleDateString( "ar-EG", "America/New_York"));
Assert.assertEquals("Tuesday, September 5, 2023", d.toLocaleDateString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("dateStyle", "full")
.toString()));
Assert.assertEquals("2023년 9월 5일 화요일", d.toLocaleDateString("ko-KR", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("dateStyle", "full")
.toString()));
Assert.assertEquals("середа, 6 вересня 2023 р.", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev")
.put("dateStyle", "full")
.toString()));
Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023", d.toLocaleDateString("ar-EG", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("dateStyle", "full")
.toString()));
Assert.assertEquals("Tuesday 9/5/2023", d.toLocaleDateString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "EEEE M/d/yyyy")
.toString()));
Assert.assertEquals("화요일 9/5/2023", d.toLocaleDateString("ko-KR", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "EEEE M/d/yyyy")
.toString()));
Assert.assertEquals("середа 9/6/2023", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev")
.put("pattern", "EEEE M/d/yyyy")
.toString()));
Assert.assertEquals("الثلاثاء 9/5/2023", d.toLocaleDateString("ar-EG", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "EEEE M/d/yyyy")
.toString()));
}
@Test
void testToLocaleTimeString() {
TbDate d = new TbDate(1693962245000L);
// Depends on time zone, so we just check it works;
Assert.assertNotNull(d.toLocaleTimeString());
Assert.assertNotNull(d.toLocaleTimeString("en-US"));
Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", "America/New_York"));
Assert.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York"));
Assert.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev"));
Assert.assertEquals("9:04:05 م", d.toLocaleTimeString( "ar-EG", "America/New_York"));
Assert.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("04:04:05 за східноєвропейським літнім часом", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "h:mm:ss a")
.toString()));
Assert.assertEquals("9:04:05 오후", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "h:mm:ss a")
.toString()));
Assert.assertEquals("4:04:05 дп", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev")
.put("pattern", "h:mm:ss a")
.toString()));
Assert.assertEquals("9:04:05 م", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "h:mm:ss a")
.toString()));
}
@Test
void testToLocaleString() {
TbDate d = new TbDate(1693962245000L);
// Depends on time zone, so we just check it works;
Assert.assertNotNull(d.toLocaleString());
Assert.assertNotNull(d.toLocaleString("en-US"));
Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York"));
Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York"));
Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev"));
Assert.assertEquals("5\u200F/9\u200F/2023 9:04:05 م", d.toLocaleString( "ar-EG", "America/New_York"));
Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("dateStyle", "full")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("2023년 9월 5일 화요일 오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("dateStyle", "full")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev")
.put("dateStyle", "full")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("dateStyle", "full")
.put("timeStyle", "full")
.toString()));
Assert.assertEquals("9/5/2023, 9:04:05 PM", d.toLocaleString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "M/d/yyyy, h:mm:ss a")
.toString()));
Assert.assertEquals("9/5/2023, 9:04:05 오후", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "M/d/yyyy, h:mm:ss a")
.toString()));
Assert.assertEquals("9/6/2023, 4:04:05 дп", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev")
.put("pattern", "M/d/yyyy, h:mm:ss a")
.toString()));
Assert.assertEquals("9/5/2023, 9:04:05 م", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York")
.put("pattern", "M/d/yyyy, h:mm:ss a")
.toString()));
}
private static String toLocalString(TbDate d, Locale locale, ZoneId tz) {
LocalDateTime ldt = d.toInstant().atZone(tz).toLocalDateTime();
// new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale)
String formatPattern =
DateTimeFormatterBuilder.getLocalizedDateTimePattern(
FormatStyle.SHORT,
FormatStyle.MEDIUM,
IsoChronology.INSTANCE,
locale);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern, locale);
return formatter.format(ldt);
}
private static String toLocalString2(TbDate d, Locale locale, ZoneId tz) {
DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
dateFormat.setTimeZone(TimeZone.getTimeZone(tz));
return dateFormat.format(d);
}
} }

74
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfigu
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.DeviceUpdatedEvent;
@ -113,19 +114,25 @@ public class SnmpTransportContext extends TransportContext {
SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
DeviceSessionContext deviceSessionContext; DeviceSessionContext sessionContext;
try { try {
deviceSessionContext = new DeviceSessionContext( sessionContext = DeviceSessionContext.builder()
device, deviceProfile, credentials.getCredentialsId(), .tenantId(deviceProfile.getTenantId())
profileTransportConfiguration, deviceTransportConfiguration, this .device(device)
); .deviceProfile(deviceProfile)
registerSessionMsgListener(deviceSessionContext); .token(credentials.getCredentialsId())
.profileTransportConfiguration(profileTransportConfiguration)
.deviceTransportConfiguration(deviceTransportConfiguration)
.snmpTransportContext(this)
.build();
registerSessionMsgListener(sessionContext);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString()); log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString());
transportService.errorEvent(device.getTenantId(), device.getId(), "sessionEstablishing", e);
return; return;
} }
sessions.put(device.getId(), deviceSessionContext); sessions.put(device.getId(), sessionContext);
snmpTransportService.createQueryingTasks(deviceSessionContext); snmpTransportService.createQueryingTasks(sessionContext);
log.info("Established SNMP device session for device {}", device.getId()); log.info("Established SNMP device session for device {}", device.getId());
} }
@ -148,14 +155,17 @@ public class SnmpTransportContext extends TransportContext {
sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration);
snmpTransportService.cancelQueryingTasks(sessionContext); snmpTransportService.cancelQueryingTasks(sessionContext);
snmpTransportService.createQueryingTasks(sessionContext); snmpTransportService.createQueryingTasks(sessionContext);
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null);
} else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) {
sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration);
sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration);
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null);
} else { } else {
log.trace("Configuration of the device {} was not updated", device); log.trace("Configuration of the device {} was not updated", device);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage()); log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage());
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, false, e);
destroyDeviceSession(sessionContext); destroyDeviceSession(sessionContext);
} }
} }
@ -168,43 +178,58 @@ public class SnmpTransportContext extends TransportContext {
transportService.deregisterSession(sessionContext.getSessionInfo()); transportService.deregisterSession(sessionContext.getSessionInfo());
snmpTransportService.cancelQueryingTasks(sessionContext); snmpTransportService.cancelQueryingTasks(sessionContext);
sessions.remove(sessionContext.getDeviceId()); sessions.remove(sessionContext.getDeviceId());
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STOPPED, true, null);
log.trace("Unregistered and removed session"); log.trace("Unregistered and removed session");
} }
private void registerSessionMsgListener(DeviceSessionContext deviceSessionContext) { private void registerSessionMsgListener(DeviceSessionContext sessionContext) {
transportService.process(DeviceTransportType.SNMP, transportService.process(DeviceTransportType.SNMP,
TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceSessionContext.getToken()).build(), TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(),
new TransportServiceCallback<>() { new TransportServiceCallback<>() {
@Override @Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) { public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (msg.hasDeviceInfo()) { if (msg.hasDeviceInfo()) {
SessionInfoProto sessionInfo = SessionInfoCreator.create( registerTransportSession(sessionContext, msg);
msg, SnmpTransportContext.this, UUID.randomUUID() sessionContext.setSessionTimeoutHandler(() -> {
); registerTransportSession(sessionContext, msg);
});
transportService.registerAsyncSession(sessionInfo, deviceSessionContext); transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STARTED, true, null);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), TransportServiceCallback.EMPTY);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), TransportServiceCallback.EMPTY);
deviceSessionContext.setSessionInfo(sessionInfo);
deviceSessionContext.setDeviceInfo(msg.getDeviceInfo());
deviceSessionContext.setConnected(true);
} else { } else {
log.warn("[{}] Failed to process device auth", deviceSessionContext.getDeviceId()); log.warn("[{}] Failed to process device auth", sessionContext.getDeviceId());
} }
} }
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
log.warn("[{}] Failed to process device auth: {}", deviceSessionContext.getDeviceId(), e); log.warn("[{}] Failed to process device auth: {}", sessionContext.getDeviceId(), e);
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STARTED, false, e);
} }
}); });
} }
private void registerTransportSession(DeviceSessionContext deviceSessionContext, ValidateDeviceCredentialsResponse msg) {
SessionInfoProto sessionInfo = SessionInfoCreator.create(
msg, SnmpTransportContext.this, UUID.randomUUID()
);
log.debug("Registering transport session: {}", sessionInfo);
transportService.registerAsyncSession(sessionInfo, deviceSessionContext);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder()
.setSessionType(TransportProtos.SessionType.ASYNC)
.build(), TransportServiceCallback.EMPTY);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder()
.setSessionType(TransportProtos.SessionType.ASYNC)
.build(), TransportServiceCallback.EMPTY);
deviceSessionContext.setSessionInfo(sessionInfo);
deviceSessionContext.setDeviceInfo(msg.getDeviceInfo());
deviceSessionContext.setConnected(true);
}
@EventListener(DeviceUpdatedEvent.class) @EventListener(DeviceUpdatedEvent.class)
public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) { public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) {
Device device = deviceUpdatedEvent.getDevice(); Device device = deviceUpdatedEvent.getDevice();
log.trace("Got creating or updating device event for device {}", device); log.debug("Got creating or updating device event for device {}", device);
DeviceTransportType transportType = Optional.ofNullable(device.getDeviceData().getTransportConfiguration()) DeviceTransportType transportType = Optional.ofNullable(device.getDeviceData().getTransportConfiguration())
.map(DeviceTransportConfiguration::getType) .map(DeviceTransportConfiguration::getType)
.orElse(null); .orElse(null);
@ -238,6 +263,7 @@ public class SnmpTransportContext extends TransportContext {
} }
public void onDeviceProfileUpdated(DeviceProfile deviceProfile, DeviceSessionContext sessionContext) { public void onDeviceProfileUpdated(DeviceProfile deviceProfile, DeviceSessionContext sessionContext) {
log.debug("Handling device profile {} update event for device {}", deviceProfile.getId(), sessionContext.getDeviceId());
updateDeviceSession(sessionContext, sessionContext.getDevice(), deviceProfile); updateDeviceSession(sessionContext, sessionContext.getDevice(), deviceProfile);
} }

97
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java

@ -15,7 +15,9 @@
*/ */
package org.thingsboard.server.transport.snmp.service; package org.thingsboard.server.transport.snmp.service;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU; import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU; import org.snmp4j.ScopedPDU;
@ -25,43 +27,61 @@ import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString; import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable; import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding; import org.snmp4j.smi.VariableBinding;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import org.thingsboard.server.common.data.util.TypeCastUtil;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream;
@TbSnmpTransportComponent @TbSnmpTransportComponent
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class PduService { public class PduService {
public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
PDU pdu = setUpPdu(sessionContext);
pdu.setType(communicationConfig.getMethod().getCode()); @Value("${transport.snmp.max_request_oids:100}")
pdu.addAll(communicationConfig.getAllMappings().stream() private int maxRequestOids;
.filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey()))
.map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) @Value("${transport.snmp.response.ignore_type_cast_errors:false}")
.map(value -> { private boolean ignoreTypeCastErrors;
Variable variable = toSnmpVariable(value, mapping.getDataType());
return new VariableBinding(new OID(mapping.getOid()), variable); public List<PDU> createPdus(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
}) List<PDU> pdus = new ArrayList<>();
.orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) List<SnmpMapping> allMappings = communicationConfig.getAllMappings();
.collect(Collectors.toList()));
for (List<SnmpMapping> mappings : Lists.partition(allMappings, maxRequestOids)) {
PDU pdu = setUpPdu(sessionContext);
pdu.setType(communicationConfig.getMethod().getCode());
pdu.addAll(mappings.stream()
.filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey()))
.map(mapping -> Optional.ofNullable(values.get(mapping.getKey()))
.map(value -> {
Variable variable = toSnmpVariable(value, mapping.getDataType());
return new VariableBinding(new OID(mapping.getOid()), variable);
})
.orElseGet(() -> new VariableBinding(new OID(mapping.getOid()))))
.collect(Collectors.toList()));
if (pdu.size() > 0) {
pdus.add(pdu);
}
}
return pdu; return pdus;
} }
public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) { public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) {
@ -116,8 +136,8 @@ public class PduService {
} }
public JsonObject processPdu(PDU pdu, List<SnmpMapping> responseMappings) { public JsonObject processPdus(List<PDU> pdus, List<SnmpMapping> responseMappings) {
Map<OID, String> values = processPdu(pdu); Map<OID, String> values = processPdus(pdus);
Map<OID, SnmpMapping> mappings = new HashMap<>(); Map<OID, SnmpMapping> mappings = new HashMap<>();
if (responseMappings != null) { if (responseMappings != null) {
@ -143,29 +163,40 @@ public class PduService {
return data; return data;
} }
public Map<OID, String> processPdu(PDU pdu) { public Map<OID, String> processPdus(List<PDU> pdus) {
return IntStream.range(0, pdu.size()) return pdus.stream()
.mapToObj(pdu::get) .flatMap(pdu -> pdu.getVariableBindings().stream())
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null))
.collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString)); .collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString));
} }
public void processValue(String key, DataType dataType, String value, JsonObject result) { public void processValue(String key, DataType dataType, String value, JsonObject result) {
switch (dataType) { try {
case LONG: switch (dataType) {
result.addProperty(key, Long.parseLong(value)); case STRING:
break; case JSON:
case BOOLEAN: result.addProperty(key, value);
result.addProperty(key, Boolean.parseBoolean(value)); break;
break; case LONG:
case DOUBLE: case DOUBLE:
result.addProperty(key, Double.parseDouble(value)); result.addProperty(key, TypeCastUtil.castToNumber(value).getValue());
break; break;
case STRING: case BOOLEAN:
case JSON: if (StringUtils.equalsAnyIgnoreCase(value, "true", "false")) {
default: result.addProperty(key, Boolean.parseBoolean(value));
result.addProperty(key, value); } else {
throw new IllegalArgumentException("Can't parse '" + value + "' as boolean");
}
break;
}
} catch (IllegalArgumentException e) {
if (ignoreTypeCastErrors) {
log.debug("Ignoring value '{}' for key '{}' because of data type mismatch ({} required)", value, key, dataType);
} else {
throw e;
}
} }
} }
} }

221
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java

@ -17,10 +17,13 @@ package org.thingsboard.server.transport.snmp.service;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.PDU; import org.snmp4j.PDU;
import org.snmp4j.Snmp; import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping; import org.snmp4j.TransportMapping;
@ -29,10 +32,15 @@ import org.snmp4j.mp.MPv3;
import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM; import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.OctetString; import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -45,15 +53,16 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.SnmpTransportContext;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
@ -71,9 +80,11 @@ import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class SnmpTransportService implements TbTransportService { public class SnmpTransportService implements TbTransportService, CommandResponder {
private final TransportService transportService; private final TransportService transportService;
private final PduService pduService; private final PduService pduService;
@Autowired @Lazy
private SnmpTransportContext transportContext;
@Getter @Getter
private Snmp snmp; private Snmp snmp;
@ -83,6 +94,8 @@ public class SnmpTransportService implements TbTransportService {
private final Map<SnmpCommunicationSpec, ResponseDataMapper> responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class); private final Map<SnmpCommunicationSpec, ResponseDataMapper> responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class);
private final Map<SnmpCommunicationSpec, ResponseProcessor> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); private final Map<SnmpCommunicationSpec, ResponseProcessor> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class);
@Value("${transport.snmp.bind_port:1620}")
private Integer snmpBindPort;
@Value("${transport.snmp.response_processing.parallelism_level}") @Value("${transport.snmp.response_processing.parallelism_level}")
private Integer responseProcessingParallelismLevel; private Integer responseProcessingParallelismLevel;
@Value("${transport.snmp.underlying_protocol}") @Value("${transport.snmp.underlying_protocol}")
@ -114,15 +127,16 @@ public class SnmpTransportService implements TbTransportService {
TransportMapping<?> transportMapping; TransportMapping<?> transportMapping;
switch (snmpUnderlyingProtocol) { switch (snmpUnderlyingProtocol) {
case "udp": case "udp":
transportMapping = new DefaultUdpTransportMapping(); transportMapping = new DefaultUdpTransportMapping(new UdpAddress(snmpBindPort));
break; break;
case "tcp": case "tcp":
transportMapping = new DefaultTcpTransportMapping(); transportMapping = new DefaultTcpTransportMapping(new TcpAddress(snmpBindPort));
break; break;
default: default:
throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported");
} }
snmp = new Snmp(transportMapping); snmp = new Snmp(transportMapping);
snmp.addNotificationListener(transportMapping, transportMapping.getListenAddress(), this);
snmp.listen(); snmp.listen();
USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
@ -143,6 +157,7 @@ public class SnmpTransportService implements TbTransportService {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString());
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), config.getSpec().getLabel(), e);
} }
}, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS);
}) })
@ -161,18 +176,24 @@ public class SnmpTransportService implements TbTransportService {
} }
private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) { private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
PDU request = pduService.createPdu(sessionContext, communicationConfig, values); List<PDU> request = pduService.createPdus(sessionContext, communicationConfig, values);
RequestInfo requestInfo = new RequestInfo(communicationConfig.getSpec(), communicationConfig.getAllMappings()); RequestContext requestContext = RequestContext.builder()
sendRequest(sessionContext, request, requestInfo); .communicationSpec(communicationConfig.getSpec())
.method(communicationConfig.getMethod())
.responseMappings(communicationConfig.getAllMappings())
.requestSize(request.size())
.build();
sendRequest(sessionContext, request, requestContext);
} }
private void sendRequest(DeviceSessionContext sessionContext, PDU request, RequestInfo requestInfo) { private void sendRequest(DeviceSessionContext sessionContext, List<PDU> request, RequestContext requestContext) {
if (request.size() > 0) { for (PDU pdu : request) {
log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); log.debug("Executing SNMP request for device {} with {} variable bindings", sessionContext.getDeviceId(), pdu.size());
try { try {
snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext); snmp.send(pdu, sessionContext.getTarget(), requestContext, sessionContext);
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString()); log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString());
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e);
} }
} }
} }
@ -215,51 +236,135 @@ public class SnmpTransportService implements TbTransportService {
DataType dataType = snmpMapping.getDataType(); DataType dataType = snmpMapping.getDataType();
PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType); PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType);
RequestInfo requestInfo = new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), communicationConfig.getSpec(), communicationConfig.getAllMappings()); RequestContext requestContext = RequestContext.builder()
sendRequest(sessionContext, request, requestInfo); .requestId(toDeviceRpcRequestMsg.getRequestId())
.communicationSpec(communicationConfig.getSpec())
.method(snmpMethod)
.responseMappings(communicationConfig.getAllMappings())
.requestSize(1)
.build();
sendRequest(sessionContext, List.of(request), requestContext);
} }
public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) {
((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext);
RequestContext requestContext = (RequestContext) event.getUserObject();
if (event.getError() != null) { if (event.getError() != null) {
log.warn("SNMP response error: {}", event.getError().toString()); log.warn("SNMP response error: {}", event.getError().toString());
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException(event.getError()));
return; return;
} }
PDU response = event.getResponse(); PDU responsePdu = event.getResponse();
if (response == null) { if (log.isTraceEnabled()) {
log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); log.trace("Received PDU for device {}: {}", sessionContext.getDeviceId(), responsePdu);
}
List<PDU> response;
if (requestContext.getRequestSize() == 1) {
if (responsePdu == null) {
log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID());
if (requestContext.getMethod() == SnmpMethod.GET) {
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException("No response from device"));
}
return;
}
response = List.of(responsePdu);
} else {
List<PDU> responseParts = requestContext.getResponseParts();
responseParts.add(responsePdu);
if (responseParts.size() == requestContext.getRequestSize()) {
response = new ArrayList<>();
for (PDU responsePart : responseParts) {
if (responsePart != null) {
response.add(responsePart);
}
}
log.debug("All response parts are collected for request to device {}", sessionContext.getDeviceId());
} else {
log.trace("Awaiting other response parts for request to device {}", sessionContext.getDeviceId());
return;
}
}
responseProcessingExecutor.execute(() -> {
try {
processResponse(sessionContext, response, requestContext);
} catch (Exception e) {
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e);
}
});
}
/*
* SNMP notifications handler
*
* TODO: add check for host uniqueness when saving device (for backward compatibility - only for the ones using from-device RPC requests)
*
* NOTE: SNMP TRAPs support won't work properly when there is more than one SNMP transport,
* due to load-balancing of requests from devices: session might not be on this instance
* */
@Override
public void processPdu(CommandResponderEvent event) {
Address sourceAddress = event.getPeerAddress();
DeviceSessionContext sessionContext = transportContext.getSessions().stream()
.filter(session -> session.getTarget().getAddress().equals(sourceAddress))
.findFirst().orElse(null);
if (sessionContext == null) {
log.warn("SNMP TRAP processing failed: couldn't find device session for address {}", sourceAddress);
return; return;
} }
RequestInfo requestInfo = (RequestInfo) event.getUserObject(); try {
processIncomingTrap(sessionContext, event);
} catch (Throwable e) {
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(),
SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST.getLabel(), e);
}
}
private void processIncomingTrap(DeviceSessionContext sessionContext, CommandResponderEvent event) {
PDU pdu = event.getPDU();
if (pdu == null) {
log.warn("Got empty trap from device {}", sessionContext.getDeviceId());
throw new IllegalArgumentException("Received TRAP with no data");
}
log.debug("Processing SNMP trap from device {} (PDU: {}}", sessionContext.getDeviceId(), pdu);
SnmpCommunicationConfig communicationConfig = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream()
.filter(config -> config.getSpec() == SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST).findFirst()
.orElseThrow(() -> new IllegalArgumentException("No config found for to-server RPC requests"));
RequestContext requestContext = RequestContext.builder()
.communicationSpec(communicationConfig.getSpec())
.responseMappings(communicationConfig.getAllMappings())
.method(SnmpMethod.TRAP)
.build();
responseProcessingExecutor.execute(() -> { responseProcessingExecutor.execute(() -> {
processResponse(sessionContext, response, requestInfo); processResponse(sessionContext, List.of(pdu), requestContext);
}); });
} }
private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) { private void processResponse(DeviceSessionContext sessionContext, List<PDU> response, RequestContext requestContext) {
ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationSpec()); ResponseProcessor responseProcessor = responseProcessors.get(requestContext.getCommunicationSpec());
if (responseProcessor == null) return; if (responseProcessor == null) return;
JsonObject responseData = responseDataMappers.get(requestInfo.getCommunicationSpec()).map(response, requestInfo); JsonObject responseData = responseDataMappers.get(requestContext.getCommunicationSpec()).map(response, requestContext);
if (responseData.size() == 0) {
if (responseData.entrySet().isEmpty()) { log.warn("No values in the SNMP response for device {}", sessionContext.getDeviceId());
log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID()); throw new IllegalArgumentException("No values in the response");
return;
} }
responseProcessor.process(responseData, requestInfo, sessionContext); responseProcessor.process(responseData, requestContext, sessionContext);
reportActivity(sessionContext.getSessionInfo()); reportActivity(sessionContext.getSessionInfo());
} }
private void configureResponseDataMappers() { private void configureResponseDataMappers() {
responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdu, requestInfo) -> { responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdus, requestContext) -> {
JsonObject responseData = new JsonObject(); JsonObject responseData = new JsonObject();
pduService.processPdu(pdu).forEach((oid, value) -> { pduService.processPdus(pdus).forEach((oid, value) -> {
requestInfo.getResponseMappings().stream() requestContext.getResponseMappings().stream()
.filter(snmpMapping -> snmpMapping.getOid().equals(oid.toDottedString())) .filter(snmpMapping -> snmpMapping.getOid().equals(oid.toDottedString()))
.findFirst() .findFirst()
.ifPresent(snmpMapping -> { .ifPresent(snmpMapping -> {
@ -269,8 +374,8 @@ public class SnmpTransportService implements TbTransportService {
return responseData; return responseData;
}); });
ResponseDataMapper defaultResponseDataMapper = (pdu, requestInfo) -> { ResponseDataMapper defaultResponseDataMapper = (pdus, requestContext) -> {
return pduService.processPdu(pdu, requestInfo.getResponseMappings()); return pduService.processPdus(pdus, requestContext.getResponseMappings());
}; };
Arrays.stream(SnmpCommunicationSpec.values()) Arrays.stream(SnmpCommunicationSpec.values())
.forEach(communicationSpec -> { .forEach(communicationSpec -> {
@ -279,34 +384,39 @@ public class SnmpTransportService implements TbTransportService {
} }
private void configureResponseProcessors() { private void configureResponseProcessors() {
responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestInfo, sessionContext) -> { responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestContext, sessionContext) -> {
TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData); TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData);
transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null); transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null);
log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData);
}); });
responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestInfo, sessionContext) -> { responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestContext, sessionContext) -> {
TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData); TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData);
transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null); transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null);
log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData);
}); });
responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestInfo, sessionContext) -> { responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestContext, sessionContext) -> {
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(requestInfo.getRequestId()) .setRequestId(requestContext.getRequestId())
.setPayload(JsonConverter.toJson(responseData)) .setPayload(JsonConverter.toJson(responseData))
.build(); .build();
transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null); transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null);
log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId()); log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId());
}); });
responseProcessors.put(SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST, (responseData, requestContext, sessionContext) -> {
TransportProtos.ToServerRpcRequestMsg toServerRpcRequestMsg = TransportProtos.ToServerRpcRequestMsg.newBuilder()
.setRequestId(0)
.setMethodName(requestContext.getMethod().name())
.setParams(JsonConverter.toJson(responseData))
.build();
transportService.process(sessionContext.getSessionInfo(), toServerRpcRequestMsg, null);
});
} }
private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() transportService.reportActivity(sessionInfo);
.setAttributeSubscription(true)
.setRpcSubscription(true)
.setLastActivityTime(System.currentTimeMillis())
.build(), TransportServiceCallback.EMPTY);
} }
@ -335,29 +445,34 @@ public class SnmpTransportService implements TbTransportService {
} }
@Data @Data
private static class RequestInfo { private static class RequestContext {
private Integer requestId; private final Integer requestId;
private SnmpCommunicationSpec communicationSpec; private final SnmpCommunicationSpec communicationSpec;
private List<SnmpMapping> responseMappings; private final SnmpMethod method;
private final List<SnmpMapping> responseMappings;
public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec, List<SnmpMapping> responseMappings) { private final int requestSize;
this.requestId = requestId; private List<PDU> responseParts;
this.communicationSpec = communicationSpec;
this.responseMappings = responseMappings;
}
public RequestInfo(SnmpCommunicationSpec communicationSpec, List<SnmpMapping> responseMappings) { @Builder
public RequestContext(Integer requestId, SnmpCommunicationSpec communicationSpec, SnmpMethod method, List<SnmpMapping> responseMappings, int requestSize) {
this.requestId = requestId;
this.communicationSpec = communicationSpec; this.communicationSpec = communicationSpec;
this.method = method;
this.responseMappings = responseMappings; this.responseMappings = responseMappings;
this.requestSize = requestSize;
if (requestSize > 1) {
this.responseParts = Collections.synchronizedList(new ArrayList<>());
}
} }
} }
private interface ResponseDataMapper { private interface ResponseDataMapper {
JsonObject map(PDU pdu, RequestInfo requestInfo); JsonObject map(List<PDU> pdus, RequestContext requestContext);
} }
private interface ResponseProcessor { private interface ResponseProcessor {
void process(JsonObject responseData, RequestInfo requestInfo, DeviceSessionContext sessionContext); void process(JsonObject responseData, RequestContext requestContext, DeviceSessionContext sessionContext);
} }
} }

31
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.snmp.session; package org.thingsboard.server.transport.snmp.session;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -26,9 +27,12 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
@ -57,17 +61,22 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
private SnmpDeviceTransportConfiguration deviceTransportConfiguration; private SnmpDeviceTransportConfiguration deviceTransportConfiguration;
@Getter @Getter
private final Device device; private final Device device;
@Getter
private final TenantId tenantId;
private final SnmpTransportContext snmpTransportContext; private final SnmpTransportContext snmpTransportContext;
private final AtomicInteger msgIdSeq = new AtomicInteger(0); private final AtomicInteger msgIdSeq = new AtomicInteger(0);
@Getter @Getter
private boolean isActive = true; private boolean isActive = true;
@Setter
private Runnable sessionTimeoutHandler;
@Getter @Getter
private final List<ScheduledFuture<?>> queryingTasks = new LinkedList<>(); private final List<ScheduledFuture<?>> queryingTasks = new LinkedList<>();
public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, @Builder
public DeviceSessionContext(TenantId tenantId, Device device, DeviceProfile deviceProfile, String token,
SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration,
SnmpDeviceTransportConfiguration deviceTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration,
SnmpTransportContext snmpTransportContext) throws Exception { SnmpTransportContext snmpTransportContext) throws Exception {
@ -75,6 +84,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
super.setDeviceId(device.getId()); super.setDeviceId(device.getId());
super.setDeviceProfile(deviceProfile); super.setDeviceProfile(deviceProfile);
this.device = device; this.device = device;
this.tenantId = tenantId;
this.token = token; this.token = token;
this.snmpTransportContext = snmpTransportContext; this.snmpTransportContext = snmpTransportContext;
@ -131,19 +141,32 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
@Override @Override
public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) { public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) {
log.trace("[{}] Received attributes update notification to device", sessionId); log.trace("[{}] Received attributes update notification to device", sessionId);
snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); try {
snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification);
} catch (Exception e) {
snmpTransportContext.getTransportService().errorEvent(getTenantId(), getDeviceId(), SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING.getLabel(), e);
}
} }
@Override @Override
public void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification) { public void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification) {
log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
if (sessionCloseNotification.getMessage().equals(DefaultTransportService.SESSION_EXPIRED_MESSAGE)) {
if (sessionTimeoutHandler != null) {
sessionTimeoutHandler.run();
}
}
} }
@Override @Override
public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) { public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) {
log.trace("[{}] Received RPC command to device", sessionId); log.trace("[{}] Received RPC command to device", sessionId);
snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); try {
snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest);
snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
} catch (Exception e) {
snmpTransportContext.getTransportService().errorEvent(getTenantId(), getDeviceId(), SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST.getLabel(), e);
}
} }
@Override @Override

23
common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.transport.snmp; package org.thingsboard.server.transport.snmp;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.CommunityTarget; import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU; import org.snmp4j.PDU;
import org.snmp4j.Snmp; import org.snmp4j.Snmp;
@ -35,7 +34,6 @@ import org.snmp4j.agent.mo.snmp.SnmpTargetMIB;
import org.snmp4j.agent.mo.snmp.StorageType; import org.snmp4j.agent.mo.snmp.StorageType;
import org.snmp4j.agent.mo.snmp.VacmMIB; import org.snmp4j.agent.mo.snmp.VacmMIB;
import org.snmp4j.agent.security.MutableVACM; import org.snmp4j.agent.security.MutableVACM;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants; import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityModel;
@ -53,27 +51,11 @@ import org.snmp4j.transport.TransportMappings;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class SnmpDeviceSimulatorV2 extends BaseAgent { public class SnmpDeviceSimulatorV2 extends BaseAgent {
public static class RequestProcessor extends CommandProcessor {
private final Consumer<CommandResponderEvent> processor;
public RequestProcessor(Consumer<CommandResponderEvent> processor) {
super(new OctetString(MPv3.createLocalEngineID()));
this.processor = processor;
}
@Override
public void processPdu(CommandResponderEvent event) {
processor.accept(event);
}
}
private final Target target; private final Target target;
private final Address address; private final Address address;
private Snmp snmp; private Snmp snmp;
@ -81,10 +63,7 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent {
private final String password; private final String password;
public SnmpDeviceSimulatorV2(int port, String password) throws IOException { public SnmpDeviceSimulatorV2(int port, String password) throws IOException {
super(new File("conf.agent"), new File("bootCounter.agent"), new RequestProcessor(event -> { super(new File("conf.agent"), new File("bootCounter.agent"), new CommandProcessor(new OctetString("12312")));
System.out.println("aboba");
((Snmp) event.getSource()).cancel(event.getPDU(), event1 -> System.out.println("canceled"));
}));
CommunityTarget target = new CommunityTarget(); CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(password)); target.setCommunity(new OctetString(password));
this.address = GenericAddress.parse("udp:0.0.0.0/" + port); this.address = GenericAddress.parse("udp:0.0.0.0/" + port);

43
common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java

@ -16,34 +16,41 @@
package org.thingsboard.server.transport.snmp; package org.thingsboard.server.transport.snmp;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
public class SnmpTestV2 { public class SnmpTestV2 {
public static void main(String[] args) throws IOException {
SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public");
device.start(); private static final Scanner scanner = new Scanner(System.in);
device.setUpMappings(Map.of(
".1.3.6.1.2.1.1.1.50", "12",
".1.3.6.1.2.1.2.1.52", "56",
".1.3.6.1.2.1.3.1.54", "yes",
".1.3.6.1.2.1.7.1.58", ""
));
public static void main(String[] args) throws IOException {
SnmpDeviceSimulatorV2 client = new SnmpDeviceSimulatorV2(1610, "public");
// while (true) { client.start();
// new Scanner(System.in).nextLine(); Map<String, String> mappings = new HashMap<>();
// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12")); // for (int i = 1; i <= 500; i++) {
// System.out.println("sent"); // String oid = String.format(".1.3.6.1.2.1.%s.1.52", i);
// mappings.put(oid, "value_" + i);
// } // }
mappings.put("1.3.6.1.2.1.266.1.52", "****");
// Snmp snmp = new Snmp(device.transportMappings[0]); client.setUpMappings(mappings);
// device.snmp.addCommandResponder(event -> { inputTraps(client);
// System.out.println(event);
// }); scanner.nextLine();
}
new Scanner(System.in).nextLine(); private static void inputTraps(SnmpDeviceSimulatorV2 client) throws IOException {
while (true) {
String data = scanner.nextLine();
if (!data.isEmpty()) {
client.sendTrap("127.0.0.1", 1620, Map.of(
"1.3.6.1.2.1.266.1.52", data + " (266)",
"1.3.6.1.2.1.267.1.52", data + " (267)"
));
}
}
} }
} }

7
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -17,6 +17,9 @@ package org.thingsboard.server.common.transport;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
@ -144,6 +147,10 @@ public interface TransportService {
void reportActivity(SessionInfoProto sessionInfo); void reportActivity(SessionInfoProto sessionInfo);
void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error);
void errorEvent(TenantId tenantId, DeviceId deviceId, String method, Throwable error);
void deregisterSession(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo);
void log(SessionInfoProto sessionInfo, String msg); void log(SessionInfoProto sessionInfo, String msg);

49
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -22,6 +22,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -49,6 +50,8 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.limit.LimitedApi;
import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
@ -827,6 +830,37 @@ public class DefaultTransportService implements TransportService {
sessionsToRemove.forEach(sessionsActivity::remove); sessionsToRemove.forEach(sessionsActivity::remove);
} }
@Override
public void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error) {
ToCoreMsg msg = ToCoreMsg.newBuilder()
.setLifecycleEventMsg(TransportProtos.LifecycleEventProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setEntityIdMSB(deviceId.getId().getMostSignificantBits())
.setEntityIdLSB(deviceId.getId().getLeastSignificantBits())
.setServiceId(serviceInfoProvider.getServiceId())
.setLcEventType(eventType.name())
.setSuccess(success)
.setError(error != null ? ExceptionUtils.getStackTrace(error) : ""))
.build();
sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY);
}
@Override
public void errorEvent(TenantId tenantId, DeviceId deviceId, String method, Throwable error) {
ToCoreMsg msg = ToCoreMsg.newBuilder()
.setErrorEventMsg(TransportProtos.ErrorEventProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setEntityIdMSB(deviceId.getId().getMostSignificantBits())
.setEntityIdLSB(deviceId.getId().getLeastSignificantBits())
.setServiceId(serviceInfoProvider.getServiceId())
.setMethod(method)
.setError(ExceptionUtils.getStackTrace(error)))
.build();
sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY);
}
@Override @Override
public SessionMetaData registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { public SessionMetaData registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) {
SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener);
@ -1118,18 +1152,21 @@ public class DefaultTransportService implements TransportService {
} }
protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) { protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); ToCoreMsg toCoreMsg = ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build();
sendToCore(getTenantId(sessionInfo), getDeviceId(sessionInfo), toCoreMsg, getRoutingKey(sessionInfo), callback);
}
private void sendToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, UUID routingKey, TransportServiceCallback<Void> callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, entityId, tpi.getFullTopicName(), msg);
} }
TransportTbQueueCallback transportTbQueueCallback = callback != null ? TransportTbQueueCallback transportTbQueueCallback = callback != null ?
new TransportTbQueueCallback(callback) : null; new TransportTbQueueCallback(callback) : null;
tbCoreProducerStats.incrementTotal(); tbCoreProducerStats.incrementTotal();
StatsCallback wrappedCallback = new StatsCallback(transportTbQueueCallback, tbCoreProducerStats); StatsCallback wrappedCallback = new StatsCallback(transportTbQueueCallback, tbCoreProducerStats);
tbCoreMsgProducer.send(tpi, tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(routingKey, msg), wrappedCallback);
new TbProtoQueueMsg<>(getRoutingKey(sessionInfo),
ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()),
wrappedCallback);
} }
private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {

21
common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java

@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.collect.Lists;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
@ -35,7 +36,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -228,27 +228,24 @@ public class JacksonUtil {
return node; return node;
} }
public static void replaceUuidsRecursively(JsonNode node, Set<String> skipFieldsSet, Pattern includedFieldsPattern, UnaryOperator<UUID> replacer) { public static void replaceUuidsRecursively(JsonNode node, Set<String> skippedRootFields, Pattern includedFieldsPattern, UnaryOperator<UUID> replacer, boolean root) {
if (node == null) { if (node == null) {
return; return;
} }
if (node.isObject()) { if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node; ObjectNode objectNode = (ObjectNode) node;
List<String> fieldNames = new ArrayList<>(objectNode.size()); List<String> fieldNames = Lists.newArrayList(objectNode.fieldNames());
objectNode.fieldNames().forEachRemaining(fieldNames::add);
for (String fieldName : fieldNames) { for (String fieldName : fieldNames) {
if (skipFieldsSet.contains(fieldName)) { if (root && skippedRootFields.contains(fieldName)) {
continue; continue;
} }
if (includedFieldsPattern != null) {
if (!RegexUtils.matches(fieldName, includedFieldsPattern)) {
continue;
}
}
var child = objectNode.get(fieldName); var child = objectNode.get(fieldName);
if (child.isObject() || child.isArray()) { if (child.isObject() || child.isArray()) {
replaceUuidsRecursively(child, skipFieldsSet, includedFieldsPattern, replacer); replaceUuidsRecursively(child, skippedRootFields, includedFieldsPattern, replacer, false);
} else if (child.isTextual()) { } else if (child.isTextual()) {
if (includedFieldsPattern != null && !RegexUtils.matches(fieldName, includedFieldsPattern)) {
continue;
}
String text = child.asText(); String text = child.asText();
String newText = RegexUtils.replace(text, RegexUtils.UUID_PATTERN, uuid -> replacer.apply(UUID.fromString(uuid)).toString()); String newText = RegexUtils.replace(text, RegexUtils.UUID_PATTERN, uuid -> replacer.apply(UUID.fromString(uuid)).toString());
if (!text.equals(newText)) { if (!text.equals(newText)) {
@ -261,7 +258,7 @@ public class JacksonUtil {
for (int i = 0; i < array.size(); i++) { for (int i = 0; i < array.size(); i++) {
JsonNode arrayElement = array.get(i); JsonNode arrayElement = array.get(i);
if (arrayElement.isObject() || arrayElement.isArray()) { if (arrayElement.isObject() || arrayElement.isArray()) {
replaceUuidsRecursively(arrayElement, skipFieldsSet, includedFieldsPattern, replacer); replaceUuidsRecursively(arrayElement, skippedRootFields, includedFieldsPattern, replacer, false);
} else if (arrayElement.isTextual()) { } else if (arrayElement.isTextual()) {
String text = arrayElement.asText(); String text = arrayElement.asText();
String newText = RegexUtils.replace(text, RegexUtils.UUID_PATTERN, uuid -> replacer.apply(UUID.fromString(uuid)).toString()); String newText = RegexUtils.replace(text, RegexUtils.UUID_PATTERN, uuid -> replacer.apply(UUID.fromString(uuid)).toString());

7
dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java

@ -69,6 +69,8 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_DEVICE_ID = "Incorrect deviceId "; public static final String INCORRECT_DEVICE_ID = "Incorrect deviceId ";
public static final String DEFAULT_DEVICE_TELEMETRY_TOPIC = "v1/devices/me/telemetry"; public static final String DEFAULT_DEVICE_TELEMETRY_TOPIC = "v1/devices/me/telemetry";
public static final String HTTP_DEFAULT_PORT = "80";
public static final String HTTPS_DEFAULT_PORT = "443";
private final Map<String, Resource> certs = new ConcurrentHashMap<>(); private final Map<String, Resource> certs = new ConcurrentHashMap<>();
@ -225,8 +227,9 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
return null; return null;
} }
String hostName = getHost(baseUrl, properties); String hostName = getHost(baseUrl, properties);
String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); String propertiesPort = properties.getPort();
String port = (propertiesPort.isEmpty() || HTTP_DEFAULT_PORT.equals(propertiesPort) || HTTPS_DEFAULT_PORT.equals(propertiesPort))
? "" : ":" + propertiesPort;
return DeviceConnectivityUtil.getHttpPublishCommand(protocol, hostName, port, deviceCredentials); return DeviceConnectivityUtil.getHttpPublishCommand(protocol, hostName, port, deviceCredentials);
} }

2
dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java

@ -30,7 +30,7 @@ import java.util.List;
*/ */
public interface RuleNodeDao extends Dao<RuleNode> { public interface RuleNodeDao extends Dao<RuleNode> {
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search); List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String configurationSearch);
PageData<RuleNode> findAllRuleNodesByType(String type, PageLink pageLink); PageData<RuleNode> findAllRuleNodesByType(String type, PageLink pageLink);

23
dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java

@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.util.ReflectionUtils; import org.thingsboard.server.common.data.util.ReflectionUtils;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
@ -41,15 +40,14 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
@Component @Component
@Slf4j @Slf4j
public class RuleChainDataValidator extends DataValidator<RuleChain> { public class RuleChainDataValidator extends DataValidator<RuleChain> {
@Autowired
private RuleChainDao ruleChainDao;
@Autowired @Autowired
@Lazy @Lazy
private RuleChainService ruleChainService; private RuleChainService ruleChainService;
@ -88,15 +86,19 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
} }
} }
public static void validateMetaData(RuleChainMetaData ruleChainMetaData) { public static List<Throwable> validateMetaData(RuleChainMetaData ruleChainMetaData) {
ConstraintValidator.validateFields(ruleChainMetaData); ConstraintValidator.validateFields(ruleChainMetaData);
ruleChainMetaData.getNodes().forEach(RuleChainDataValidator::validateRuleNode); List<Throwable> throwables = ruleChainMetaData.getNodes().stream()
.map(RuleChainDataValidator::validateRuleNode)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) { if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections()); validateCircles(ruleChainMetaData.getConnections());
} }
return throwables;
} }
public static void validateRuleNode(RuleNode ruleNode) { public static Throwable validateRuleNode(RuleNode ruleNode) {
String errorPrefix = "'" + ruleNode.getName() + "' node configuration is invalid: "; String errorPrefix = "'" + ruleNode.getName() + "' node configuration is invalid: ";
ConstraintValidator.validateFields(ruleNode, errorPrefix); ConstraintValidator.validateFields(ruleNode, errorPrefix);
Object nodeConfig; Object nodeConfig;
@ -104,11 +106,12 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
Class<Object> nodeConfigType = ReflectionUtils.getAnnotationProperty(ruleNode.getType(), Class<Object> nodeConfigType = ReflectionUtils.getAnnotationProperty(ruleNode.getType(),
"org.thingsboard.rule.engine.api.RuleNode", "configClazz"); "org.thingsboard.rule.engine.api.RuleNode", "configClazz");
nodeConfig = JacksonUtil.treeToValue(ruleNode.getConfiguration(), nodeConfigType); nodeConfig = JacksonUtil.treeToValue(ruleNode.getConfiguration(), nodeConfigType);
} catch (Exception e) { } catch (Throwable t) {
log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(e)); log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(t));
return; return t;
} }
ConstraintValidator.validateFields(nodeConfig, errorPrefix); ConstraintValidator.validateFields(nodeConfig, errorPrefix);
return null;
} }
private static void validateCircles(List<NodeConnectionInfo> connectionInfos) { private static void validateCircles(List<NodeConnectionInfo> connectionInfos) {

4
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java

@ -56,8 +56,8 @@ public class JpaRuleNodeDao extends JpaAbstractDao<RuleNodeEntity, RuleNode> imp
} }
@Override @Override
public List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search) { public List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String configurationSearch) {
return DaoUtil.convertDataList(ruleNodeRepository.findRuleNodesByTenantIdAndType(tenantId.getId(), type, search)); return DaoUtil.convertDataList(ruleNodeRepository.findRuleNodesByTenantIdAndType(tenantId.getId(), type, configurationSearch));
} }
@Override @Override

13
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java

@ -29,19 +29,22 @@ import java.util.UUID;
public interface RuleNodeRepository extends JpaRepository<RuleNodeEntity, UUID> { public interface RuleNodeRepository extends JpaRepository<RuleNodeEntity, UUID> {
@Query("SELECT r FROM RuleNodeEntity r WHERE r.ruleChainId in " + @Query(nativeQuery = true, value = "SELECT * FROM rule_node r WHERE r.rule_chain_id in " +
"(select id from RuleChainEntity rc WHERE rc.tenantId = :tenantId) " + "(select id from rule_chain rc WHERE rc.tenant_id = :tenantId) AND r.type = :ruleType " +
"AND r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") " AND (:searchText IS NULL OR r.configuration ILIKE CONCAT('%', :searchText, '%'))")
List<RuleNodeEntity> findRuleNodesByTenantIdAndType(@Param("tenantId") UUID tenantId, List<RuleNodeEntity> findRuleNodesByTenantIdAndType(@Param("tenantId") UUID tenantId,
@Param("ruleType") String ruleType, @Param("ruleType") String ruleType,
@Param("searchText") String searchText); @Param("searchText") String searchText);
@Query("SELECT r FROM RuleNodeEntity r WHERE r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") @Query(nativeQuery = true, value = "SELECT * FROM rule_node r WHERE r.type = :ruleType " +
" AND (:searchText IS NULL OR r.configuration ILIKE CONCAT('%', :searchText, '%'))")
Page<RuleNodeEntity> findAllRuleNodesByType(@Param("ruleType") String ruleType, Page<RuleNodeEntity> findAllRuleNodesByType(@Param("ruleType") String ruleType,
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
@Query("SELECT r FROM RuleNodeEntity r WHERE r.type = :ruleType AND r.configurationVersion < :version AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") @Query(nativeQuery = true, value = "SELECT * FROM rule_node r WHERE r.type = :ruleType " +
" AND configuration_version < :version " +
" AND (:searchText IS NULL OR r.configuration ILIKE CONCAT('%', :searchText, '%'))")
Page<RuleNodeEntity> findAllRuleNodesByTypeAndVersionLessThan(@Param("ruleType") String ruleType, Page<RuleNodeEntity> findAllRuleNodesByTypeAndVersionLessThan(@Param("ruleType") String ruleType,
@Param("version") int version, @Param("version") int version,
@Param("searchText") String searchText, @Param("searchText") String searchText,

5
dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.util;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -48,8 +49,8 @@ public class KvUtils {
String key = tsKvEntry.getKey(); String key = tsKvEntry.getKey();
if (key == null) { if (StringUtils.isBlank(key)) {
throw new DataValidationException("Key can't be null"); throw new DataValidationException("Key can't be null or empty");
} }
if (key.length() > 255) { if (key.length() > 255) {

3
dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java

@ -23,7 +23,6 @@ import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -35,8 +34,6 @@ import static org.mockito.Mockito.verify;
@SpringBootTest(classes = RuleChainDataValidator.class) @SpringBootTest(classes = RuleChainDataValidator.class)
class RuleChainDataValidatorTest { class RuleChainDataValidatorTest {
@MockBean
RuleChainDao ruleChainDao;
@MockBean @MockBean
RuleChainService ruleChainService; RuleChainService ruleChainService;
@MockBean @MockBean

158
dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java

@ -0,0 +1,158 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.rule;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.util.concurrent.ListeningExecutorService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.AbstractJpaDaoTest;
import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleNodeDao;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class JpaRuleNodeDaoTest extends AbstractJpaDaoTest {
public static final int COUNT = 40;
public static final String PREFIX_FOR_RULE_NODE_NAME = "SEARCH_TEXT_";
List<UUID> ruleNodeIds;
TenantId tenantId1;
TenantId tenantId2;
RuleChainId ruleChainId1;
RuleChainId ruleChainId2;
@Autowired
private RuleChainDao ruleChainDao;
@Autowired
private RuleNodeDao ruleNodeDao;
ListeningExecutorService executor;
@Before
public void setUp() {
tenantId1 = TenantId.fromUUID(Uuids.timeBased());
ruleChainId1 = new RuleChainId(UUID.randomUUID());
tenantId2 = TenantId.fromUUID(Uuids.timeBased());
ruleChainId2 = new RuleChainId(UUID.randomUUID());
ruleNodeIds = createRuleNodes(tenantId1, tenantId2, ruleChainId1, ruleChainId2, COUNT);
}
@After
public void tearDown() throws Exception {
ruleNodeDao.removeAllByIds(ruleNodeIds);
if (executor != null) {
executor.shutdownNow();
}
}
@Test
public void testSaveRuleName0x00_thenSomeDatabaseException() {
RuleNode ruleNode = getRuleNode(ruleChainId1, "T", "\u0000");
assertThatThrownBy(() -> ruleNodeIds.add(ruleNodeDao.save(tenantId1, ruleNode).getUuidId()));
}
@Test
public void testFindRuleNodesByTenantIdAndType() {
List<RuleNode> ruleNodes1 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId1, "A", PREFIX_FOR_RULE_NODE_NAME);
assertEquals(20, ruleNodes1.size());
List<RuleNode> ruleNodes2 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId2, "B", PREFIX_FOR_RULE_NODE_NAME);
assertEquals(20, ruleNodes2.size());
ruleNodes1 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId1, "A", null);
assertEquals(20, ruleNodes1.size());
ruleNodes2 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId2, "B", null);
assertEquals(20, ruleNodes2.size());
}
@Test
public void testFindRuleNodesByType() {
PageData<RuleNode> ruleNodes = ruleNodeDao.findAllRuleNodesByType( "A", new PageLink(10, 0, PREFIX_FOR_RULE_NODE_NAME));
assertEquals(20, ruleNodes.getTotalElements());
assertEquals(2, ruleNodes.getTotalPages());
assertEquals(10, ruleNodes.getData().size());
ruleNodes = ruleNodeDao.findAllRuleNodesByType( "A", new PageLink(10, 0));
assertEquals(20, ruleNodes.getTotalElements());
assertEquals(2, ruleNodes.getTotalPages());
assertEquals(10, ruleNodes.getData().size());
}
@Test
public void testFindRuleNodesByTypeAndVersionLessThan() {
PageData<RuleNode> ruleNodes = ruleNodeDao.findAllRuleNodesByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0, PREFIX_FOR_RULE_NODE_NAME));
assertEquals(20, ruleNodes.getTotalElements());
assertEquals(2, ruleNodes.getTotalPages());
assertEquals(10, ruleNodes.getData().size());
ruleNodes = ruleNodeDao.findAllRuleNodesByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0));
assertEquals(20, ruleNodes.getTotalElements());
assertEquals(2, ruleNodes.getTotalPages());
assertEquals(10, ruleNodes.getData().size());
}
private List<UUID> createRuleNodes(TenantId tenantId1, TenantId tenantId2, RuleChainId ruleChainId1, RuleChainId ruleChainId2, int count) {
var chain1 = new RuleChain(ruleChainId1);
chain1.setTenantId(tenantId1);
chain1.setName(ruleChainId1.toString());
ruleChainDao.save(tenantId1, chain1);
var chain2 = new RuleChain(ruleChainId2);
chain2.setTenantId(tenantId2);
chain2.setName(ruleChainId2.toString());
ruleChainDao.save(tenantId2, chain2);
List<UUID> savedRuleNodeIds = new ArrayList<>();
for (int i = 0; i < count / 2; i++) {
savedRuleNodeIds.add(ruleNodeDao.save(tenantId1, getRuleNode(ruleChainId1, "A", Integer.toString(i))).getUuidId());
savedRuleNodeIds.add(ruleNodeDao.save(tenantId2, getRuleNode(ruleChainId2, "B", Integer.toString(i + count / 2))).getUuidId());
}
return savedRuleNodeIds;
}
private RuleNode getRuleNode(RuleChainId ruleChainId, String type, String nameSuffix) {
return getRuleNode(ruleChainId, Uuids.timeBased(), type, nameSuffix);
}
private RuleNode getRuleNode(RuleChainId ruleChainId, UUID ruleNodeId, String type, String nameSuffix) {
RuleNode ruleNode = new RuleNode();
ruleNode.setId(new RuleNodeId(ruleNodeId));
ruleNode.setRuleChainId(ruleChainId);
ruleNode.setName(nameSuffix);
ruleNode.setType(type);
ruleNode.setConfiguration(JacksonUtil.newObjectNode().put("searchHint", PREFIX_FOR_RULE_NODE_NAME + nameSuffix));
ruleNode.setConfigurationVersion(0);
return ruleNode;
}
}

2
docker/docker-compose.yml

@ -235,6 +235,8 @@ services:
tb-snmp-transport: tb-snmp-transport:
restart: always restart: always
image: "${DOCKER_REPO}/${SNMP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${SNMP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
ports:
- "1620:1620/udp"
environment: environment:
TB_SERVICE_ID: tb-snmp-transport TB_SERVICE_ID: tb-snmp-transport
JAVA_OPTS: "${JAVA_OPTS}" JAVA_OPTS: "${JAVA_OPTS}"

1
docker/tb-snmp-transport.env

@ -1,6 +1,7 @@
ZOOKEEPER_ENABLED=true ZOOKEEPER_ENABLED=true
ZOOKEEPER_URL=zookeeper:2181 ZOOKEEPER_URL=zookeeper:2181
SNMP_BIND_PORT=1620
METRICS_ENABLED=true METRICS_ENABLED=true
METRICS_ENDPOINTS_EXPOSE=prometheus METRICS_ENDPOINTS_EXPOSE=prometheus
WEB_APPLICATION_ENABLE=true WEB_APPLICATION_ENABLE=true

2
pom.xml

@ -78,7 +78,7 @@
<zookeeper.version>3.8.1</zookeeper.version> <zookeeper.version>3.8.1</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version> <protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version> <grpc.version>1.42.1</grpc.version>
<tbel.version>1.1.0</tbel.version> <tbel.version>1.1.1</tbel.version>
<lombok.version>1.18.18</lombok.version> <lombok.version>1.18.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version> <paho.client.version>1.2.4</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version> <paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

16
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
@ -35,4 +37,18 @@ public interface TbNode {
default void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { default void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) {
} }
/**
* Upgrades the configuration from a specific version to the current version specified in the
* {@link RuleNode} annotation for the instance of {@link TbNode}.
*
* @param fromVersion The version from which the configuration needs to be upgraded.
* @param oldConfiguration The old configuration to be upgraded.
* @return A pair consisting of a Boolean flag indicating the success of the upgrade
* and a JsonNode representing the upgraded configuration.
* @throws TbNodeException If an error occurs during the upgrade process.
*/
default TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
return new TbPair<>(false, oldConfiguration);
}
} }

35
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbVersionedNode.java

@ -1,35 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.api;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.util.TbPair;
public interface TbVersionedNode extends TbNode {
/**
* Upgrades the configuration from a specific version to the current version specified in the
* {@link RuleNode} annotation for the instance of {@link TbVersionedNode}.
*
* @param fromVersion The version from which the configuration needs to be upgraded.
* @param oldConfiguration The old configuration to be upgraded.
* @return A pair consisting of a Boolean flag indicating the success of the upgrade
* and a JsonNode representing the upgraded configuration.
* @throws TbNodeException If an error occurs during the upgrade process.
*/
TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException;
}

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java

@ -22,9 +22,9 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbVersionedNode;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -58,7 +58,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
"Output connections: <code>True</code>, <code>False</code>, <code>Failure</code>", "Output connections: <code>True</code>, <code>False</code>, <code>Failure</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFilterNodeCheckRelationConfig") configDirective = "tbFilterNodeCheckRelationConfig")
public class TbCheckRelationNode implements TbVersionedNode { public class TbCheckRelationNode implements TbNode {
private static final String DIRECTION_PROPERTY_NAME = "direction"; private static final String DIRECTION_PROPERTY_NAME = "direction";

30
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java

@ -84,7 +84,7 @@ import static org.thingsboard.rule.engine.math.TbMathArgumentType.CONSTANT;
) )
public class TbMathNode implements TbNode { public class TbMathNode implements TbNode {
private static final ConcurrentMap<EntityId, SemaphoreWithQueue<TbMsgTbContext>> locks = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); private static final ConcurrentMap<EntityId, SemaphoreWithQueue<TbMsgTbContextBiFunction>> locks = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
private final ThreadLocal<Expression> customExpression = new ThreadLocal<>(); private final ThreadLocal<Expression> customExpression = new ThreadLocal<>();
private TbMathNodeConfiguration config; private TbMathNodeConfiguration config;
private boolean msgBodyToJsonConversionRequired; private boolean msgBodyToJsonConversionRequired;
@ -111,21 +111,21 @@ public class TbMathNode implements TbNode {
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
var semaphoreWithQueue = locks.computeIfAbsent(msg.getOriginator(), SemaphoreWithQueue::new); var semaphoreWithQueue = locks.computeIfAbsent(msg.getOriginator(), SemaphoreWithQueue::new);
semaphoreWithQueue.getQueue().add(new TbMsgTbContext(msg, ctx)); semaphoreWithQueue.getQueue().add(new TbMsgTbContextBiFunction(msg, ctx, this::processMsgAsync));
tryProcessQueue(semaphoreWithQueue); tryProcessQueue(semaphoreWithQueue);
} }
void tryProcessQueue(SemaphoreWithQueue<TbMsgTbContext> lockAndQueue) { void tryProcessQueue(SemaphoreWithQueue<TbMsgTbContextBiFunction> lockAndQueue) {
final Semaphore semaphore = lockAndQueue.getSemaphore(); final Semaphore semaphore = lockAndQueue.getSemaphore();
final Queue<TbMsgTbContext> queue = lockAndQueue.getQueue(); final Queue<TbMsgTbContextBiFunction> queue = lockAndQueue.getQueue();
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
// The semaphore have to be acquired before EACH poll and released before NEXT poll. // The semaphore have to be acquired before EACH poll and released before NEXT poll.
// Otherwise, some message will remain unprocessed in queue // Otherwise, some message will remain unprocessed in queue
if (!semaphore.tryAcquire()) { if (!semaphore.tryAcquire()) {
return; return;
} }
TbMsgTbContext tbMsgTbContext = null; TbMsgTbContextBiFunction tbMsgTbContext = null;
try { try {
tbMsgTbContext = queue.poll(); tbMsgTbContext = queue.poll();
if (tbMsgTbContext == null) { if (tbMsgTbContext == null) {
@ -140,7 +140,7 @@ public class TbMathNode implements TbNode {
} }
//DO PROCESSING //DO PROCESSING
final TbContext ctx = tbMsgTbContext.getCtx(); final TbContext ctx = tbMsgTbContext.getCtx();
final ListenableFuture<TbMsg> resultMsgFuture = processMsgAsync(ctx, msg); final ListenableFuture<TbMsg> resultMsgFuture = tbMsgTbContext.getBiFunction().apply(ctx, msg);
DonAsynchron.withCallback(resultMsgFuture, resultMsg -> { DonAsynchron.withCallback(resultMsgFuture, resultMsg -> {
try { try {
ctx.tellSuccess(resultMsg); ctx.tellSuccess(resultMsg);
@ -156,10 +156,17 @@ public class TbMathNode implements TbNode {
tryProcessQueue(lockAndQueue); tryProcessQueue(lockAndQueue);
} }
}, ctx.getDbCallbackExecutor()); }, ctx.getDbCallbackExecutor());
} catch (Throwable e) { } catch (Throwable t) {
semaphore.release(); semaphore.release();
log.warn("[{}] Failed to process message: {}", lockAndQueue.getEntityId(), tbMsgTbContext == null ? null : tbMsgTbContext.getMsg(), e); if (tbMsgTbContext == null) { // if no message polled, the loop become infinite, will throw exception
throw e; log.error("[{}] Failed to process TbMsgTbContext queue", lockAndQueue.getEntityId(), t);
throw t;
}
TbMsg msg = tbMsgTbContext.getMsg();
TbContext ctx = tbMsgTbContext.getCtx();
log.warn("[{}] Failed to process message: {}", lockAndQueue.getEntityId(), msg, t);
ctx.tellFailure(msg, t); // you are not allowed to throw here, because queue will remain unprocessed
continue; // We are probably the last who process the queue. We have to continue poll until get successful callback or queue is empty
} }
break; //submitted async exact one task. next poll will try on callback break; //submitted async exact one task. next poll will try on callback
} }
@ -367,7 +374,7 @@ public class TbMathNode implements TbNode {
return function.apply(arg1.getValue(), arg2.getValue()); return function.apply(arg1.getValue(), arg2.getValue());
} }
private ListenableFuture<TbMathArgumentValue> resolveArguments(TbContext ctx, TbMsg msg, Optional<ObjectNode> msgBodyOpt, TbMathArgument arg) { ListenableFuture<TbMathArgumentValue> resolveArguments(TbContext ctx, TbMsg msg, Optional<ObjectNode> msgBodyOpt, TbMathArgument arg) {
String argKey = getKeyFromTemplate(msg, arg.getType(), arg.getKey()); String argKey = getKeyFromTemplate(msg, arg.getType(), arg.getKey());
switch (arg.getType()) { switch (arg.getType()) {
case CONSTANT: case CONSTANT:
@ -433,9 +440,10 @@ public class TbMathNode implements TbNode {
@Data @Data
@RequiredArgsConstructor @RequiredArgsConstructor
static public class TbMsgTbContext { static public class TbMsgTbContextBiFunction {
final TbMsg msg; final TbMsg msg;
final TbContext ctx; final TbContext ctx;
final BiFunction<TbContext, TbMsg, ListenableFuture<TbMsg>> biFunction;
} }
} }

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java

@ -22,9 +22,9 @@ import com.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbVersionedNode;
import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.rule.engine.util.TbMsgSource;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@Slf4j @Slf4j
public abstract class TbAbstractNodeWithFetchTo<C extends TbAbstractFetchToNodeConfiguration> implements TbVersionedNode { public abstract class TbAbstractNodeWithFetchTo<C extends TbAbstractFetchToNodeConfiguration> implements TbNode {
protected final static String FETCH_TO_PROPERTY_NAME = "fetchTo"; protected final static String FETCH_TO_PROPERTY_NAME = "fetchTo";

8
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java

@ -24,9 +24,9 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbVersionedNode;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -43,9 +43,7 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; import static org.thingsboard.server.common.data.DataConstants.*;
import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY;
import static org.thingsboard.server.common.data.DataConstants.SCOPE;
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST;
@Slf4j @Slf4j
@ -64,7 +62,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R
configDirective = "tbActionNodeAttributesConfig", configDirective = "tbActionNodeAttributesConfig",
icon = "file_upload" icon = "file_upload"
) )
public class TbMsgAttributesNode implements TbVersionedNode { public class TbMsgAttributesNode implements TbNode {
static final String UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY = "updateAttributesOnlyOnValueChange"; static final String UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY = "updateAttributesOnlyOnValueChange";
private TbMsgAttributesNodeConfiguration config; private TbMsgAttributesNodeConfiguration config;

150
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java

@ -18,6 +18,9 @@ package org.thingsboard.rule.engine.math;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Triple;
import org.assertj.core.api.SoftAssertions;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -27,7 +30,9 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.verification.Timeout;
import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.AbstractListeningExecutor;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
@ -72,6 +77,10 @@ import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willReturn; import static org.mockito.BDDMockito.willReturn;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout;
@ -128,7 +137,15 @@ public class TbMathNodeTest {
return initNode(TbRuleNodeMathFunctionType.CUSTOM, expression, result, arguments); return initNode(TbRuleNodeMathFunctionType.CUSTOM, expression, result, arguments);
} }
private TbMathNode initNodeWithCustomFunction(TbContext ctx, String expression, TbMathResult result, TbMathArgument... arguments) {
return initNode(ctx, TbRuleNodeMathFunctionType.CUSTOM, expression, result, arguments);
}
private TbMathNode initNode(TbRuleNodeMathFunctionType operation, String expression, TbMathResult result, TbMathArgument... arguments) { private TbMathNode initNode(TbRuleNodeMathFunctionType operation, String expression, TbMathResult result, TbMathArgument... arguments) {
return initNode(this.ctx, operation, expression, result, arguments);
}
private TbMathNode initNode(TbContext ctx, TbRuleNodeMathFunctionType operation, String expression, TbMathResult result, TbMathArgument... arguments) {
try { try {
TbMathNodeConfiguration configuration = new TbMathNodeConfiguration(); TbMathNodeConfiguration configuration = new TbMathNodeConfiguration();
configuration.setOperation(operation); configuration.setOperation(operation);
@ -521,10 +538,11 @@ public class TbMathNodeTest {
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey") new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey")
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 10).toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 10).toString());
Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg);
node.onMsg(ctx, msg);
}); ArgumentCaptor<Throwable> tCaptor = ArgumentCaptor.forClass(Throwable.class);
assertNotNull(thrown.getMessage()); Mockito.verify(ctx, Mockito.timeout(5000)).tellFailure(eq(msg), tCaptor.capture());
Assert.assertNotNull(tCaptor.getValue().getMessage());
} }
@Test @Test
@ -534,11 +552,12 @@ public class TbMathNodeTest {
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a")
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY);
Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg);
node.onMsg(ctx, msg);
}); ArgumentCaptor<Throwable> tCaptor = ArgumentCaptor.forClass(Throwable.class);
assertNotNull(thrown.getMessage()); Mockito.verify(ctx, Mockito.timeout(5000)).tellFailure(eq(msg), tCaptor.capture());
Assert.assertNotNull(tCaptor.getValue().getMessage());
} }
@Test @Test
@ -553,10 +572,10 @@ public class TbMathNodeTest {
CountDownLatch slowProcessingLatch = new CountDownLatch(1); CountDownLatch slowProcessingLatch = new CountDownLatch(1);
List<TbMsg> slowMsgList = IntStream.range(0, 5) List<TbMsg> slowMsgList = IntStream.range(0, 5)
.mapToObj(x -> TbMsg.newMsg("TEST", originatorSlow, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString())) .mapToObj(x -> TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originatorSlow, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()))
.collect(Collectors.toList()); .collect(Collectors.toList());
List<TbMsg> fastMsgList = IntStream.range(0, 2) List<TbMsg> fastMsgList = IntStream.range(0, 2)
.mapToObj(x -> TbMsg.newMsg("TEST", originatorFast, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString())) .mapToObj(x -> TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originatorFast, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()))
.collect(Collectors.toList()); .collect(Collectors.toList());
assertThat(slowMsgList.size()).as("slow msgs >= rule-dispatcher pool size").isGreaterThanOrEqualTo(RULE_DISPATCHER_POOL_SIZE); assertThat(slowMsgList.size()).as("slow msgs >= rule-dispatcher pool size").isGreaterThanOrEqualTo(RULE_DISPATCHER_POOL_SIZE);
@ -609,6 +628,115 @@ public class TbMathNodeTest {
verify(ctx, never()).tellFailure(any(), any()); verify(ctx, never()).tellFailure(any(), any());
} }
@Test
public void testExp4j_concurrentBySingleOriginator_processMsgAsyncException() {
TbMathNode node = spy(initNodeWithCustomFunction("2a+3b",
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")
));
willThrow(new RuntimeException("Message body has no 'delta'")).given(node).resolveArguments(any(), any(), any(), any());
EntityId originatorSlow = DeviceId.fromString("7f01170d-6bba-419c-b95c-2b4c3ba32f30");
CountDownLatch slowProcessingLatch = new CountDownLatch(1);
List<TbMsg> slowMsgList = IntStream.range(0, 5)
.mapToObj(x -> TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originatorSlow, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()))
.collect(Collectors.toList());
assertThat(slowMsgList.size()).as("slow msgs >= rule-dispatcher pool size").isGreaterThanOrEqualTo(RULE_DISPATCHER_POOL_SIZE);
log.debug("rule-dispatcher [{}], db-callback [{}], slowMsg [{}]", RULE_DISPATCHER_POOL_SIZE, DB_CALLBACK_POOL_SIZE, slowMsgList.size());
willAnswer(invocation -> {
TbMsg msg = invocation.getArgument(1);
if (slowProcessingLatch.getCount() > 0) {
log.debug("Await on slowProcessingLatch before processMsgAsync");
try {
assertThat(slowProcessingLatch.await(30, TimeUnit.SECONDS)).as("await on slowProcessingLatch").isTrue();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("\uD83D\uDC0C processMsgAsync with exception [{}][{}]", msg.getOriginator(), msg);
return invocation.callRealMethod();
}).given(node).processMsgAsync(eq(ctx), argThat(slowMsgList::contains));
willAnswer(invocation -> {
TbMsg msg = invocation.getArgument(1);
log.debug("submit slow originator onMsg [{}][{}]", msg.getOriginator(), msg);
return invocation.callRealMethod();
}).given(node).onMsg(eq(ctx), argThat(slowMsgList::contains));
// submit slow msg may block all rule engine dispatcher threads
slowMsgList.forEach(msg -> ruleEngineDispatcherExecutor.executeAsync(() -> node.onMsg(ctx, msg)));
// wait until dispatcher threads started with all slowMsg
verify(node, new Timeout(TimeUnit.SECONDS.toMillis(5), times(slowMsgList.size()))).onMsg(eq(ctx), argThat(slowMsgList::contains));
slowProcessingLatch.countDown();
verify(ctx, new Timeout(TimeUnit.SECONDS.toMillis(5), times(slowMsgList.size()))).tellFailure(any(), any());
verify(ctx, never()).tellSuccess(any());
}
@Test
public void testExp4j_concurrentBySingleOriginator_SingleMsg_manyNodesWithDifferentOutput() {
assertThat(RULE_DISPATCHER_POOL_SIZE).as("dispatcher pool size have to be > 1").isGreaterThan(1);
CountDownLatch processingLatch = new CountDownLatch(1);
List<Triple<TbContext, String, TbMathNode>> ctxNodes = IntStream.range(0, RULE_DISPATCHER_POOL_SIZE * 2)
.mapToObj(x -> {
final TbContext ctx = mock(TbContext.class); // many rule nodes - many contexts
willReturn(dbCallbackExecutor).given(ctx).getDbCallbackExecutor();
final String resultKey = "result" + x;
final TbMathNode node = spy(initNodeWithCustomFunction(ctx, "2a+3b",
new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, resultKey, 1, false, true, null),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")));
willAnswer(invocation -> {
if (processingLatch.getCount() > 0) {
log.debug("Await on processingLatch before processMsgAsync");
try {
assertThat(processingLatch.await(30, TimeUnit.SECONDS)).as("await on processingLatch").isTrue();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("\uD83D\uDC0C processMsgAsync on node with expected resultKey [{}]", resultKey);
return invocation.callRealMethod();
}).given(node).processMsgAsync(any(), any());
willAnswer(invocation -> {
TbMsg msg = invocation.getArgument(1);
log.debug("submit originator onMsg [{}][{}]", msg.getOriginator(), msg);
return invocation.callRealMethod();
}).given(node).onMsg(any(), any());
return Triple.of(ctx, resultKey, node);
})
.collect(Collectors.toList());
ctxNodes.forEach(ctxNode -> ruleEngineDispatcherExecutor.executeAsync(() -> ctxNode.getRight()
.onMsg(ctxNode.getLeft(), TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, "{\"a\":2,\"b\":2}"))));
ctxNodes.forEach(ctxNode -> verify(ctxNode.getRight(), timeout(5000)).onMsg(eq(ctxNode.getLeft()), any()));
processingLatch.countDown();
SoftAssertions softly = new SoftAssertions();
ctxNodes.forEach(ctxNode -> {
final TbContext ctx = ctxNode.getLeft();
final String resultKey = ctxNode.getMiddle();
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, timeout(5000)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue();
assertThat(resultMsg).as("result msg non null for result key " + resultKey).isNotNull();
log.debug("asserting result key [{}] in metadata [{}]", resultKey, resultMsg.getMetaData().getData());
softly.assertThat(resultMsg.getMetaData().getValue(resultKey)).as("asserting result key " + resultKey)
.isEqualTo("10.0");
});
softly.assertAll();
verify(ctx, never()).tellFailure(any(), any());
}
static class RuleDispatcherExecutor extends AbstractListeningExecutor { static class RuleDispatcherExecutor extends AbstractListeningExecutor {
@Override @Override
protected int getThreadPollSize() { protected int getThreadPollSize() {

4
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -99,11 +99,15 @@ redis:
transport: transport:
snmp: snmp:
enabled: "${SNMP_ENABLED:true}" enabled: "${SNMP_ENABLED:true}"
bind_port: "${SNMP_BIND_PORT:1620}"
response_processing: response_processing:
# parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices
parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}"
# to configure SNMP to work over UDP or TCP # to configure SNMP to work over UDP or TCP
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
response:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"

13
ui-ngx/src/app/core/translate/missing-translate-handler.ts

@ -21,7 +21,18 @@ export class TbMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) { handle(params: MissingTranslationHandlerParams) {
if (params.key && !params.key.startsWith(customTranslationsPrefix)) { if (params.key && !params.key.startsWith(customTranslationsPrefix)) {
console.warn('Translation for \'' + params.key + '\' doesn\'t exist'); console.warn('Translation for \'' + params.key + '\' doesn\'t exist');
params.translateService.set(params.key, params.key); let translations: any;
const parts = params.key.split('.');
for (let i=parts.length-1; i>=0; i--) {
const newTranslations = {};
if (i === parts.length-1) {
newTranslations[parts[i]] = params.key;
} else {
newTranslations[parts[i]] = translations;
}
translations = newTranslations;
}
params.translateService.setTranslation(params.translateService.currentLang, translations, true);
} }
} }
} }

8
ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html

@ -17,7 +17,7 @@
--> -->
<form [formGroup]="attributeFormGroup" (ngSubmit)="add()" style="min-width: 400px;"> <form [formGroup]="attributeFormGroup" (ngSubmit)="add()" style="min-width: 400px;">
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2>{{ title | translate }}</h2> <h2>{{ (isTelemetry ? 'attribute.add-telemetry' : 'attribute.add') | translate }}</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="cancel()"
@ -34,7 +34,7 @@
<mat-label translate>attribute.key</mat-label> <mat-label translate>attribute.key</mat-label>
<input matInput formControlName="key" required> <input matInput formControlName="key" required>
<mat-error *ngIf="attributeFormGroup.get('key').hasError('required')"> <mat-error *ngIf="attributeFormGroup.get('key').hasError('required')">
{{ 'attribute.key-required' | translate }} {{ (isTelemetry ? 'attribute.telemetry-key-required' : 'attribute.key-required') | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="attributeFormGroup.get('key').hasError('maxlength')"> <mat-error *ngIf="attributeFormGroup.get('key').hasError('maxlength')">
{{ 'attribute.key-max-length' | translate }} {{ 'attribute.key-max-length' | translate }}
@ -42,7 +42,7 @@
</mat-form-field> </mat-form-field>
<tb-value-input <tb-value-input
formControlName="value" formControlName="value"
requiredText="attribute.value-required"> [requiredText]="isTelemetry ? 'attribute.telemetry-value-required' : 'attribute.value-required'">
</tb-value-input> </tb-value-input>
</fieldset> </fieldset>
</div> </div>
@ -55,7 +55,7 @@
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty"> [disabled]="(isLoading$ | async) || invalid() || !attributeFormGroup.dirty">
{{ 'action.add' | translate }} {{ 'action.add' | translate }}
</button> </button>
</div> </div>

10
ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts

@ -45,7 +45,7 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
submitted = false; submitted = false;
title = ''; isTelemetry = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
@ -62,8 +62,7 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
key: ['', [Validators.required, Validators.maxLength(255)]], key: ['', [Validators.required, Validators.maxLength(255)]],
value: [null, [Validators.required]] value: [null, [Validators.required]]
}); });
this.title = this.data.attributeScope === LatestTelemetry.LATEST_TELEMETRY ? this.isTelemetry = this.data.attributeScope === LatestTelemetry.LATEST_TELEMETRY;
'attribute.add-telemetry' : 'attribute.add'
} }
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
@ -72,6 +71,11 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
return originalErrorState || customErrorState; return originalErrorState || customErrorState;
} }
invalid(): boolean {
const value = this.attributeFormGroup.get('value').value;
return !Array.isArray(value) && this.attributeFormGroup.invalid;
}
cancel(): void { cancel(): void {
this.dialogRef.close(false); this.dialogRef.close(false);
} }

2
ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html

@ -33,7 +33,7 @@
</button> </button>
<button mat-button mat-raised-button color="primary" <button mat-button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty"> [disabled]="(isLoading$ | async) || invalid() || !attributeFormGroup.dirty">
{{ 'action.update' | translate }} {{ 'action.update' | translate }}
</button> </button>
</div> </div>

5
ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts

@ -62,6 +62,11 @@ export class EditAttributeValuePanelComponent extends PageComponent implements O
return originalErrorState || customErrorState; return originalErrorState || customErrorState;
} }
invalid(): boolean {
const value = this.attributeFormGroup.get('value').value;
return !Array.isArray(value) && this.attributeFormGroup.invalid;
}
cancel(): void { cancel(): void {
this.overlayRef.dispose(); this.overlayRef.dispose();
} }

10
ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts

@ -107,9 +107,15 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
this.columns.push( this.columns.push(
new DateEntityTableColumn<EdgeEvent>('createdTime', 'event.event-time', this.datePipe, '120px'), new DateEntityTableColumn<EdgeEvent>('createdTime', 'event.event-time', this.datePipe, '120px'),
new EntityTableColumn<EdgeEvent>('type', 'event.type', '25%', new EntityTableColumn<EdgeEvent>('type', 'event.type', '25%',
entity => this.translate.instant(edgeEventTypeTranslations.get(entity.type)), entity => ({}), false), entity => {
let key = edgeEventTypeTranslations.get(entity.type);
return key ? this.translate.instant(key) : entity.type;
}, entity => ({}), false),
new EntityTableColumn<EdgeEvent>('action', 'edge.event-action', '25%', new EntityTableColumn<EdgeEvent>('action', 'edge.event-action', '25%',
entity => this.translate.instant(edgeEventActionTypeTranslations.get(entity.action)), entity => ({}), false), entity => {
let key = edgeEventActionTypeTranslations.get(entity.action);
return key ? this.translate.instant(key) : entity.action;
}, entity => ({}), false),
new EntityTableColumn<EdgeEvent>('entityId', 'edge.entity-id', '40%', new EntityTableColumn<EdgeEvent>('entityId', 'edge.entity-id', '40%',
(entity) => entity.entityId ? entity.entityId : '', () => ({}), false), (entity) => entity.entityId ? entity.entityId : '', () => ({}), false),
new EntityTableColumn<EdgeEvent>('status', 'event.status', '10%', new EntityTableColumn<EdgeEvent>('status', 'event.status', '10%',

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save