Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into feature/calculated-fields

pull/12510/head
Andrii Shvaika 1 year ago
parent
commit
f0a71aa3bd
  1. 346
      application/src/main/data/json/system/scada_symbols/crane-hp.svg
  2. 355
      application/src/main/data/json/system/scada_symbols/drawwork-hp.svg
  3. 354
      application/src/main/data/json/system/scada_symbols/drill-hp.svg
  4. 361
      application/src/main/data/json/system/scada_symbols/drilling-line-hp.svg
  5. 354
      application/src/main/data/json/system/scada_symbols/drilling-rig-hp.svg
  6. 293
      application/src/main/data/json/system/scada_symbols/hook-hp.svg
  7. 743
      application/src/main/data/json/system/scada_symbols/meter.svg
  8. 267
      application/src/main/data/json/system/scada_symbols/platform-hp.svg
  9. 343
      application/src/main/data/json/system/scada_symbols/preventer-hp.svg
  10. 346
      application/src/main/data/json/system/scada_symbols/rotor-hp.svg
  11. 717
      application/src/main/data/json/system/scada_symbols/small-left-meter.svg
  12. 688
      application/src/main/data/json/system/scada_symbols/small-meter.svg
  13. 717
      application/src/main/data/json/system/scada_symbols/small-right-center.svg
  14. 4
      application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json
  15. 20
      application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json
  16. 4
      application/src/main/data/json/system/widget_bundles/scada_fluid_system.json
  17. 24
      application/src/main/data/json/system/widget_types/gateway_configuration.json
  18. 24
      application/src/main/data/json/system/widget_types/gateway_configuration__single_device_.json
  19. 23
      application/src/main/data/json/system/widget_types/gateway_connectors.json
  20. 23
      application/src/main/data/json/system/widget_types/gateway_custom_statistics.json
  21. 23
      application/src/main/data/json/system/widget_types/gateway_general_chart_statistics.json
  22. 30
      application/src/main/data/json/system/widget_types/gateway_general_configuration.json
  23. 23
      application/src/main/data/json/system/widget_types/gateway_logs.json
  24. 44
      application/src/main/data/json/system/widget_types/gateway_status.json
  25. 23
      application/src/main/data/json/system/widget_types/service_rpc.json
  26. 4432
      application/src/main/data/resources/dashboards/gateways_dashboard.json
  27. 8
      application/src/main/data/resources/js_modules/gateway-management-extension.js
  28. 2
      application/src/main/data/upgrade/basic/schema_update.sql
  29. 14
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  30. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  31. 88
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  32. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java
  33. 9
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java
  34. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
  35. 31
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  36. 5
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java
  37. 14
      application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java
  38. 13
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  39. 1
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  40. 4
      application/src/main/java/org/thingsboard/server/service/install/update/ResourcesUpdater.java
  41. 7
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  42. 10
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  43. 132
      application/src/main/resources/thingsboard.yml
  44. 11
      application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java
  45. 31
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  46. 2
      application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java
  47. 75
      application/src/test/java/org/thingsboard/server/edge/RelationEdgeTest.java
  48. 20
      application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java
  49. 5
      application/src/test/java/org/thingsboard/server/service/script/AbstractTbelInvokeTest.java
  50. 82
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java
  51. 19
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
  52. 17
      application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java
  53. 8
      application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
  54. 2
      application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
  55. 291
      application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java
  56. 44
      application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityJksIntegrationTest.java
  57. 44
      application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityPemIntegrationTest.java
  58. 3
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java
  59. 3
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java
  60. 82
      application/src/test/java/org/thingsboard/server/transport/coap/x509/CertPrivateKey.java
  61. 239
      application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java
  62. 129
      application/src/test/java/org/thingsboard/server/transport/coap/x509/TbAdvancedCertificateVerifier.java
  63. 38
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  64. 6
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  65. 1
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  66. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java
  67. 8
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  68. 3
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  69. 81
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  70. 13
      application/src/test/resources/coap/credentials/client/cert.pem
  71. 14
      application/src/test/resources/coap/credentials/client/cert_01.pem
  72. 4
      application/src/test/resources/coap/credentials/client/key.pem
  73. 8
      application/src/test/resources/coap/credentials/client/key_01.pem
  74. BIN
      application/src/test/resources/coap/credentials/coapclientTest.jks
  75. BIN
      application/src/test/resources/coap/credentials/coapserverTest.jks
  76. 35
      application/src/test/resources/coap/credentials/server/cert.pem
  77. 3
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java
  78. 2
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java
  79. 5
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java
  80. 9
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java
  81. 22
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionKey.java
  82. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  83. 4
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/app/StoreInfo.java
  84. 2
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  85. 1
      common/edge-api/src/main/proto/edge.proto
  86. 8
      common/queue/pom.xml
  87. 56
      common/queue/src/main/java/org/thingsboard/server/queue/RuleEngineTbQueueAdminFactory.java
  88. 137
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusAdmin.java
  89. 175
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java
  90. 110
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusProducerTemplate.java
  91. 72
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java
  92. 37
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusSettings.java
  93. 317
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java
  94. 291
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java
  95. 208
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java
  96. 98
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java
  97. 153
      common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java
  98. 315
      common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java
  99. 278
      common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java
  100. 209
      common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java

346
application/src/main/data/json/system/scada_symbols/crane-hp.svg

@ -0,0 +1,346 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Crane",
"description": "Crane with various states.",
"searchTags": [
"crane"
],
"widgetSizeX": 5,
"widgetSizeY": 6,
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "running",
"name": "{i18n:scada.symbol.running}",
"hint": "{i18n:scada.symbol.running-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.running}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "running"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "runningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "{i18n:scada.symbol.running}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "stoppedColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#666666",
"required": null,
"subLabel": "{i18n:scada.symbol.stopped}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<g fill="#fff" stroke="#1a1a1a" stroke-width="2" tb:tag="background">
<path d="m999 1101h-518v98h518z"/>
<path d="m89 301h-88v38h88z"/>
<path d="m38.637 353.25c1.8257-2.795 3.0211-5.169 3.5801-7.616 0.4834-2.115 0.4778-4.225 0.0646-6.636h22.765c0.4236 2.76 0.5531 8.434-2.4543 17.437-1.0074 3.015-3.2478 5.918-6.1765 8.829-2.4452 2.43-5.2981 4.799-8.1981 7.207-0.57 0.473-1.1418 0.948-1.7126 1.425-3.4492 2.882-6.8589 5.843-9.4634 8.969s-4.4841 6.511-4.7352 10.25c-0.4781 7.123 1.5349 12.183 4.5904 15.755 3.0323 3.545 7.0211 5.546 10.381 6.711 2.6292 0.911 7.97 1.542 12.913-0.172 2.4934-0.865 4.9095-2.335 6.8093-4.687 1.9017-2.355 3.2394-5.537 3.6645-9.748 0.2685-2.66 0.4677-4.993 0.639-7.001 0.0914-1.07 0.1749-2.048 0.2566-2.933 0.2394-2.589 0.4634-4.351 0.8094-5.493 0.345-1.138 0.6919-1.294 0.9494-1.334 0.2122-0.033 0.5457-0.012 1.0684 0.146 0.5169 0.156 1.1513 0.424 1.9388 0.814 8.6238 4.272 11.837 11.217 12.524 18.027 0.6932 6.867-1.1952 13.571-2.7777 17.098-6.6047 14.722-20.145 23.7-37.326 23.7-10.333 0-18.218-1.819-24.553-5.103-6.3316-3.283-11.186-8.065-15.404-14.105-3.3703-4.826-6.7534-12.808-7.5927-21.713-0.83751-8.887 0.86149-18.628 7.5566-27.085 6.2949-7.951 12.501-11.818 17.718-14.561 0.9365-0.492 1.8598-0.957 2.7531-1.407 1.5646-0.788 3.0372-1.53 4.3284-2.291 2.0627-1.216 3.8384-2.576 5.0837-4.483z"/>
</g><path d="m559.64 1101-515.24-1098.3 341.87 221.97 538.94 876.28z" stroke="#1a1a1a" stroke-width="2"/><path d="m148 225h237" stroke="#1a1a1a" stroke-width="2"/><path d="m249 438h269" stroke="#1a1a1a" stroke-width="2"/><path d="m362 680h304" stroke="#1a1a1a" stroke-width="2"/><path d="m491 955h344" stroke="#1a1a1a" stroke-width="2"/><path d="m249 437.92 136.32-212.92" stroke="#1a1a1a" stroke-width="2"/><path d="m362 680.92 155.32-242.92" stroke="#1a1a1a" stroke-width="1.9997"/><path d="m492 954.92 175.31-274.92" stroke="#1a1a1a" stroke-width="2.0018"/><path d="m44 2v299" stroke="#1a1a1a" stroke-width="2"/><path d="m336.32 0s-336.32 0-336.32 201v985.08c0 7.9536 8.9545 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4435 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2235 8.4 11.667 8.4h73.33c6.4435 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2235-8.4-11.667-8.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

355
application/src/main/data/json/system/scada_symbols/drawwork-hp.svg

@ -0,0 +1,355 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="400" fill="none" version="1.1" viewBox="0 0 600 400"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Drawwork",
"description": "Drawwork with various states.",
"searchTags": [
"drawwork"
],
"widgetSizeX": 3,
"widgetSizeY": 2,
"tags": [
{
"tag": "circle-background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = '#dedede';\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "drawwork-background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "running",
"name": "{i18n:scada.symbol.running}",
"hint": "{i18n:scada.symbol.running-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.running}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "running"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "runningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "{i18n:scada.symbol.running}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "stoppedColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#666666",
"required": null,
"subLabel": "{i18n:scada.symbol.stopped}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<g fill="#fff" stroke="#1a1a1a" tb:tag="drawwork-background">
<path d="m65.049 374 49.936-64h143.35l49.622 64z" stroke-width="2"/>
<rect x="24.777" y="374" width="324" height="24.999" rx="3.6512" stroke-width="2.0011"/>
<rect x="188" y="1" width="333" height="252" stroke-width="2"/>
<rect x="521" y="1" width="78" height="398" stroke-width="2"/>
<circle cx="187" cy="187" r="186" stroke-width="2"/>
</g><mask id="path-6-inside-1_4465_2848" fill="#ffffff">
<path d="m308.33 187c0 66.274-53.726 120-120 120s-120-53.726-120-120 53.726-120 120-120 120 53.726 120 120zm-221.31 0c0 55.95 45.357 101.31 101.31 101.31s101.31-45.357 101.31-101.31-45.357-101.31-101.31-101.31-101.31 45.357-101.31 101.31z"/>
</mask><path d="m308.33 187c0 66.274-53.726 120-120 120s-120-53.726-120-120 53.726-120 120-120 120 53.726 120 120zm-221.31 0c0 55.95 45.357 101.31 101.31 101.31s101.31-45.357 101.31-101.31-45.357-101.31-101.31-101.31-101.31 45.357-101.31 101.31z" fill="#dedede" mask="url(#path-6-inside-1_4465_2848)" stroke="#1a1a1a" stroke-width="4" tb:tag="circle-background"/><line x1="421.5" x2="421.5" y1="4.3711e-8" y2="254" stroke="#1a1a1a" stroke-width="2"/><line x1="442" x2="442" y1="4.3711e-8" y2="254" stroke="#1a1a1a" stroke-width="2"/><line x1="462" x2="462" y1="4.3711e-8" y2="254" stroke="#1a1a1a" stroke-width="2"/><line x1="482" x2="482" y1="4.3711e-8" y2="254" stroke="#1a1a1a" stroke-width="2"/><line x1="502" x2="502" y1="4.3711e-8" y2="254" stroke="#1a1a1a" stroke-width="2"/><path d="m201.79 0s-201.79 0-201.79 67v328.36c0 2.6512 5.3727 4.6404 12 4.6404h576c6.627 0 12-1.9892 12-4.6404v-328.36c0-67-198.21-67-198.21-67h-101.79zm201.21 81.2c-3.8661 0-6.9999 1.2536-6.9999 2.8v300.4c0 1.5464 3.1341 2.8 6.9999 2.8h43.998c3.8661 0 6.9999-1.2536 6.9999-2.8v-300.4c0-1.5464-3.1341-2.8-6.9999-2.8z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

354
application/src/main/data/json/system/scada_symbols/drill-hp.svg

@ -0,0 +1,354 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Drill",
"description": "Drill with various states.",
"searchTags": [
"drill",
"drilling"
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "running",
"name": "{i18n:scada.symbol.running}",
"hint": "{i18n:scada.symbol.running-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.running}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "running"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "runningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "{i18n:scada.symbol.running}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "stoppedColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#666666",
"required": null,
"subLabel": "{i18n:scada.symbol.stopped}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<mask id="path-5-inside-1_4566_53869" fill="#ffffff">
<path d="m160 113.52v50.507h-20.5l-10.711-48.771c-13.492 6.47-22.789 20.101-22.789 35.871 0 22.024 18.132 39.877 40.5 39.877s40.5-17.853 40.5-39.877c0-17.362-11.27-32.133-27-37.607z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><mask id="path-7-inside-2_4566_53869" fill="#ffffff">
<path d="m40 113.52v50.507h20.5l10.711-48.771c13.492 6.47 22.789 20.101 22.789 35.871 0 22.024-18.132 39.877-40.5 39.877s-40.5-17.853-40.5-39.877c0-17.362 11.27-32.133 27-37.607z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g fill="#fff" tb:tag="background">
<path d="m62.143 81 10.732-80h54.25l10.732 80z" stroke="#1a1a1a" stroke-width="2"/>
<path d="m70 81h60v103.02c-0.122 0.098-0.274 0.218-0.453 0.358-0.534 0.419-1.312 1.019-2.284 1.739-1.945 1.442-4.662 3.363-7.751 5.283-3.092 1.921-6.539 3.829-9.947 5.255-3.421 1.431-6.73 2.344-9.565 2.344-2.8349 0-6.1436-0.913-9.5652-2.344-3.4077-1.426-6.8551-3.334-9.9469-5.255-3.0891-1.92-5.806-3.841-7.7511-5.283-0.9721-0.72-1.7501-1.32-2.2841-1.739-0.179-0.14-0.3305-0.26-0.4527-0.358z" stroke="#1a1a1a" stroke-width="2"/>
<path d="m120.9 81.054c4e-3 -0.0183 8e-3 -0.0363 0.012-0.054h24.671l15.413 23.301v60.699h-22.222c-0.093-0.212-0.209-0.478-0.346-0.794-0.389-0.898-0.951-2.199-1.637-3.812-1.373-3.226-3.245-7.696-5.241-12.672-4.001-9.972-8.476-21.912-10.459-29.961-2.355-9.559-2.358-18.942-1.767-25.951 0.296-3.5009 0.739-6.4002 1.108-8.4209 0.184-1.0102 0.35-1.8002 0.468-2.3352z" stroke="#1a1a1a" stroke-width="2"/>
<path d="m79.096 81.054c-0.0041-0.0183-0.0081-0.0363-0.012-0.054h-24.67l-15.413 23.301v60.699h22.222c0.0926-0.212 0.2084-0.478 0.3456-0.794 0.3896-0.898 0.951-2.199 1.6373-3.812 1.3728-3.226 3.2446-7.696 5.2411-12.672 4.0007-9.972 8.4754-21.912 10.459-29.961 2.3552-9.559 2.3585-18.942 1.7667-25.951-0.2956-3.5009-0.7389-6.4002-1.1075-8.4209-0.1842-1.0102-0.3497-1.8002-0.4685-2.3352z" stroke="#1a1a1a" stroke-width="2"/>
<path d="m160 113.52v50.507h-20.5l-10.711-48.771c-13.492 6.47-22.789 20.101-22.789 35.871 0 22.024 18.132 39.877 40.5 39.877s40.5-17.853 40.5-39.877c0-17.362-11.27-32.133-27-37.607z" clip-rule="evenodd" fill-rule="evenodd"/>
<path d="m40 113.52v50.507h20.5l10.711-48.771c13.492 6.47 22.789 20.101 22.789 35.871 0 22.024-18.132 39.877-40.5 39.877s-40.5-17.853-40.5-39.877c0-17.362 11.27-32.133 27-37.607z" clip-rule="evenodd" fill-rule="evenodd"/>
</g><path d="m139.62 111.98 0.876 52.023-2 0.033-0.876-52.022zm20.376 1.533h-2v-2.814l2.657 0.925zm0 50.507h2v2h-2zm-20.5 0v2h-1.608l-0.345-1.571zm-10.711-48.771-0.865-1.804 2.277-1.091 0.541 2.466zm33.211-1.736v50.507h-4v-50.507zm-2 52.507h-20.5v-4h20.5zm-22.453-1.571-10.712-48.771 3.907-0.858 10.711 48.771zm-7.893-47.397c-12.836 6.156-21.654 19.109-21.654 34.068h-4c0-16.58 9.776-30.89 23.924-37.675zm-21.654 34.068c0 20.89 17.208 37.877 38.5 37.877v4c-23.443 0-42.5-18.72-42.5-41.877zm38.5 37.877c21.292 0 38.5-16.987 38.5-37.877h4c0 23.157-19.057 41.877-42.5 41.877zm38.5-37.877c0-16.469-10.69-30.51-25.657-35.719l1.314-3.777c16.494 5.739 28.343 21.24 28.343 39.496z" fill="#1a1a1a" mask="url(#path-5-inside-1_4566_53869)"/><path d="m59.5 111.84v52.183h2v-52.183zm-19.5 1.676h2v-2.814l-2.6574 0.925zm0 50.507h-2v2h2zm20.5 0v2h1.6084l0.345-1.571zm10.711-48.771 0.8648-1.804-2.2766-1.091-0.5417 2.466zm-33.211-1.736v50.507h4v-50.507zm2 52.507h20.5v-4h-20.5zm22.453-1.571 10.711-48.771-3.9069-0.858-10.711 48.771zm7.8931-47.397c12.836 6.156 21.654 19.109 21.654 34.068h4c0-16.58-9.7765-30.89-23.924-37.675zm21.654 34.068c0 20.89-17.208 37.877-38.5 37.877v4c23.443 0 42.5-18.72 42.5-41.877zm-38.5 37.877c-21.292 0-38.5-16.987-38.5-37.877h-4c0 23.157 19.057 41.877 42.5 41.877zm-38.5-37.877c0-16.469 10.69-30.51 25.657-35.719l-1.3148-3.777c-16.493 5.739-28.343 21.24-28.343 39.496z" fill="#1a1a1a" mask="url(#path-7-inside-2_4566_53869)"/><path d="m121.36 81.141 17.501 23.335" stroke="#1a1a1a" stroke-width="2"/><path d="m138.65 164.92v-61.051" stroke="#1a1a1a" stroke-width="2"/><path d="m138.59 104.21h22.274" stroke="#1a1a1a" stroke-width="2"/><path d="m79.019 81.141-17.854 23.246" stroke="#1a1a1a" stroke-width="2"/><path d="m61.342 164.58-0.17678-60.281" stroke="#1a1a1a" stroke-width="2"/><path d="m39.023 104.3 22.185 0.0442" stroke="#1a1a1a" stroke-width="2"/><path d="m67.264 0s-67.264 0-67.264 33.5v164.18c0 1.3256 1.7909 2.3202 4 2.3202h192c2.209 0 4-0.9946 4-2.3202v-164.18c0-33.5-66.07-33.5-66.07-33.5h-33.929zm67.07 40.6c-1.2887 0-2.3333 0.6268-2.3333 1.4v150.2c0 0.7732 1.0447 1.4 2.3333 1.4h14.666c1.2887 0 2.3333-0.6268 2.3333-1.4v-150.2c0-0.7732-1.0447-1.4-2.3333-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

361
application/src/main/data/json/system/scada_symbols/drilling-line-hp.svg

@ -0,0 +1,361 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Drilling line",
"description": "Drilling line with various states.",
"searchTags": [
"drilling line"
],
"widgetSizeX": 1,
"widgetSizeY": 2,
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "secondary-background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = '#dedede';\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "running",
"name": "{i18n:scada.symbol.running}",
"hint": "{i18n:scada.symbol.running-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.running}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "running"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "runningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "{i18n:scada.symbol.running}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "stoppedColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#666666",
"required": null,
"subLabel": "{i18n:scada.symbol.stopped}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<g fill="#fff" stroke-width="2" tb:tag="background">
<path d="m63 121h74v240h-74z" stroke="#1A1A1A"/>
<ellipse cx="100.54" cy="101" rx="99.422" ry="100"/>
<g transform="translate(-.043811 1)" clip-path="url(#clip0_4864_4144)" stroke="#727171">
<path d="m97.225 37.672c-7.5861-19.484-16.826-28.051-22.054-34.859l-28.813 0.75778-20.347 24.447c1.6695-1.8123 10.516-0.092 32.545 21.288 22.029 21.38 29.908 40.861 31.093 47.929l15.475-10.344c0-15.191-0.313-29.735-7.899-49.219z"/>
<path d="m166.5 132.89c20.8 2.127 32.637-2.193 41.079-3.744l12.457-25.992-12.488-29.253c0.852 2.312-4.71 9.4022-33.78 19.267-29.07 9.8649-50.001 7.9989-56.833 5.8331l-3.553 17.159c13.521 6.923 32.318 14.603 53.118 16.73z"/>
<path d="m46.728 132.5c-14.634 14.934-23.517 27.044-27.569 34.611l12.551 25.947 30.656 8.48c-2.3389-0.776-4.4126-9.546 6.0037-38.423s24.928-44.076 30.881-48.066l-12.593-9.458c-13.844 6.253-25.296 11.976-39.93 26.909z"/>
</g>
<circle cx="100.47" cy="101.51" r="20.796" fill="#dedede" stroke="#000" tb:tag="secondary-background"/>
<path d="m39 361h122v38h-122z" fill="#dedede" stroke="#000" tb:tag="secondary-background"/>
</g><ellipse cx="100.11" cy="101" rx="99.009" ry="99.164" stroke="#000" stroke-width="2"/><path d="m67.264 0s-67.264 0-67.264 67v328.36c0 2.6512 1.7909 4.6404 4 4.6404h192c2.209 0 4-1.9892 4-4.6404v-328.36c0-67-66.07-67-66.07-67h-33.929zm67.07 81.2c-1.2887 0-2.3333 1.2536-2.3333 2.8v300.4c0 1.5464 1.0447 2.8 2.3333 2.8h14.666c1.2887 0 2.3333-1.2536 2.3333-2.8v-300.4c0-1.5464-1.0447-2.8-2.3333-2.8z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><defs>
<clipPath id="clip0_4864_4144">
<rect width="200" height="200" rx="100" fill="#fff"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

354
application/src/main/data/json/system/scada_symbols/drilling-rig-hp.svg

@ -0,0 +1,354 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="1800" fill="none" version="1.1" viewBox="0 0 800 1800"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Drilling rig",
"description": "Drilling rig with various states.",
"searchTags": [
"drilling rig"
],
"widgetSizeX": 4,
"widgetSizeY": 9,
"tags": [
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "drilling-ring",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "running",
"name": "{i18n:scada.symbol.running}",
"hint": "{i18n:scada.symbol.running-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.running}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "running"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "runningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "{i18n:scada.symbol.running}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "stoppedColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#666666",
"required": null,
"subLabel": "{i18n:scada.symbol.stopped}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<path d="m280.06 201h239.89c2.435 0 4.516 1.754 4.928 4.153l273.94 1593.8h-797.63l273.94-1593.8c0.413-2.399 2.493-4.153 4.928-4.153z" stroke="#1a1a1a" stroke-width="2"/><line x1="276.08" x2="575.67" y1="202.29" y2="501.88" stroke="#1a1a1a" stroke-width="1.9986"/><line x1="523.29" x2="224.71" y1="201.71" y2="500.29" stroke="#1a1a1a" stroke-width="1.9986"/><line x1="225.08" x2="647.67" y1="500.29" y2="922.88" stroke="#1a1a1a" stroke-width="1.999"/><line x1="573.88" x2="151.29" y1="502.29" y2="924.88" stroke="#1a1a1a" stroke-width="1.999"/><line x1="152.71" x2="751.29" y1="925.29" y2="1523.9" stroke="#1a1a1a" stroke-width="1.9993"/><line x1="647.29" x2="48.707" y1="924.71" y2="1523.3" stroke="#1a1a1a" stroke-width="1.9993"/><path d="m389.98 1028.8c2.365-3.65 3.896-6.72 4.6109-9.87 0.64099-2.82 0.61099-5.64 0.017-8.92h30.21c0.58999 3.57 0.85699 11.11-3.162 23.22-1.333 4.02-4.2859 7.86-8.1039 11.68-3.19 3.19-6.9109 6.3-10.681 9.45-0.73999 0.62-1.482 1.24-2.223 1.86-4.4839 3.77-8.8969 7.63-12.264 11.69-3.366 4.07-5.7649 8.44-6.0849 13.24-0.61599 9.24 1.978 15.78 5.8949 20.39 3.8929 4.58 9.0239 7.17 13.365 8.69 3.366 1.17 10.24 1.99 16.595-0.23 3.199-1.11 6.2889-3.01 8.7169-6.03 2.429-3.03 4.1489-7.14 4.6969-12.59 0.349-3.48 0.607-6.53 0.82999-9.15 0.119-1.4 0.227-2.69 0.334-3.85 0.31099-3.38 0.60399-5.72 1.064-7.25 0.461-1.53 0.96899-1.88 1.476-1.96 0.337-0.05 0.81799-0.01 1.519 0.2 0.69499 0.21 1.536 0.57 2.566 1.09 11.321 5.64 15.547 14.83 16.45 23.83 0.90799 9.05-1.564 17.88-3.637 22.53-8.6369 19.38-26.348 31.19-48.797 31.19-13.467 0-23.767-2.39-32.055-6.71-8.2839-4.32-14.63-10.62-20.134-18.55-4.4109-6.36-8.8289-16.85-9.9249-28.56-1.095-11.69 1.125-24.52 9.8889-35.66 8.2179-10.45 16.325-15.53 23.13-19.13 1.229-0.65 2.434-1.27 3.598-1.85 2.026-1.03 3.924-1.99 5.5949-2.99 2.665-1.58 4.9279-3.33 6.5099-5.76z" fill="#dedede" stroke="#1a1a1a" stroke-width="2"/><path d="m339 1150h122v38h-122z" fill="#dedede" stroke="#1a1a1a" stroke-width="2"/><path d="m339 981h122v38h-122z" fill="#dedede" stroke="#1a1a1a" stroke-width="2"/><path d="m363 1188h74v487h-74z" fill="#dedede" stroke="#1a1a1a" stroke-width="2"/><path d="m363 201h74v780h-74z" stroke="#1a1a1a" stroke-width="2"/><circle cx="401.33" cy="120" r="119" fill="#dedede" stroke="#1a1a1a" stroke-width="2.0026"/><g clip-path="url(#clip0_4613_175962)" fill="#dedede">
<path d="m397.27 45.19c-9.0358-23.378-20.04-33.657-26.268-41.826l-34.32 0.90925-24.236 29.334c1.9888-2.1746 12.525-0.11046 38.765 25.543 26.239 25.653 35.623 49.028 37.035 57.509l18.432-12.412c0-18.227-0.37246-35.679-9.4092-59.057z" stroke="#727171" stroke-width="1.996"/>
<path d="m480.57 159.3c24.917 2.557 39.097-2.6364 49.21-4.5l14.922-31.246-14.959-35.167c1.0206 2.7794-5.643 11.303-40.467 23.162-34.824 11.859-59.898 9.617-68.082 7.0128l-4.2568 20.627c16.198 8.3224 38.716 17.554 63.633 20.112z" stroke="#727171" stroke-width="2.0051"/>
<path d="m337.34 158.51c-17.566 17.88-28.229 32.38-33.094 41.44l15.066 31.067 36.799 10.153c-2.8073-0.928-5.2964-11.429 7.2073-46.004 12.504-34.575 29.922-52.773 37.069-57.55l-15.116-11.324c-16.619 7.4871-30.366 14.338-47.932 32.219z" stroke="#727171" stroke-width="2.0037"/>
<ellipse cx="401.68" cy="120.34" rx="24.997" ry="24.997" stroke="#1a1a1a" stroke-width="2.0051"/>
</g><circle cx="401.33" cy="120" r="119" stroke="#1a1a1a" stroke-width="2.0026"/><defs>
<clipPath id="clip0_4613_175962">
<rect x="281.33" width="240" height="240" rx="120" fill="#fff"/>
</clipPath>
</defs><g fill="#fff" stroke="#1a1a1a" stroke-width="2" tb:tag="drilling-ring">
<path d="m229 1675h342v91h-342z"/>
<path d="m141 1765h518v34h-518z"/>
</g><path d="m269.06 0s-269.06 0-269.06 301.5v1477.6c0 11.93 7.1636 20.882 16 20.882h768c8.836 0 16-8.9514 16-20.882v-1477.6c0-301.5-264.28-301.5-264.28-301.5h-135.72zm268.28 365.4c-5.1548 0-9.3332 5.6412-9.3332 12.6v1351.8c0 6.9588 4.1788 12.6 9.3332 12.6h58.664c5.1548 0 9.3332-5.6412 9.3332-12.6v-1351.8c0-6.9588-4.1788-12.6-9.3332-12.6z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

293
application/src/main/data/json/system/scada_symbols/hook-hp.svg

@ -0,0 +1,293 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Hook",
"description": "Hook with various states.",
"searchTags": [
"hook",
"drilling"
],
"widgetSizeX": 1,
"widgetSizeY": 2,
"tags": [
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "hook",
"stateRenderFunction": "element.attr({fill: ctx.properties.hookColor});",
"actions": null
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "hookColor",
"name": "{i18n:scada.symbol.hook-color}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "",
"divider": false,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<g fill="#fff" stroke="#1a1a1a" stroke-width="2" tb:tag="hook">
<path d="m83.979 279.78c2.3649-3.644 3.8966-6.715 4.6115-9.864 0.64089-2.823 0.61129-5.644 0.017-8.921h30.209c0.58999 3.566 0.85699 11.114-3.162 23.222-1.333 4.015-4.2859 7.854-8.1039 11.674-3.19 3.19-6.9109 6.3-10.681 9.45-0.74019 0.619-1.4823 1.239-2.2226 1.862-4.4846 3.772-8.8978 7.63-12.264 11.697-3.3665 4.066-5.7648 8.433-6.0848 13.232-0.61659 9.244 1.9776 15.781 5.8941 20.39 3.8938 4.582 9.0245 7.176 13.366 8.691 3.3656 1.175 10.24 1.992 16.594-0.225 3.199-1.116 6.2889-3.011 8.7169-6.037 2.429-3.028 4.149-7.131 4.6969-12.59 0.34899-3.477 0.60699-6.524 0.82999-9.148 0.119-1.403 0.22699-2.684 0.33399-3.846 0.311-3.382 0.60399-5.72 1.064-7.249 0.46099-1.528 0.96899-1.88 1.476-1.959 0.33699-0.054 0.81799-0.013 1.519 0.199 0.69499 0.211 1.536 0.57 2.566 1.084 11.321 5.643 15.547 14.835 16.45 23.832 0.90798 9.053-1.564 17.881-3.637 22.532-8.6369 19.376-26.348 31.189-48.797 31.189-13.468 0-23.767-2.387-32.055-6.711-8.2843-4.322-14.63-10.617-20.134-18.55-4.4111-6.357-8.8287-16.852-9.925-28.56-1.0945-11.689 1.1254-24.52 9.889-35.661 8.2183-10.447 16.325-15.531 23.129-19.132 1.2292-0.65 2.4347-1.261 3.5979-1.851 2.0264-1.027 3.9244-1.989 5.5955-2.981 2.6649-1.581 4.9275-3.332 6.5092-5.769z"/>
<path d="m58.08 261.38c-1.9988-1.28-4.4082-3.607-7.1067-6.92-2.688-3.299-5.6227-7.526-8.6813-12.552-6.1167-10.053-12.701-23.261-18.777-38.55-12.154-30.585-22.24-69.418-22.511-107.89-0.43146-61.407 29.982-85.126 51.186-90.56 10.719-2.7467 30.395-3.9859 49.703-3.9086 19.316 0.07727 38.034 1.4712 46.85 3.9039 17.425 4.8087 50.257 32.066 50.257 90.571 0 32.108-9.83 70.081-22.104 101.85-6.133 15.874-12.866 30.17-19.264 41.38-6.423 11.254-12.436 19.266-17.095 22.714-1.119 0.828-3.04 1.62-5.682 2.327-2.615 0.701-5.841 1.294-9.494 1.775-7.304 0.96-16.24 1.459-25.255 1.477-9.0138 0.019-18.079-0.442-25.642-1.393-3.7817-0.476-7.1717-1.072-9.9838-1.787-2.8296-0.72-5.0029-1.542-6.4004-2.437z"/>
</g><mask id="path-5-inside-1_4471_52357" fill="#ffffff">
<path d="m172.8 99c0 39.765-32.236 72-72 72-39.765 0-72-32.235-72-72 0-39.764 32.236-72 72-72 39.764 0 72 32.236 72 72zm-132.78 0c0 33.57 27.214 60.784 60.784 60.784 33.57 0 60.784-27.214 60.784-60.784 0-33.57-27.214-60.784-60.784-60.784-33.57 0-60.784 27.214-60.784 60.784z"/>
</mask><path d="m172.8 99c0 39.765-32.236 72-72 72-39.765 0-72-32.235-72-72 0-39.764 32.236-72 72-72 39.764 0 72 32.236 72 72zm-132.78 0c0 33.57 27.214 60.784 60.784 60.784 33.57 0 60.784-27.214 60.784-60.784 0-33.57-27.214-60.784-60.784-60.784-33.57 0-60.784 27.214-60.784 60.784z" fill="#dedede" mask="url(#path-5-inside-1_4471_52357)" stroke="#1a1a1a" stroke-width="4"/><path d="m67.264 0s-67.264 0-67.264 67v328.36c0 2.6512 1.7909 4.6404 4 4.6404h192c2.209 0 4-1.9892 4-4.6404v-328.36c0-67-66.07-67-66.07-67h-33.929zm67.07 81.2c-1.2887 0-2.3333 1.2536-2.3333 2.8v300.4c0 1.5464 1.0447 2.8 2.3333 2.8h14.666c1.2887 0 2.3333-1.2536 2.3333-2.8v-300.4c0-1.5464-1.0447-2.8-2.3333-2.8z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

743
application/src/main/data/json/system/scada_symbols/meter.svg

@ -0,0 +1,743 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="400" fill="none" version="1.1" viewBox="0 0 100 400"><tb:metadata xmlns=""><![CDATA[{
"title": "Meter",
"description": "Meter displays the current value with a moving pointer on the scale.",
"searchTags": [
"scale",
"level",
"progress",
"thermometer"
],
"widgetSizeX": 1,
"widgetSizeY": 4,
"tags": [
{
"tag": "background",
"stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "progress-indicator",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = initial - (normalizedValue * initial);\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\n\nif (ctx.properties.progressArrow && !ctx.properties.progressBar) {\n element.show();\n var initial = ctx.properties.valueBox ? 329: 366;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n translateY: initial\n });\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressArrowColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill});\n \n var offset = calculateOffset(value, minValue, maxValue, initial);\n\n var elementOffset = element.remember('offset');\n if (offset !== elementOffset) {\n element.remember('offset', offset);\n ctx.api.cssAnimate(element, 500).transform({\n translateY: offset\n });\n }\n} else {\n element.hide();\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "progressBar",
"stateRenderFunction": "if (ctx.properties.progressBar) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "progressBorder",
"stateRenderFunction": "if (!ctx.properties.valueBox) {\n element.attr({'height': 367})\n}",
"actions": null
},
{
"tag": "progressCircle",
"stateRenderFunction": "if (!ctx.properties.valueBox) {\n element.attr({cy:383});\n}",
"actions": null
},
{
"tag": "progressFill",
"stateRenderFunction": "if (!ctx.properties.valueBox) {\n element.attr({y:-378});\n}\n\nfunction calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = normalizedValue * initial;\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\nif (ctx.properties.progressBar) {\n var initial = ctx.properties.valueBox ? 329: 366;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 2});\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressBarColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill, stroke: fill});\n ctx.tags.progressCircle[0].fill(fill);\n \n var height = calculateOffset(value, minValue, maxValue, initial);\n\n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height+2});\n }\n} else {\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "var scaleSet = element.remember('scaleSet');\nif (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n \n var start = 11;\n var end = ctx.properties.valueBox ? 328 : 365;\n var majorIntervalLength = end / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n element.add(ctx.svg.line(63, end+11, 63, 11).stroke({ width: 1 }).attr({class: 'majorTick'}));\n for (var i = 0; i < majorIntervals+1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(51, y, 63, y).stroke({ width: 1 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (maxValue - ((maxValue - (minValue)) / (majorIntervals) * i)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 45, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n}\n\nvar majorFont = ctx.properties.majorFont;\nvar majorColor = ctx.properties.majorColor;\nvar minorColor = ctx.properties.minorColor;\nif (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n} else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n}\n\nvar majorTicks = element.find('line.majorTick');\nmajorTicks.forEach(t => t.attr({stroke: majorColor}));\n\nvar majorTicksText = element.find('text.majorTickText');\nctx.api.font(majorTicksText, majorFont, majorColor);\n\nvar minorTicks = element.find('line.minorTick');\nminorTicks.forEach(t => t.attr({stroke: minorColor}));\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(57, minorY, 63, minorY).stroke({ width: 1 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
"tag": "value-box",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "value-box-background",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var colorProcessor = ctx.properties.valueBoxColor;\n colorProcessor.update(ctx.values.value);\n element.attr({fill: colorProcessor.color});\n}",
"actions": null
},
{
"tag": "value-text",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var valueTextFont = ctx.properties.valueTextFont;\n var valueTextColor = ctx.properties.valueTextColor;\n var currentVolume = ctx.values.value;\n var valueText = ctx.api.formatValue(currentVolume, 0, ctx.properties.valueUnits, false);\n var colorProcessor = ctx.properties.valueTextColor;\n colorProcessor.update(ctx.values.value);\n ctx.api.font(element, valueTextFont, colorProcessor.color);\n ctx.api.text(element, valueText);\n}",
"actions": null
}
],
"behavior": [
{
"id": "value",
"name": "{i18n:scada.symbol.value}",
"hint": null,
"group": null,
"type": "value",
"valueType": "DOUBLE",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": {
"action": "GET_TIME_SERIES",
"defaultValue": null,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "waterLevel"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning-state}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical-state}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.critical-state-animation}",
"hint": "{i18n:scada.symbol.critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "minValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 0,
"required": true,
"subLabel": "{i18n:scada.symbol.min-value}",
"divider": true,
"min": -1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "maxValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 100,
"required": true,
"subLabel": "{i18n:scada.symbol.max-value}",
"max": 1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "backgroundColor",
"name": "{i18n:scada.symbol.background-color}",
"type": "color",
"default": "#FFFFFF",
"disabled": false,
"visible": true
},
{
"id": "progressBar",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "switch",
"default": true,
"disabled": false,
"visible": true
},
{
"id": "progressBarColor",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#4D94E1",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressBar",
"disabled": false,
"visible": true
},
{
"id": "progressArrow",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "switch",
"default": false,
"disabled": false,
"visible": true
},
{
"id": "progressArrowColor",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#1C943E",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressArrow",
"disabled": true,
"visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
"disabled": false,
"visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#F3F3F3",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "%",
"subLabel": "{i18n:scada.symbol.units}",
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "valueTextFont",
"name": "{i18n:scada.symbol.value-text}",
"type": "font",
"default": {
"size": 14,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#0000008A",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 10,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
"min": 1,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "majorFont",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "font",
"default": {
"size": 12,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disabled": false,
"visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 10,
"subLabel": "{i18n:scada.symbol.intervals}",
"min": 1,
"disabled": false,
"visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
}
]
}]]></tb:metadata>
<path d="m16 400c-2.2091 0-4-1.791-4-4v-392c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v392c0 2.209-1.7909 4-4 4h-68z" fill="#E5E5E5" tb:tag="background"/><path d="m16 400c-2.2091 0-4-1.791-4-4v-392c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v392c0 2.209-1.7909 4-4 4h-68z" fill="url(#paint0_linear_2475_365165)"/><path d="m16 398.5c-1.3807 0-2.5-1.119-2.5-2.5v-392c0-1.3807 1.1193-2.5 2.5-2.5h68c1.3807 0 2.5 1.1193 2.5 2.5v392c0 1.381-1.1193 2.5-2.5 2.5h-68z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="scale">
<path d="m27.539 10.44v8.5605h-1.4121v-6.8848l-2.0918 0.709v-1.166l3.3339-1.2187h0.17zm8.7754 3.58v1.3946c0 0.6679-0.0665 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.2461 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5468 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.5859 0.7032 0.1641 0.2851 0.2891 0.6308 0.375 1.0371 0.086 0.4023 0.129 0.8711 0.129 1.4062zm-1.4122 1.5938v-1.8047c0-0.3398-0.0195-0.6387-0.0585-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1133-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4336 0.3984-0.1171 0.1836-0.207 0.4258-0.2695 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0195 0.6465 0.0586 0.9082s0.0977 0.4863 0.1758 0.6738c0.0781 0.1836 0.1719 0.3359 0.2812 0.457 0.1133 0.1172 0.2422 0.2032 0.3868 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2226 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441 0.0585-0.3047 0.0878-0.6699 0.0878-1.0957zm8.4825-1.5938v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z" fill="#000" fill-opacity=".38"/>
<path d="m31.891 50.186h0.1172c0.5391 0 0.9883-0.0703 1.3477-0.2109 0.3633-0.1445 0.6523-0.3438 0.8672-0.5977 0.2148-0.2539 0.3691-0.5527 0.4628-0.8965 0.0938-0.3437 0.1407-0.7168 0.1407-1.1191v-1.4707c0-0.3477-0.0371-0.6523-0.1114-0.9141-0.0703-0.2656-0.1718-0.4863-0.3046-0.6621-0.129-0.1797-0.2793-0.3144-0.4512-0.4043-0.168-0.0898-0.3496-0.1347-0.5449-0.1347-0.2149 0-0.4082 0.0488-0.5801 0.1465-0.168 0.0937-0.3106 0.2246-0.4278 0.3925-0.1132 0.1641-0.2011 0.3575-0.2636 0.5801-0.0586 0.2188-0.0879 0.4531-0.0879 0.7031 0 0.2344 0.0273 0.461 0.082 0.6797 0.0586 0.2149 0.1445 0.4063 0.2578 0.5742 0.1133 0.168 0.2559 0.3008 0.4278 0.3985 0.1718 0.0976 0.373 0.1465 0.6035 0.1465 0.2187 0 0.4199-0.0411 0.6035-0.1231 0.1836-0.0859 0.3437-0.2012 0.4805-0.3457 0.1367-0.1445 0.2441-0.3066 0.3222-0.4863 0.0782-0.1797 0.1211-0.3633 0.1289-0.5508l0.5391 0.1641c0 0.2968-0.0625 0.5898-0.1875 0.8789-0.1211 0.2851-0.291 0.5468-0.5098 0.7851-0.2148 0.2344-0.4668 0.4219-0.7558 0.5625-0.2852 0.1407-0.5957 0.211-0.9317 0.211-0.4062 0-0.7656-0.0762-1.0781-0.2286-0.3086-0.1562-0.5664-0.3671-0.7734-0.6328-0.2032-0.2656-0.3555-0.5703-0.4571-0.914-0.1015-0.3438-0.1523-0.7051-0.1523-1.084 0-0.4102 0.0625-0.7949 0.1875-1.1543s0.3066-0.6758 0.5449-0.9492c0.2383-0.2774 0.5274-0.4922 0.8672-0.6446 0.3438-0.1562 0.7324-0.2343 1.166-0.2343 0.461 0 0.8653 0.0898 1.2129 0.2695 0.3477 0.1797 0.6406 0.4277 0.8789 0.7441s0.418 0.6836 0.5391 1.1016 0.1816 0.8672 0.1816 1.3477v0.498c0 0.5039-0.0449 0.9902-0.1347 1.459-0.0899 0.4648-0.2364 0.8965-0.4395 1.2949-0.1992 0.3945-0.4629 0.7422-0.791 1.043-0.3242 0.2969-0.7227 0.5293-1.1953 0.6972-0.4688 0.1641-1.0196 0.2461-1.6524 0.2461h-0.1289v-1.166zm11.494-3.8672v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.293-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2382 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3926 0.2305 0.1796 0.4258 0.414 0.586 0.7031 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0546-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3985-0.1172 0.1835-0.2071 0.4257-0.2696 0.7265-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.336 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z" fill="#000" fill-opacity=".38"/>
<path d="m36.332 81.274c0 0.5313-0.123 0.9785-0.3691 1.3418s-0.582 0.6387-1.0078 0.8262c-0.4219 0.1836-0.8985 0.2754-1.4297 0.2754-0.5313 0-1.0098-0.0918-1.4356-0.2754-0.4257-0.1875-0.7617-0.4629-1.0078-0.8262s-0.3691-0.8105-0.3691-1.3418c0-0.3515 0.0683-0.6699 0.2051-0.9551 0.1367-0.289 0.33-0.5371 0.58-0.7441 0.254-0.2109 0.5508-0.373 0.8907-0.4863 0.3437-0.1133 0.7187-0.1699 1.125-0.1699 0.539 0 1.0215 0.0996 1.4472 0.2988 0.4258 0.1992 0.7598 0.4746 1.002 0.8262 0.2461 0.3515 0.3691 0.7617 0.3691 1.2304zm-1.4179-0.0703c0-0.2851-0.0586-0.5351-0.1758-0.75-0.1172-0.2148-0.2813-0.3808-0.4922-0.498s-0.4551-0.1758-0.7324-0.1758c-0.2813 0-0.5254 0.0586-0.7325 0.1758-0.207 0.1172-0.3691 0.2832-0.4863 0.498-0.1133 0.2149-0.1699 0.4649-0.1699 0.75 0 0.2891 0.0566 0.5391 0.1699 0.75 0.1133 0.207 0.2754 0.3652 0.4863 0.4746 0.211 0.1094 0.459 0.1641 0.7442 0.1641 0.2851 0 0.5312-0.0547 0.7383-0.1641 0.207-0.1094 0.3672-0.2676 0.4804-0.4746 0.1133-0.2109 0.17-0.4609 0.17-0.75zm1.2246-3.8906c0 0.4258-0.1133 0.8047-0.3399 1.1367-0.2226 0.332-0.5312 0.5938-0.9258 0.7852-0.3945 0.1875-0.8437 0.2812-1.3476 0.2812-0.5078 0-0.9609-0.0937-1.3594-0.2812-0.3945-0.1914-0.7051-0.4532-0.9316-0.7852-0.2227-0.332-0.334-0.7109-0.334-1.1367 0-0.5078 0.1113-0.9356 0.334-1.2832 0.2265-0.3516 0.5371-0.6192 0.9316-0.8027 0.3945-0.1836 0.8457-0.2754 1.3535-0.2754s0.959 0.0918 1.3535 0.2754c0.3946 0.1835 0.7032 0.4511 0.9258 0.8027 0.2266 0.3476 0.3399 0.7754 0.3399 1.2832zm-1.4121 0.0469c0-0.2539-0.0508-0.4766-0.1524-0.668-0.0976-0.1953-0.2363-0.3477-0.416-0.457-0.1797-0.1094-0.3926-0.1641-0.6387-0.1641s-0.459 0.0527-0.6386 0.1582c-0.1797 0.1055-0.3184 0.2539-0.4161 0.4453-0.0976 0.1914-0.1464 0.4199-0.1464 0.6856 0 0.2617 0.0488 0.4902 0.1464 0.6855 0.0977 0.1914 0.2364 0.3418 0.4161 0.4512 0.1836 0.1094 0.3984 0.164 0.6445 0.164s0.459-0.0546 0.6387-0.164 0.3183-0.2598 0.416-0.4512c0.0976-0.1953 0.1465-0.4238 0.1465-0.6855zm8.6582 1.2597v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4687-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3399-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z" fill="#000" fill-opacity=".38"/>
<path d="m36.42 107.37v0.773l-3.4101 7.758h-1.4883l3.4043-7.406h-4.418v-1.125h5.9121zm6.9649 3.55v1.395c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.142-0.246 0.293-0.541 0.508-0.8847 0.645-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.221-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.395c0-0.671 0.0664-1.238 0.1992-1.699 0.1328-0.465 0.3222-0.842 0.5683-1.131 0.2461-0.293 0.5391-0.506 0.8789-0.638 0.3438-0.133 0.7266-0.2 1.1485-0.2 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.406zm-1.4121 1.594v-1.805c0-0.339-0.0196-0.638-0.0586-0.896-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.118-0.2422-0.202-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.083v1.805c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.117 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m35.02 139.62h0.1758v1.155h-0.0996c-0.5039 0-0.9395 0.078-1.3066 0.234-0.3633 0.156-0.6621 0.371-0.8965 0.644-0.2344 0.274-0.4102 0.594-0.5274 0.961-0.1132 0.364-0.1699 0.75-0.1699 1.161v1.341c0 0.34 0.0371 0.641 0.1113 0.903 0.0743 0.257 0.1778 0.474 0.3106 0.65 0.1367 0.172 0.293 0.303 0.4687 0.393 0.1758 0.089 0.3653 0.134 0.5684 0.134 0.2109 0 0.4023-0.043 0.5742-0.129 0.1719-0.089 0.3184-0.212 0.4395-0.369 0.1211-0.156 0.2129-0.341 0.2754-0.556s0.0937-0.45 0.0937-0.703c0-0.243-0.0312-0.469-0.0937-0.68-0.0586-0.215-0.1465-0.402-0.2637-0.563-0.1172-0.164-0.2637-0.291-0.4395-0.38-0.1718-0.094-0.3711-0.141-0.5976-0.141-0.2813 0-0.5371 0.066-0.7676 0.199-0.2266 0.133-0.4102 0.307-0.5508 0.522-0.1367 0.211-0.2109 0.435-0.2226 0.673l-0.5391-0.175c0.0313-0.364 0.1113-0.69 0.2402-0.979 0.1328-0.289 0.3067-0.535 0.5215-0.738 0.2149-0.203 0.4629-0.358 0.7442-0.463 0.2851-0.109 0.5976-0.164 0.9375-0.164 0.414 0 0.7734 0.078 1.0781 0.234 0.3047 0.157 0.5566 0.369 0.7558 0.639 0.2032 0.266 0.3536 0.57 0.4512 0.914 0.1016 0.34 0.1524 0.695 0.1524 1.066 0 0.411-0.0625 0.793-0.1875 1.149-0.125 0.351-0.3086 0.66-0.5508 0.926-0.2383 0.265-0.5293 0.472-0.8731 0.621-0.3398 0.148-0.7265 0.222-1.1601 0.222-0.4571 0-0.8653-0.088-1.2246-0.263-0.3555-0.176-0.6582-0.418-0.9082-0.727-0.2461-0.308-0.4336-0.664-0.5625-1.066-0.1289-0.403-0.1934-0.828-0.1934-1.278v-0.586c0-0.648 0.082-1.259 0.2461-1.834 0.1641-0.578 0.416-1.087 0.7559-1.529 0.3437-0.441 0.7832-0.787 1.3183-1.037 0.5352-0.254 1.1738-0.381 1.916-0.381zm8.3653 3.604v1.394c0 0.668-0.0664 1.239-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.508-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.085-0.543-0.22-0.7734-0.404-0.2305-0.183-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.642-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.699 0.1328-0.465 0.3222-0.842 0.5683-1.131 0.2461-0.293 0.5391-0.506 0.8789-0.639 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.213 0.7734 0.392 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.286 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.872 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.261-0.0977-0.482-0.1758-0.662-0.0742-0.183-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.215-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.297-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.909 0.0391 0.261 0.0977 0.486 0.1758 0.674 0.0781 0.183 0.1719 0.335 0.2813 0.457 0.1132 0.117 0.2421 0.203 0.3867 0.257 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129 0.1719-0.085 0.3164-0.222 0.4336-0.41 0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m32.236 176.53-1.125-0.275 0.4629-4.289h4.5996v1.195h-3.4277l-0.2344 2.092c0.1328-0.078 0.3164-0.154 0.5508-0.229 0.2344-0.078 0.502-0.117 0.8027-0.117 0.4024 0 0.7618 0.067 1.0782 0.199 0.3203 0.129 0.5918 0.319 0.8144 0.569 0.2227 0.246 0.3926 0.547 0.5098 0.902 0.1172 0.352 0.1758 0.748 0.1758 1.19 0 0.394-0.0586 0.763-0.1758 1.107-0.1133 0.344-0.2852 0.646-0.5156 0.908-0.2305 0.262-0.5215 0.467-0.8731 0.615-0.3477 0.145-0.7598 0.217-1.2363 0.217-0.3555 0-0.6973-0.051-1.0254-0.152-0.3242-0.106-0.6152-0.26-0.8731-0.463-0.2578-0.207-0.4648-0.461-0.6211-0.762-0.1562-0.304-0.246-0.654-0.2695-1.049h1.3828c0.0352 0.278 0.1114 0.514 0.2285 0.709 0.1211 0.192 0.2813 0.338 0.4805 0.44 0.1992 0.101 0.4297 0.152 0.6914 0.152 0.2383 0 0.4434-0.041 0.6153-0.123 0.1718-0.086 0.3144-0.207 0.4277-0.363 0.1172-0.16 0.2031-0.348 0.2578-0.563 0.0586-0.214 0.0879-0.453 0.0879-0.714 0-0.25-0.0332-0.479-0.0996-0.686-0.0625-0.207-0.1582-0.387-0.2871-0.539-0.125-0.152-0.2832-0.27-0.4746-0.352-0.1914-0.086-0.4121-0.129-0.6621-0.129-0.336 0-0.5938 0.049-0.7735 0.147-0.1758 0.098-0.3398 0.219-0.4922 0.363zm11.148-1.013v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.465 0.3222-0.841 0.5683-1.131 0.2461-0.293 0.5391-0.505 0.8789-0.638 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.128 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m36.631 209.76v1.125h-6.1524l-0.0469-0.85 3.6797-5.765h1.1309l-1.2246 2.097-2.1153 3.393h4.7286zm-1.0664-5.49v8.531h-1.4122v-8.531h1.4122zm7.8203 3.55v1.395c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.849-0.5625 1.142-0.246 0.293-0.541 0.508-0.8847 0.645-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.221-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.395c0-0.672 0.0664-1.238 0.1992-1.699 0.1328-0.465 0.3222-0.842 0.5683-1.131 0.2461-0.293 0.5391-0.506 0.8789-0.638 0.3438-0.133 0.7266-0.2 1.1485-0.2 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.402 0.1289 0.871 0.1289 1.406zm-1.4121 1.594v-1.805c0-0.339-0.0196-0.638-0.0586-0.896-0.0391-0.262-0.0977-0.483-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.446-0.1132-0.117-0.2422-0.201-0.3867-0.251-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.042-0.586 0.128-0.1718 0.082-0.3164 0.215-0.4335 0.399-0.1172 0.183-0.2071 0.426-0.2696 0.726-0.0586 0.297-0.0879 0.659-0.0879 1.084v1.805c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.117 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.223 0.4336-0.41c0.1172-0.191 0.2051-0.44 0.2637-0.744 0.0586-0.305 0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m32.412 240.2h0.8438c0.3281 0 0.5996-0.057 0.8144-0.17 0.2188-0.113 0.3809-0.27 0.4863-0.469 0.1055-0.199 0.1582-0.428 0.1582-0.685 0-0.27-0.0488-0.5-0.1464-0.692-0.0938-0.195-0.2383-0.346-0.4336-0.451-0.1914-0.105-0.4356-0.158-0.7325-0.158-0.25 0-0.4765 0.051-0.6796 0.152-0.1993 0.098-0.3575 0.238-0.4747 0.422-0.1171 0.18-0.1757 0.395-0.1757 0.645h-1.418c0-0.454 0.1191-0.856 0.3574-1.207 0.2383-0.352 0.5625-0.627 0.9727-0.827 0.414-0.203 0.8789-0.304 1.3945-0.304 0.5508 0 1.0313 0.091 1.4414 0.275 0.4141 0.18 0.7363 0.449 0.9668 0.809 0.2305 0.359 0.3457 0.804 0.3457 1.336 0 0.242-0.0566 0.488-0.1699 0.738s-0.2813 0.478-0.5039 0.685c-0.2227 0.204-0.5 0.37-0.832 0.498-0.3321 0.125-0.7168 0.188-1.1543 0.188h-1.0606v-0.785zm0 1.101v-0.773h1.0606c0.5 0 0.9257 0.059 1.2773 0.176 0.3555 0.117 0.6445 0.279 0.8672 0.486 0.2226 0.203 0.3848 0.436 0.4863 0.697 0.1055 0.262 0.1582 0.539 0.1582 0.832 0 0.399-0.0722 0.754-0.2168 1.067-0.1406 0.308-0.3418 0.57-0.6035 0.785s-0.5684 0.377-0.9199 0.486c-0.3477 0.11-0.7266 0.164-1.1367 0.164-0.3672 0-0.7188-0.05-1.0547-0.152-0.336-0.102-0.6367-0.252-0.9024-0.451-0.2656-0.203-0.4765-0.455-0.6328-0.756-0.1523-0.305-0.2285-0.656-0.2285-1.055h1.4121c0 0.254 0.0586 0.479 0.1758 0.674 0.1211 0.191 0.2891 0.342 0.5039 0.451 0.2188 0.11 0.4688 0.164 0.75 0.164 0.2969 0 0.5527-0.052 0.7676-0.158 0.2148-0.105 0.3789-0.262 0.4922-0.469 0.1172-0.207 0.1758-0.457 0.1758-0.75 0-0.332-0.0645-0.601-0.1934-0.808s-0.3125-0.36-0.5508-0.457c-0.2383-0.102-0.5195-0.153-0.8437-0.153h-0.8438zm10.973-1.177v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.642-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.464 0.3222-0.841 0.5683-1.13 0.2461-0.293 0.5391-0.506 0.8789-0.639 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.212 0.7734 0.392 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.183-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.215-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.297-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.909 0.0391 0.261 0.0977 0.486 0.1758 0.673 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.204 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m36.473 276.28v1.125h-5.7188v-0.967l2.7773-3.029c0.3047-0.344 0.545-0.641 0.7208-0.891 0.1757-0.25 0.2988-0.474 0.3691-0.674 0.0742-0.203 0.1113-0.4 0.1113-0.591 0-0.27-0.0508-0.506-0.1523-0.709-0.0977-0.207-0.2422-0.37-0.4336-0.487-0.1914-0.121-0.4238-0.181-0.6973-0.181-0.3164 0-0.582 0.068-0.7969 0.205-0.2148 0.136-0.3769 0.326-0.4863 0.568-0.1094 0.238-0.164 0.512-0.164 0.82h-1.4122c0-0.496 0.1133-0.949 0.3399-1.359 0.2265-0.414 0.5547-0.742 0.9844-0.984 0.4297-0.246 0.9472-0.37 1.5527-0.37 0.5703 0 1.0547 0.096 1.4531 0.288 0.3985 0.191 0.7012 0.462 0.9082 0.814 0.211 0.352 0.3164 0.768 0.3164 1.248 0 0.266-0.0429 0.529-0.1289 0.791-0.0859 0.262-0.209 0.524-0.3691 0.785-0.1563 0.258-0.3418 0.518-0.5567 0.78-0.2148 0.257-0.4511 0.519-0.7089 0.785l-1.8457 2.033h3.9375zm6.9121-3.856v1.395c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.142-0.246 0.293-0.541 0.508-0.8847 0.645-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.221-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.395c0-0.671 0.0664-1.238 0.1992-1.699 0.1328-0.465 0.3222-0.842 0.5683-1.131 0.2461-0.293 0.5391-0.506 0.8789-0.638 0.3438-0.133 0.7266-0.2 1.1485-0.2 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.406zm-1.4121 1.594v-1.805c0-0.339-0.0196-0.638-0.0586-0.896-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.118-0.2422-0.202-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.083v1.805c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.117 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m34.609 301.14v8.56h-1.4121v-6.885l-2.0918 0.709v-1.166l3.334-1.218h0.1699zm8.7754 3.58v1.394c0 0.668-0.0664 1.239-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.508-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.085-0.543-0.22-0.7734-0.404-0.2305-0.183-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.642-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.699 0.1328-0.465 0.3222-0.842 0.5683-1.131 0.2461-0.293 0.5391-0.506 0.8789-0.639 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.213 0.7734 0.392 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.286 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.872 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.261-0.0977-0.482-0.1758-0.662-0.0742-0.183-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.215-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.297-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.909 0.0391 0.261 0.0977 0.486 0.1758 0.674 0.0781 0.183 0.1719 0.335 0.2813 0.457 0.1132 0.117 0.2421 0.203 0.3867 0.257 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129 0.1719-0.085 0.3164-0.222 0.4336-0.41 0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m43.385 337.02v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.465 0.3222-0.841 0.5683-1.131 0.2461-0.293 0.5391-0.505 0.8789-0.638 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.128 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m63 10v329h1v-329z" fill="#747474" style="stroke-width:.70711"/>
<line x1="52" x2="64" y1="10.5" y2="10.5" stroke="#747474"/>
<line x1="58" x2="64" y1="13.79" y2="13.79" stroke="#747474"/>
<line x1="58" x2="64" y1="17.08" y2="17.08" stroke="#747474"/>
<line x1="58" x2="64" y1="20.37" y2="20.37" stroke="#747474"/>
<line x1="58" x2="64" y1="23.66" y2="23.66" stroke="#747474"/>
<line x1="58" x2="64" y1="26.95" y2="26.95" stroke="#747474"/>
<line x1="58" x2="64" y1="30.24" y2="30.24" stroke="#747474"/>
<line x1="58" x2="64" y1="33.53" y2="33.53" stroke="#747474"/>
<line x1="58" x2="64" y1="36.82" y2="36.82" stroke="#747474"/>
<line x1="58" x2="64" y1="40.11" y2="40.11" stroke="#747474"/>
<line x1="52" x2="64" y1="43.4" y2="43.4" stroke="#747474"/>
<line x1="58" x2="64" y1="46.69" y2="46.69" stroke="#747474"/>
<line x1="58" x2="64" y1="49.98" y2="49.98" stroke="#747474"/>
<line x1="58" x2="64" y1="53.27" y2="53.27" stroke="#747474"/>
<line x1="58" x2="64" y1="56.56" y2="56.56" stroke="#747474"/>
<line x1="58" x2="64" y1="59.85" y2="59.85" stroke="#747474"/>
<line x1="58" x2="64" y1="63.14" y2="63.14" stroke="#747474"/>
<line x1="58" x2="64" y1="66.43" y2="66.43" stroke="#747474"/>
<line x1="58" x2="64" y1="69.72" y2="69.72" stroke="#747474"/>
<line x1="58" x2="64" y1="73.01" y2="73.01" stroke="#747474"/>
<line x1="52" x2="64" y1="76.3" y2="76.3" stroke="#747474"/>
<line x1="58" x2="64" y1="79.59" y2="79.59" stroke="#747474"/>
<line x1="58" x2="64" y1="82.88" y2="82.88" stroke="#747474"/>
<line x1="58" x2="64" y1="86.17" y2="86.17" stroke="#747474"/>
<line x1="58" x2="64" y1="89.46" y2="89.46" stroke="#747474"/>
<line x1="58" x2="64" y1="92.75" y2="92.75" stroke="#747474"/>
<line x1="58" x2="64" y1="96.04" y2="96.04" stroke="#747474"/>
<line x1="58" x2="64" y1="99.33" y2="99.33" stroke="#747474"/>
<line x1="58" x2="64" y1="102.62" y2="102.62" stroke="#747474"/>
<line x1="58" x2="64" y1="105.91" y2="105.91" stroke="#747474"/>
<line x1="52" x2="64" y1="109.2" y2="109.2" stroke="#747474"/>
<line x1="58" x2="64" y1="112.49" y2="112.49" stroke="#747474"/>
<line x1="58" x2="64" y1="115.78" y2="115.78" stroke="#747474"/>
<line x1="58" x2="64" y1="119.07" y2="119.07" stroke="#747474"/>
<line x1="58" x2="64" y1="122.36" y2="122.36" stroke="#747474"/>
<line x1="58" x2="64" y1="125.65" y2="125.65" stroke="#747474"/>
<line x1="58" x2="64" y1="128.94" y2="128.94" stroke="#747474"/>
<line x1="58" x2="64" y1="132.23" y2="132.23" stroke="#747474"/>
<line x1="58" x2="64" y1="135.52" y2="135.52" stroke="#747474"/>
<line x1="58" x2="64" y1="138.81" y2="138.81" stroke="#747474"/>
<line x1="52" x2="64" y1="142.1" y2="142.1" stroke="#747474"/>
<line x1="58" x2="64" y1="145.39" y2="145.39" stroke="#747474"/>
<line x1="58" x2="64" y1="148.68" y2="148.68" stroke="#747474"/>
<line x1="58" x2="64" y1="151.97" y2="151.97" stroke="#747474"/>
<line x1="58" x2="64" y1="155.26" y2="155.26" stroke="#747474"/>
<line x1="58" x2="64" y1="158.55" y2="158.55" stroke="#747474"/>
<line x1="58" x2="64" y1="161.84" y2="161.84" stroke="#747474"/>
<line x1="58" x2="64" y1="165.13" y2="165.13" stroke="#747474"/>
<line x1="58" x2="64" y1="168.42" y2="168.42" stroke="#747474"/>
<line x1="58" x2="64" y1="171.71" y2="171.71" stroke="#747474"/>
<line x1="52" x2="64" y1="175" y2="175" stroke="#747474"/>
<line x1="58" x2="64" y1="178.29" y2="178.29" stroke="#747474"/>
<line x1="58" x2="64" y1="181.58" y2="181.58" stroke="#747474"/>
<line x1="58" x2="64" y1="184.87" y2="184.87" stroke="#747474"/>
<line x1="58" x2="64" y1="188.16" y2="188.16" stroke="#747474"/>
<line x1="58" x2="64" y1="191.45" y2="191.45" stroke="#747474"/>
<line x1="58" x2="64" y1="194.74" y2="194.74" stroke="#747474"/>
<line x1="58" x2="64" y1="198.03" y2="198.03" stroke="#747474"/>
<line x1="58" x2="64" y1="201.32" y2="201.32" stroke="#747474"/>
<line x1="58" x2="64" y1="204.61" y2="204.61" stroke="#747474"/>
<line x1="52" x2="64" y1="207.9" y2="207.9" stroke="#747474"/>
<line x1="58" x2="64" y1="211.19" y2="211.19" stroke="#747474"/>
<line x1="58" x2="64" y1="214.48" y2="214.48" stroke="#747474"/>
<line x1="58" x2="64" y1="217.77" y2="217.77" stroke="#747474"/>
<line x1="58" x2="64" y1="221.06" y2="221.06" stroke="#747474"/>
<line x1="58" x2="64" y1="224.35" y2="224.35" stroke="#747474"/>
<line x1="58" x2="64" y1="227.64" y2="227.64" stroke="#747474"/>
<line x1="58" x2="64" y1="230.93" y2="230.93" stroke="#747474"/>
<line x1="58" x2="64" y1="234.22" y2="234.22" stroke="#747474"/>
<line x1="58" x2="64" y1="237.51" y2="237.51" stroke="#747474"/>
<line x1="52" x2="64" y1="240.8" y2="240.8" stroke="#747474"/>
<line x1="58" x2="64" y1="244.09" y2="244.09" stroke="#747474"/>
<line x1="58" x2="64" y1="247.38" y2="247.38" stroke="#747474"/>
<line x1="58" x2="64" y1="250.67" y2="250.67" stroke="#747474"/>
<line x1="58" x2="64" y1="253.96" y2="253.96" stroke="#747474"/>
<line x1="58" x2="64" y1="257.25" y2="257.25" stroke="#747474"/>
<line x1="58" x2="64" y1="260.54" y2="260.54" stroke="#747474"/>
<line x1="58" x2="64" y1="263.83" y2="263.83" stroke="#747474"/>
<line x1="58" x2="64" y1="267.12" y2="267.12" stroke="#747474"/>
<line x1="58" x2="64" y1="270.41" y2="270.41" stroke="#747474"/>
<line x1="52" x2="64" y1="273.7" y2="273.7" stroke="#747474"/>
<line x1="58" x2="64" y1="276.99" y2="276.99" stroke="#747474"/>
<line x1="58" x2="64" y1="280.28" y2="280.28" stroke="#747474"/>
<line x1="58" x2="64" y1="283.57" y2="283.57" stroke="#747474"/>
<line x1="58" x2="64" y1="286.86" y2="286.86" stroke="#747474"/>
<line x1="58" x2="64" y1="290.15" y2="290.15" stroke="#747474"/>
<line x1="58" x2="64" y1="293.44" y2="293.44" stroke="#747474"/>
<line x1="58" x2="64" y1="296.73" y2="296.73" stroke="#747474"/>
<line x1="58" x2="64" y1="300.02" y2="300.02" stroke="#747474"/>
<line x1="58" x2="64" y1="303.31" y2="303.31" stroke="#747474"/>
<line x1="52" x2="64" y1="306.6" y2="306.6" stroke="#747474"/>
<line x1="58" x2="64" y1="309.89" y2="309.89" stroke="#747474"/>
<line x1="58" x2="64" y1="313.18" y2="313.18" stroke="#747474"/>
<line x1="58" x2="64" y1="316.47" y2="316.47" stroke="#747474"/>
<line x1="58" x2="64" y1="319.76" y2="319.76" stroke="#747474"/>
<line x1="58" x2="64" y1="323.05" y2="323.05" stroke="#747474"/>
<line x1="58" x2="64" y1="326.34" y2="326.34" stroke="#747474"/>
<line x1="58" x2="64" y1="329.63" y2="329.63" stroke="#747474"/>
<line x1="58" x2="64" y1="332.92" y2="332.92" stroke="#747474"/>
<line x1="58" x2="64" y1="336.21" y2="336.21" stroke="#747474"/>
<line x1="52" x2="64" y1="339.5" y2="339.5" stroke="#747474"/>
</g><g tb:tag="progressBar">
<rect x="68" y="10.5" width="6" height="331" ry="1.12" stroke="#cecece" tb:tag="progressBorder"/>
<rect transform="scale(1,-1)" x="68" y="-342" width="6" height="125" ry="1.12" fill="#4d94e1" stroke="#4d94e1" tb:tag="progressFill"/>
<circle cx="71" cy="347" r="7" fill="#4d94e1" tb:tag="progressCircle"/>
</g><g transform="translate(0,329)" fill="#1c943e" style="display: none;" tb:tag="progress-indicator">
<path d="m63.022 10.562 10.5 6.062v-12.124z" style=""/>
</g><g filter="url(#filter0_ii_2475_365165)" tb:tag="value-box">
<rect x="21.5" y="362" width="56" height="30" rx="4" fill="#f3f3f3" fill-opacity=".75" tb:tag="value-box-background"/>
<rect x="22.5" y="363" width="54" height="28" rx="3" stroke="#fff" stroke-width="2"/>
<text x="50.492188" y="378.0625" fill="#727171" font-family="Roboto" font-size="14px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan dominant-baseline="middle">37%</tspan></text>
</g><path d="m37.56 0s-25.56 0-25.56 67v328.36c0 2.6512 0.68053 4.6404 1.52 4.6404h72.96c0.83942 0 1.52-1.9892 1.52-4.6404v-328.36c0-67-25.107-67-25.107-67h-12.893zm25.487 81.2c-0.48969 0-0.88669 1.2536-0.88669 2.8v300.4c0 1.5464 0.39697 2.8 0.88669 2.8h5.5733c0.48969 0 0.88669-1.2536 0.88669-2.8v-300.4c0-1.5464-0.39697-2.8-0.88669-2.8z" fill="#000" fill-opacity="0" style="stroke-width:.17436" tb:tag="clickArea"/><defs>
<filter id="filter0_ii_2475_365165" x="19.5" y="360" width="60" height="34" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="-2" dy="2"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0.5 0"/>
<feBlend in2="shape" result="effect1_innerShadow_2475_365165"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="2" dy="-2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_2475_365165" result="effect2_innerShadow_2475_365165"/>
</filter>
<linearGradient id="paint0_linear_2475_365165" x1="88" x2="12" y1="200" y2="200.04" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".02629"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".11617"/>
<stop stop-color="#fff" stop-opacity=".3" offset=".49829"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".88762"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".96591"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 58 KiB

267
application/src/main/data/json/system/scada_symbols/platform-hp.svg

@ -0,0 +1,267 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1200" height="600" fill="none" version="1.1" viewBox="0 0 1200 600"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Platform",
"description": "Platform with various states.",
"searchTags": [
"platform",
"drilling"
],
"widgetSizeX": 6,
"widgetSizeY": 3,
"tags": [
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "warningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<path d="m1199 201.7v397.3h-1198v-397.3c0.62677 0.346 1.4439 0.79199 2.4378 1.328 2.5404 1.371 6.2364 3.328 10.864 5.676 9.2543 4.696 22.238 10.957 37.156 17.218 29.8 12.508 67.456 25.078 98.542 25.078s68.743-12.57 98.542-25.078c14.918-6.261 27.902-12.522 37.156-17.218 4.628-2.348 8.324-4.305 10.864-5.676 1.27-0.68499 2.252-1.224 2.916-1.592 0.204-0.113 0.379-0.209 0.522-0.28999 0.143 0.081 0.318 0.17699 0.522 0.28999 0.664 0.368 1.646 0.907 2.916 1.592 2.54 1.371 6.236 3.328 10.864 5.676 9.254 4.696 22.238 10.957 37.156 17.218 29.799 12.508 67.456 25.078 98.542 25.078s68.743-12.57 98.542-25.078c14.918-6.261 27.902-12.522 37.156-17.218 4.628-2.348 8.324-4.305 10.864-5.676 1.27-0.68499 2.252-1.224 2.916-1.592 0.204-0.113 0.379-0.209 0.522-0.28999 0.143 0.081 0.318 0.17699 0.522 0.28999 0.664 0.368 1.646 0.907 2.916 1.592 2.54 1.371 6.236 3.328 10.864 5.676 9.254 4.696 22.238 10.957 37.156 17.218 29.799 12.508 67.456 25.078 98.542 25.078s68.743-12.57 98.542-25.078c14.918-6.261 27.902-12.522 37.156-17.218 4.628-2.348 8.324-4.305 10.864-5.676 1.27-0.68499 2.252-1.224 2.916-1.592 0.204-0.113 0.379-0.209 0.522-0.28999 0.143 0.081 0.318 0.17699 0.522 0.28999 0.664 0.368 1.646 0.907 2.916 1.592 2.54 1.371 6.236 3.328 10.864 5.676 9.254 4.696 22.238 10.957 37.156 17.218 29.799 12.508 67.452 25.078 98.542 25.078s68.74-12.57 98.54-25.078c14.92-6.261 27.9-12.522 37.16-17.218 4.63-2.348 8.32-4.305 10.86-5.676 1-0.536 1.81-0.98199 2.44-1.328z" stroke="#1a1a1a" stroke-width="2"/><path d="m700 300h500" stroke="#1a1a1a" stroke-width="6"/><path d="m600 0v200" stroke="#1a1a1a" stroke-width="6"/><path d="m600 400v200" stroke="#1a1a1a" stroke-width="6"/><path d="m700 300h-85c-8.284 0-15 6.716-15 15v85" stroke="#1a1a1a" stroke-width="6"/><path d="m700 300h-85c-8.284 0-15-6.716-15-15v-85" stroke="#1a1a1a" stroke-width="6"/><path d="m600 200v200" stroke="#1a1a1a" stroke-width="6"/><path d="m600 313v-26s0 13 14 13c-14 0-14 13-14 13z" fill="#1a1a1a" stroke="#1a1a1a" stroke-width="2"/><rect x="1074" y="99" width="48" height="462" stroke="#1a1a1a" stroke-width="2"/><rect x="1037" y="561" width="122" height="38" stroke="#1a1a1a" stroke-width="2"/><rect x="78" y="99" width="48" height="462" stroke="#1a1a1a" stroke-width="2"/><rect x="41" y="561" width="122" height="38" stroke="#1a1a1a" stroke-width="2"/><rect x="1" y="1" width="1198" height="98" stroke="#1a1a1a" stroke-width="2"/><path d="m599.68 99.026-598.68-98.026" stroke="#1a1a1a" stroke-width="2.0003"/><path d="m599.68 99.026-598.68-98.027" stroke="#1a1a1a" stroke-width="1.9995"/><path d="m600 99.026 598.68-98.026" stroke="#1a1a1a" stroke-width="2.0003"/><path d="m599.68 0.99987-598.68 98.027" stroke="#1a1a1a" stroke-width="1.9995"/><path d="m600 1.0007 599.68 98.025" stroke="#1a1a1a" stroke-width="2.0011"/><path d="m403.58 0s-403.58 0-403.58 100.5v492.54c0 3.9768 10.745 6.9606 24 6.9606h1152c13.254 0 24-2.9838 24-6.9606v-492.54c0-100.5-396.42-100.5-396.42-100.5h-203.57zm402.42 121.8c-7.7322 0-14 1.8804-14 4.2v450.6c0 2.3196 6.2682 4.2 14 4.2h87.996c7.7322 0 14-1.8804 14-4.2v-450.6c0-2.3196-6.2682-4.2-14-4.2z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g transform="translate(0,516)" fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g transform="translate(0 516)" fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

343
application/src/main/data/json/system/scada_symbols/preventer-hp.svg

@ -0,0 +1,343 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Preventer",
"description": "Preventer with various states.",
"searchTags": [
"preventer",
"drilling"
],
"widgetSizeX": 2,
"widgetSizeY": 1,
"tags": [
{
"tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "running",
"name": "{i18n:scada.symbol.running}",
"hint": "{i18n:scada.symbol.running-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.running}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "running"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "runningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "{i18n:scada.symbol.running}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "stoppedColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#666666",
"required": null,
"subLabel": "{i18n:scada.symbol.stopped}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<path d="m137 15h1v-14h124v14h32.947l46.053 4.8993v21.201l-46.053 4.8993h-9.947v24h45.907l68.093 12.829v32.342l-68.093 12.829h-45.907v25h8.938l47.062 5.883v20.234l-47.062 5.883h-31.938v14h-124v-14h-31.938l-47.062-5.883v-20.234l47.062-5.883h8.938v-25h-45.907l-68.093-12.829v-32.342l68.093-12.829h45.907v-24h-9.947l-46.053-4.8993v-21.201l46.053-4.8993z" tb:tag="background" fill="#fff" stroke="#1a1a1a" stroke-width="2"/><path d="m134.53 0s-134.53 0-134.53 33.5v164.18c0 1.3256 3.5818 2.3202 8 2.3202h384c4.418 0 8-0.9946 8-2.3202v-164.18c0-33.5-132.14-33.5-132.14-33.5h-67.858zm134.14 40.6c-2.5774 0-4.6666 0.6268-4.6666 1.4v150.2c0 0.7732 2.0894 1.4 4.6666 1.4h29.332c2.5774 0 4.6666-0.6268 4.6666-1.4v-150.2c0-0.7732-2.0894-1.4-4.6666-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

346
application/src/main/data/json/system/scada_symbols/rotor-hp.svg

@ -0,0 +1,346 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
"title": "HP Rotor",
"description": "Rotor with various states.",
"searchTags": [
"rotor",
"drilling"
],
"widgetSizeX": 2,
"widgetSizeY": 1,
"tags": [
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "critical",
"stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'criticalClick');"
}
}
},
{
"tag": "rotor-background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "warning",
"stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'warningClick');"
}
}
}
],
"behavior": [
{
"id": "running",
"name": "{i18n:scada.symbol.running}",
"hint": "{i18n:scada.symbol.running-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.running}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "running"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": null,
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warningClick",
"name": "{i18n:scada.symbol.warning-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.warning-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalClick",
"name": "{i18n:scada.symbol.critical-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": "{i18n:scada.symbol.critical-state}",
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.warning-critical-state-animation}",
"hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "runningColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": "{i18n:scada.symbol.running}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "stoppedColor",
"name": "{i18n:scada.symbol.colors}",
"type": "color",
"default": "#666666",
"required": null,
"subLabel": "{i18n:scada.symbol.stopped}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata>
<path d="m163 1h74v76h-74v-76z" fill="#fff" stroke="#1A1A1A" stroke-width="2"/><g fill="#fff" stroke="#1A1A1A" stroke-width="2" tb:tag="rotor-background">
<path d="m51 77h298v88h-298v-88z"/>
<path d="m1 165h398v34h-398l-5e-5 -34z"/>
</g><path d="m134.53 0s-134.53 0-134.53 33.5v164.18c0 1.3256 3.5818 2.3202 8 2.3202h384c4.418 0 8-0.9946 8-2.3202v-164.18c0-33.5-132.14-33.5-132.14-33.5h-67.858zm134.14 40.6c-2.5774 0-4.6666 0.6268-4.6666 1.4v150.2c0 0.7732 2.0894 1.4 4.6666 1.4h29.332c2.5774 0 4.6666-0.6268 4.6666-1.4v-150.2c0-0.7732-2.0894-1.4-4.6666-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><g transform="translate(0,116)" fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g><g transform="translate(0,116)" fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

717
application/src/main/data/json/system/scada_symbols/small-left-meter.svg

@ -0,0 +1,717 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="200" fill="none" version="1.1" viewBox="0 0 100 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Small left meter",
"description": "Small left meter displays the current value with a moving pointer on the scale.",
"searchTags": [
"scale",
"level",
"progress",
"thermometer"
],
"widgetSizeX": 1,
"widgetSizeY": 2,
"tags": [
{
"tag": "background",
"stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "progress-indicator",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = initial - (normalizedValue * initial);\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\n\nif (ctx.properties.progressArrow && !ctx.properties.progressBar) {\n element.show();\n var initial = ctx.properties.valueBox ? 135 : 168;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n translateY: initial\n });\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressArrowColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill});\n \n var offset = calculateOffset(value, minValue, maxValue, initial);\n\n var elementOffset = element.remember('offset');\n if (offset !== elementOffset) {\n element.remember('offset', offset);\n ctx.api.cssAnimate(element, 500).transform({\n translateY: offset\n });\n }\n} else {\n element.hide();\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "progressBar",
"stateRenderFunction": "if (ctx.properties.progressBar) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "progressBorder",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.attr({'height': 137})\n}",
"actions": null
},
{
"tag": "progressCircle",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.attr({cy:152});\n}",
"actions": null
},
{
"tag": "progressFill",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.attr({y:-147});\n}\n\nfunction calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = normalizedValue * initial;\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\nif (ctx.properties.progressBar) {\n var initial = ctx.properties.valueBox ? 135: 168;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 2});\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressBarColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill, stroke: fill});\n ctx.tags.progressCircle[0].fill(fill);\n \n var height = calculateOffset(value, minValue, maxValue, initial);\n\n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height+2});\n }\n} else {\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "var scaleSet = element.remember('scaleSet');\nif (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n \n var start = 11;\n var end = ctx.properties.valueBox ? 134 : 167;\n var majorIntervalLength = end / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n element.add(ctx.svg.line(50, end+11, 50, 11).stroke({ width: 1 }).attr({class: 'majorTick'}));\n for (var i = 0; i < majorIntervals+1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(38, y, 50, y).stroke({ width: 1 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (maxValue - ((maxValue - (minValue)) / (majorIntervals) * i)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 32, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n}\n\nvar majorFont = ctx.properties.majorFont;\nvar majorColor = ctx.properties.majorColor;\nvar minorColor = ctx.properties.minorColor;\nif (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n} else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n}\n\nvar majorTicks = element.find('line.majorTick');\nmajorTicks.forEach(t => t.attr({stroke: majorColor}));\n\nvar majorTicksText = element.find('text.majorTickText');\nctx.api.font(majorTicksText, majorFont, majorColor);\n\nvar minorTicks = element.find('line.minorTick');\nminorTicks.forEach(t => t.attr({stroke: minorColor}));\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(44, minorY, 50, minorY).stroke({ width: 1 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
"tag": "value-box",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "value-box-background",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var colorProcessor = ctx.properties.valueBoxColor;\n colorProcessor.update(ctx.values.value);\n element.attr({fill: colorProcessor.color});\n}",
"actions": null
},
{
"tag": "value-text",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var valueTextFont = ctx.properties.valueTextFont;\n var valueTextColor = ctx.properties.valueTextColor;\n var currentVolume = ctx.values.value;\n var valueText = ctx.api.formatValue(currentVolume, 0, ctx.properties.valueUnits, false);\n var colorProcessor = ctx.properties.valueTextColor;\n colorProcessor.update(ctx.values.value);\n ctx.api.font(element, valueTextFont, colorProcessor.color);\n ctx.api.text(element, valueText);\n}",
"actions": null
}
],
"behavior": [
{
"id": "value",
"name": "{i18n:scada.symbol.value}",
"hint": null,
"group": null,
"type": "value",
"valueType": "DOUBLE",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": {
"action": "GET_TIME_SERIES",
"defaultValue": null,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "waterLevel"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning-state}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical-state}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.critical-state-animation}",
"hint": "{i18n:scada.symbol.critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "minValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 0,
"required": true,
"subLabel": "{i18n:scada.symbol.min-value}",
"divider": true,
"min": -1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "maxValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 100,
"required": true,
"subLabel": "{i18n:scada.symbol.max-value}",
"max": 1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "backgroundColor",
"name": "{i18n:scada.symbol.background-color}",
"type": "color",
"default": "#FFFFFF",
"disabled": false,
"visible": true
},
{
"id": "progressBar",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "switch",
"default": true,
"disabled": false,
"visible": true
},
{
"id": "progressBarColor",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#4D94E1",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressBar",
"disabled": false,
"visible": true
},
{
"id": "progressArrow",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "switch",
"default": false,
"disabled": false,
"visible": true
},
{
"id": "progressArrowColor",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#1C943E",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressArrow",
"disabled": true,
"visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": false,
"disabled": false,
"visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#F3F3F3",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "%",
"subLabel": "{i18n:scada.symbol.units}",
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "valueTextFont",
"name": "{i18n:scada.symbol.value-text}",
"type": "font",
"default": {
"size": 14,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#0000008A",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": false,
"visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 5,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
"min": 1,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "majorFont",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "font",
"default": {
"size": 12,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disabled": false,
"visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 10,
"subLabel": "{i18n:scada.symbol.intervals}",
"min": 1,
"disabled": false,
"visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
}
]
}]]></tb:metadata>
<path d="m4 200c-2.2091 0-4-1.791-4-4v-192c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v192c0 2.209-1.7909 4-4 4h-68z" fill="#E5E5E5" tb:tag="background"/><path d="m4 200c-2.2091 0-4-1.791-4-4v-192c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v192c0 2.209-1.7909 4-4 4h-68z" fill="url(#paint0_linear_3742_299967)"/><path d="m4 198.5c-1.3807 0-2.5-1.119-2.5-2.5v-192c0-1.3807 1.1193-2.5 2.5-2.5h68c1.3807 0 2.5 1.1193 2.5 2.5v192c0 1.381-1.1193 2.5-2.5 2.5h-68z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="scale">
<g fill="#919191">
<path d="m14.539 10.088v8.5605h-1.4121v-6.8848l-2.0918 0.709v-1.166l3.3339-1.2187zm8.7754 3.58v1.3946c0 0.6679-0.0665 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.2461 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5468 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.5859 0.7032 0.1641 0.2851 0.2891 0.6308 0.375 1.0371 0.086 0.4023 0.129 0.8711 0.129 1.4062zm-1.4122 1.5938v-1.8047c0-0.3398-0.0195-0.6387-0.0585-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1133-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4336 0.3984-0.1171 0.1836-0.207 0.4258-0.2695 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0195 0.6465 0.0586 0.9082s0.0977 0.4863 0.1758 0.6738c0.0781 0.1836 0.1719 0.3359 0.2812 0.457 0.1133 0.1172 0.2422 0.2032 0.3868 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2226 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441 0.0585-0.3047 0.0878-0.6699 0.0878-1.0957zm8.4825-1.5938v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z"/>
<path d="m23.332 49.374c0 0.5313-0.123 0.9785-0.3691 1.3418s-0.582 0.6387-1.0078 0.8262c-0.4219 0.1836-0.8985 0.2754-1.4297 0.2754-0.5313 0-1.0098-0.0918-1.4356-0.2754-0.4257-0.1875-0.7617-0.4629-1.0078-0.8262s-0.3691-0.8105-0.3691-1.3418c0-0.3515 0.0683-0.6699 0.2051-0.955 0.1367-0.2891 0.33-0.5372 0.58-0.7442 0.254-0.2109 0.5508-0.373 0.8907-0.4863 0.3437-0.1133 0.7187-0.1699 1.125-0.1699 0.539 0 1.0215 0.0996 1.4472 0.2988 0.4258 0.1992 0.7598 0.4746 1.002 0.8262 0.2461 0.3515 0.3691 0.7617 0.3691 1.2304zm-1.4179-0.0703c0-0.2851-0.0586-0.5351-0.1758-0.75-0.1172-0.2148-0.2813-0.3808-0.4922-0.498s-0.4551-0.1758-0.7324-0.1758c-0.2813 0-0.5254 0.0586-0.7325 0.1758-0.207 0.1172-0.3691 0.2832-0.4863 0.498-0.1133 0.2149-0.1699 0.4649-0.1699 0.75 0 0.2891 0.0566 0.5391 0.1699 0.75 0.1133 0.207 0.2754 0.3653 0.4863 0.4746 0.211 0.1094 0.459 0.1641 0.7442 0.1641 0.2851 0 0.5312-0.0547 0.7383-0.1641 0.207-0.1093 0.3672-0.2676 0.4804-0.4746 0.1133-0.2109 0.17-0.4609 0.17-0.75zm1.2246-3.8906c0 0.4258-0.1133 0.8047-0.3399 1.1367-0.2226 0.332-0.5312 0.5938-0.9258 0.7852-0.3945 0.1875-0.8437 0.2812-1.3476 0.2812-0.5078 0-0.9609-0.0937-1.3594-0.2812-0.3945-0.1914-0.7051-0.4532-0.9316-0.7852-0.2227-0.332-0.334-0.7109-0.334-1.1367 0-0.5078 0.1113-0.9356 0.334-1.2832 0.2265-0.3516 0.5371-0.6192 0.9316-0.8027 0.3945-0.1836 0.8457-0.2754 1.3535-0.2754s0.959 0.0918 1.3535 0.2754c0.3946 0.1835 0.7032 0.4511 0.9258 0.8027 0.2266 0.3476 0.3399 0.7754 0.3399 1.2832zm-1.4121 0.0469c0-0.2539-0.0508-0.4766-0.1524-0.668-0.0976-0.1953-0.2363-0.3477-0.416-0.457-0.1797-0.1094-0.3926-0.1641-0.6387-0.1641s-0.459 0.0527-0.6386 0.1582c-0.1797 0.1055-0.3184 0.2539-0.4161 0.4453-0.0976 0.1914-0.1464 0.4199-0.1464 0.6856 0 0.2617 0.0488 0.4902 0.1464 0.6855 0.0977 0.1914 0.2364 0.3418 0.4161 0.4512 0.1836 0.1094 0.3984 0.164 0.6445 0.164s0.459-0.0546 0.6387-0.164 0.3183-0.2598 0.416-0.4512c0.0976-0.1953 0.1465-0.4238 0.1465-0.6855zm8.6582 1.2597v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2382 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.336 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z"/>
<path d="m22.02 74.016h0.1758v1.1543h-0.0996c-0.5039 0-0.9395 0.0781-1.3066 0.2343-0.3633 0.1563-0.6621 0.3711-0.8965 0.6446-0.2344 0.2734-0.4102 0.5937-0.5274 0.9609-0.1132 0.3633-0.1699 0.75-0.1699 1.1602v1.3418c0 0.3398 0.0371 0.6406 0.1113 0.9023 0.0743 0.2578 0.1778 0.4746 0.3106 0.6504 0.1367 0.1719 0.293 0.3027 0.4687 0.3926 0.1758 0.0898 0.3653 0.1347 0.5684 0.1347 0.2109 0 0.4023-0.0429 0.5742-0.1289 0.1719-0.0898 0.3184-0.2129 0.4395-0.3691 0.1211-0.1563 0.2129-0.3418 0.2754-0.5567 0.0625-0.2148 0.0937-0.4492 0.0937-0.7031 0-0.2422-0.0312-0.4687-0.0937-0.6797-0.0586-0.2148-0.1465-0.4023-0.2637-0.5625-0.1172-0.164-0.2637-0.291-0.4395-0.3808-0.1718-0.0938-0.3711-0.1406-0.5976-0.1406-0.2813 0-0.5371 0.0664-0.7676 0.1992-0.2266 0.1328-0.4102 0.3066-0.5508 0.5215-0.1367 0.2109-0.2109 0.4355-0.2226 0.6738l-0.5391-0.1758c0.0313-0.3633 0.1113-0.6895 0.2402-0.9785 0.1328-0.2891 0.3067-0.5352 0.5215-0.7383 0.2149-0.2031 0.4629-0.3574 0.7442-0.4629 0.2851-0.1094 0.5976-0.164 0.9375-0.164 0.414 0 0.7734 0.0781 1.0781 0.2343 0.3047 0.1563 0.5566 0.3692 0.7558 0.6387 0.2032 0.2656 0.3536 0.5703 0.4512 0.9141 0.1016 0.3398 0.1524 0.6953 0.1524 1.0664 0 0.4101-0.0625 0.7929-0.1875 1.1484-0.125 0.3516-0.3086 0.6602-0.5508 0.9258-0.2383 0.2656-0.5293 0.4726-0.8731 0.6211-0.3398 0.1484-0.7265 0.2226-1.1601 0.2226-0.4571 0-0.8653-0.0879-1.2246-0.2636-0.3555-0.1758-0.6582-0.418-0.9082-0.7266-0.2461-0.3086-0.4336-0.6641-0.5625-1.0664s-0.1934-0.8281-0.1934-1.2773v-0.586c0-0.6484 0.082-1.2598 0.2461-1.834 0.1641-0.5781 0.416-1.0879 0.7559-1.5293 0.3437-0.4414 0.7832-0.7871 1.3183-1.0371 0.5352-0.2539 1.1738-0.3808 1.916-0.3808zm8.3653 3.6035v1.3945c0 0.668-0.0664 1.2383-0.1993 1.7109-0.1289 0.4688-0.3164 0.8497-0.5625 1.1426-0.246 0.293-0.541 0.5078-0.8847 0.6446-0.3399 0.1367-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.0429-0.9375-0.1289-0.2852-0.0859-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.2929-0.2852-0.6425-0.375-1.0488-0.086-0.4062-0.1289-0.8769-0.1289-1.4121v-1.3945c0-0.6719 0.0664-1.2383 0.1992-1.6992 0.1328-0.4649 0.3222-0.8418 0.5683-1.1309 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.043 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3926 0.2305 0.1797 0.4258 0.4141 0.586 0.7031 0.164 0.2852 0.289 0.6309 0.375 1.0371 0.0859 0.4024 0.1289 0.8711 0.1289 1.4063zm-1.4121 1.5937v-1.8047c0-0.3398-0.0196-0.6386-0.0586-0.8964-0.0391-0.2618-0.0977-0.4825-0.1758-0.6622-0.0742-0.1835-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2011-0.3867-0.2519-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.0429-0.586 0.1289-0.1718 0.082-0.3164 0.2148-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2968-0.0879 0.6582-0.0879 1.0839v1.8047c0 0.3438 0.0196 0.6465 0.0586 0.9082 0.0391 0.2618 0.0977 0.4864 0.1758 0.6739 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2031 0.3867 0.2578 0.1484 0.0547 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.0429 0.5918-0.1289 0.1719-0.0859 0.3164-0.2226 0.4336-0.4101 0.1172-0.1914 0.2051-0.4395 0.2637-0.7442s0.0879-0.6699 0.0879-1.0957z"/>
<path d="m23.631 111.66v1.125h-6.1524l-0.0469-0.85 3.6797-5.765h1.1309l-1.2246 2.097-2.1153 3.393zm-1.0664-5.49v8.531h-1.4122v-8.531zm7.8203 3.55v1.395c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.292-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.395c0-0.671 0.0664-1.238 0.1992-1.699 0.1328-0.465 0.3222-0.842 0.5683-1.131 0.2461-0.293 0.5391-0.505 0.8789-0.638 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.042 0.9375 0.128 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.406zm-1.4121 1.594v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.118-0.2422-0.202-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.117 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z"/>
<path d="m23.473 145.48v1.125h-5.7188v-0.967l2.7773-3.029c0.3047-0.344 0.545-0.641 0.7208-0.891 0.1757-0.25 0.2988-0.474 0.3691-0.673 0.0742-0.204 0.1113-0.401 0.1113-0.592 0-0.27-0.0508-0.506-0.1523-0.709-0.0977-0.207-0.2422-0.369-0.4336-0.487-0.1914-0.121-0.4238-0.181-0.6973-0.181-0.3164 0-0.582 0.068-0.7969 0.205-0.2148 0.137-0.3769 0.326-0.4863 0.568-0.1094 0.239-0.164 0.512-0.164 0.821h-1.4122c0-0.497 0.1133-0.95 0.3399-1.36 0.2265-0.414 0.5547-0.742 0.9844-0.984 0.4297-0.246 0.9472-0.369 1.5527-0.369 0.5703 0 1.0547 0.095 1.4531 0.287 0.3985 0.191 0.7012 0.463 0.9082 0.814 0.211 0.352 0.3164 0.768 0.3164 1.248 0 0.266-0.0429 0.53-0.1289 0.791-0.0859 0.262-0.209 0.524-0.3691 0.785-0.1563 0.258-0.3418 0.518-0.5567 0.78-0.2148 0.258-0.4511 0.519-0.7089 0.785l-1.8457 2.033zm6.9121-3.855v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.642-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.464 0.3222-0.841 0.5683-1.13 0.2461-0.293 0.5391-0.506 0.8789-0.639 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.212 0.7734 0.392 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.183-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.215-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.297-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.909 0.0391 0.261 0.0977 0.486 0.1758 0.673 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.204 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z"/>
<path d="m30.385 173.02v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.465 0.3222-0.841 0.5683-1.131 0.2461-0.293 0.5391-0.505 0.8789-0.638 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.128 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z"/>
</g>
<g transform="matrix(1 0 0 1.0194 9.4118e-7 -3.9105)" stroke="#747474">
<path d="m50.695 14.031v164.03h0.22794v-164.03z" fill="#747474" stroke-width=".77206"/>
<line x1="39" x2="51" y1="14.145" y2="14.145"/>
<line x1="45" x2="51" y1="17.76" y2="17.76"/>
<line x1="45" x2="51" y1="21.02" y2="21.02"/>
<line x1="45" x2="51" y1="24.28" y2="24.28"/>
<line x1="45" x2="51" y1="27.54" y2="27.54"/>
<line x1="45" x2="51" y1="30.8" y2="30.8"/>
<line x1="45" x2="51" y1="34.06" y2="34.06"/>
<line x1="45" x2="51" y1="37.32" y2="37.32"/>
<line x1="45" x2="51" y1="40.58" y2="40.58"/>
<line x1="45" x2="51" y1="43.84" y2="43.84"/>
<line x1="39" x2="51" y1="47.1" y2="47.1"/>
<line x1="45" x2="51" y1="50.36" y2="50.36"/>
<line x1="45" x2="51" y1="53.62" y2="53.62"/>
<line x1="45" x2="51" y1="56.88" y2="56.88"/>
<line x1="45" x2="51" y1="60.14" y2="60.14"/>
<line x1="45" x2="51" y1="63.4" y2="63.4"/>
<line x1="45" x2="51" y1="66.66" y2="66.66"/>
<line x1="45" x2="51" y1="69.92" y2="69.92"/>
<line x1="45" x2="51" y1="73.18" y2="73.18"/>
<line x1="45" x2="51" y1="76.44" y2="76.44"/>
<line x1="39" x2="51" y1="79.7" y2="79.7"/>
<line x1="45" x2="51" y1="82.96" y2="82.96"/>
<line x1="45" x2="51" y1="86.22" y2="86.22"/>
<line x1="45" x2="51" y1="89.48" y2="89.48"/>
<line x1="45" x2="51" y1="92.74" y2="92.74"/>
<line x1="45" x2="51" y1="96" y2="96"/>
<line x1="45" x2="51" y1="99.26" y2="99.26"/>
<line x1="45" x2="51" y1="102.52" y2="102.52"/>
<line x1="45" x2="51" y1="105.78" y2="105.78"/>
<line x1="45" x2="51" y1="109.04" y2="109.04"/>
<line x1="39" x2="51" y1="112.3" y2="112.3"/>
<line x1="45" x2="51" y1="115.56" y2="115.56"/>
<line x1="45" x2="51" y1="118.82" y2="118.82"/>
<line x1="45" x2="51" y1="122.08" y2="122.08"/>
<line x1="45" x2="51" y1="125.34" y2="125.34"/>
<line x1="45" x2="51" y1="128.6" y2="128.6"/>
<line x1="45" x2="51" y1="131.86" y2="131.86"/>
<line x1="45" x2="51" y1="135.12" y2="135.12"/>
<line x1="45" x2="51" y1="138.38" y2="138.38"/>
<line x1="45" x2="51" y1="141.64" y2="141.64"/>
<line x1="39" x2="51" y1="144.9" y2="144.9"/>
<line x1="45" x2="51" y1="148.16" y2="148.16"/>
<line x1="45" x2="51" y1="151.42" y2="151.42"/>
<line x1="45" x2="51" y1="154.68" y2="154.68"/>
<line x1="45" x2="51" y1="157.94" y2="157.94"/>
<line x1="45" x2="51" y1="161.2" y2="161.2"/>
<line x1="45" x2="51" y1="164.46" y2="164.46"/>
<line x1="45" x2="51" y1="167.72" y2="167.72"/>
<line x1="45" x2="51" y1="170.98" y2="170.98"/>
<line x1="45" x2="51" y1="174.24" y2="174.24"/>
<line x1="38.985" x2="50.985" y1="177.96" y2="177.96"/>
</g>
</g><path d="m96 200c2.2091 0 4-1.791 4-4v-192c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v192c0 2.209 1.7909 4 4 4h4z" fill="#93979B"/><path d="m96 200c2.2091 0 4-1.791 4-4v-192c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v192c0 2.209 1.7909 4 4 4h4z" fill="url(#paint1_linear_3742_299967)"/><path d="m96 198.5c1.3807 0 2.5-1.119 2.5-2.5v-192c0-1.3807-1.1193-2.5-2.5-2.5h-4c-1.3807 0-2.5 1.1193-2.5 2.5v192c0 1.381 1.1193 2.5 2.5 2.5h4z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m76 140c0 2.209 1.7908 4 3.9999 4h4c2.2092 0 4.0001-1.791 4.0001-4v-92c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v92z" fill="#647484"/><path d="m76 140c0 2.209 1.7908 4 3.9999 4h4c2.2092 0 4.0001-1.791 4.0001-4v-92c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v92z" fill="url(#paint2_linear_3742_299967)"/><path d="m77.5 140c0 1.38 1.1193 2.5 2.4999 2.5h4c1.3808 0 2.5001-1.119 2.5001-2.5v-92c0-1.3807-1.1193-2.5-2.5-2.5h-4c-1.3807 0-2.5 1.1193-2.5 2.5v92z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs>
<linearGradient id="paint0_linear_3742_299967" x1="76" x2=".00010292" y1="100" y2="100.09" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".02629"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".11617"/>
<stop stop-color="#fff" stop-opacity=".3" offset=".49829"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".88762"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".96591"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<linearGradient id="paint1_linear_3742_299967" x1="89.93" x2="134" y1="-14.344" y2="205.53" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".23574"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".39937"/>
<stop stop-color="#fff" stop-opacity=".3" offset=".49829"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".59721"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<linearGradient id="paint2_linear_3742_299967" x1="83.045" x2="78.639" y1="47.812" y2="139.03" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset="0"/>
<stop stop-color="#020202" stop-opacity=".18" offset=".090959"/>
<stop stop-color="#020202" stop-opacity=".1" offset=".1"/>
<stop stop-color="#020202" stop-opacity=".08" offset=".20513"/>
<stop stop-color="#020202" stop-opacity=".05" offset=".21555"/>
<stop stop-color="#020202" stop-opacity=".02" offset=".36962"/>
<stop stop-color="#fff" stop-opacity=".01" offset=".37768"/>
<stop stop-color="#fff" stop-opacity=".01" offset=".62413"/>
<stop stop-color="#020202" stop-opacity=".02" offset=".6313"/>
<stop stop-color="#020202" stop-opacity=".05" offset=".77601"/>
<stop stop-color="#020202" stop-opacity=".08" offset=".7898"/>
<stop stop-color="#020202" stop-opacity=".1" offset=".9"/>
<stop stop-color="#020202" stop-opacity=".18" offset=".90846"/>
<stop stop-color="#020202" stop-opacity=".2" offset="1"/>
</linearGradient>
<filter id="filter0_ii_3742_299961" x="20" y="162" width="60" height="32" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="-2" dy="2"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0.5 0"/>
<feBlend in2="shape" result="effect1_innerShadow_3742_299961"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="2" dy="-2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_3742_299961" result="effect2_innerShadow_3742_299961"/>
</filter>
</defs><g tb:tag="progressBar">
<rect x="56" y="10.5" width="6" height="169" ry="1.12" stroke="#cecece" tb:tag="progressBorder"/>
<rect transform="scale(1,-1)" x="56" y="-180" width="6" height="62" ry="1.12" fill="#4d94e1" stroke="#4d94e1" tb:tag="progressFill"/>
<circle cx="59" cy="185" r="7" fill="#4d94e1" tb:tag="progressCircle"/>
</g><g transform="translate(0,166)" fill="#1c943e" style="display: none;" tb:tag="progress-indicator">
<path d="m52 10.562 10.5 6.062v-12.124z" style=""/>
</g><g transform="translate(-12)" filter="url(#filter0_ii_3742_299961)" style="display: none;" tb:tag="value-box">
<rect x="22" y="164" width="56" height="28" rx="4" fill="#fffefe" fill-opacity=".75" tb:tag="value-box-background" style=""/>
<rect x="23" y="165" width="54" height="26" rx="3" stroke="#fff" stroke-width="2" style=""/>
<text x="49.853027" y="179.5625" fill="#727171" font-family="Roboto" font-size="14px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve" style=""><tspan dominant-baseline="middle">37%</tspan></text>
</g><path d="m25.56-0.12402s-25.56 0-25.56 33.5v164.18c0 1.3256 0.68053 2.3202 1.52 2.3202h72.96c0.83942 0 1.52-0.9946 1.52-2.3202v-164.18c0-33.5-25.107-33.5-25.107-33.5h-12.893zm25.487 40.6c-0.48969 0-0.88669 0.6268-0.88669 1.4v150.2c0 0.7732 0.39697 1.4 0.88669 1.4h5.5733c0.48969 0 0.88669-0.6268 0.88669-1.4v-150.2c0-0.7732-0.39697-1.4-0.88669-1.4z" fill-opacity="0" tb:tag="clickArea"/>
</svg>

After

Width:  |  Height:  |  Size: 45 KiB

688
application/src/main/data/json/system/scada_symbols/small-meter.svg

@ -0,0 +1,688 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="200" fill="none" version="1.1" viewBox="0 0 100 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Small meter",
"description": "Small meter displays the current value with a moving pointer on the scale.",
"searchTags": [
"scale",
"level",
"progress",
"thermometer"
],
"widgetSizeX": 1,
"widgetSizeY": 2,
"tags": [
{
"tag": "background",
"stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "progress-indicator",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = initial - (normalizedValue * initial);\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\n\nif (ctx.properties.progressArrow && !ctx.properties.progressBar) {\n element.show();\n var initial = ctx.properties.valueBox ? 132 : 167;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n translateY: initial\n });\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressArrowColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill});\n \n var offset = calculateOffset(value, minValue, maxValue, initial);\n\n var elementOffset = element.remember('offset');\n if (offset !== elementOffset) {\n element.remember('offset', offset);\n ctx.api.cssAnimate(element, 500).transform({\n translateY: offset\n });\n }\n} else {\n element.hide();\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "progressBar",
"stateRenderFunction": "if (ctx.properties.progressBar) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "progressBorder",
"stateRenderFunction": "if (!ctx.properties.valueBox) {\n element.attr({'height': 168})\n}",
"actions": null
},
{
"tag": "progressCircle",
"stateRenderFunction": "if (!ctx.properties.valueBox) {\n element.attr({cy:185});\n}",
"actions": null
},
{
"tag": "progressFill",
"stateRenderFunction": "if (!ctx.properties.valueBox) {\n element.attr({y:-180});\n}\n\nfunction calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = normalizedValue * initial;\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\nif (ctx.properties.progressBar) {\n var initial = ctx.properties.valueBox ? 132: 168;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 2});\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressBarColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill, stroke: fill});\n ctx.tags.progressCircle[0].fill(fill);\n \n var height = calculateOffset(value, minValue, maxValue, initial);\n\n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height+2});\n }\n} else {\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "var scaleSet = element.remember('scaleSet');\nif (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n \n var start = 11;\n var end = ctx.properties.valueBox ? 132 : 167;\n var majorIntervalLength = end / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n element.add(ctx.svg.line(63, end+11, 63, 11).stroke({ width: 1 }).attr({class: 'majorTick'}));\n for (var i = 0; i < majorIntervals+1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(51, y, 63, y).stroke({ width: 1 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (maxValue - ((maxValue - (minValue)) / (majorIntervals) * i)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 45, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n}\n\nvar majorFont = ctx.properties.majorFont;\nvar majorColor = ctx.properties.majorColor;\nvar minorColor = ctx.properties.minorColor;\nif (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n} else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n}\n\nvar majorTicks = element.find('line.majorTick');\nmajorTicks.forEach(t => t.attr({stroke: majorColor}));\n\nvar majorTicksText = element.find('text.majorTickText');\nctx.api.font(majorTicksText, majorFont, majorColor);\n\nvar minorTicks = element.find('line.minorTick');\nminorTicks.forEach(t => t.attr({stroke: minorColor}));\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(57, minorY, 63, minorY).stroke({ width: 1 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
"tag": "value-box",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "value-box-background",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var colorProcessor = ctx.properties.valueBoxColor;\n colorProcessor.update(ctx.values.value);\n element.attr({fill: colorProcessor.color});\n}",
"actions": null
},
{
"tag": "value-text",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var valueTextFont = ctx.properties.valueTextFont;\n var valueTextColor = ctx.properties.valueTextColor;\n var currentVolume = ctx.values.value;\n var valueText = ctx.api.formatValue(currentVolume, 0, ctx.properties.valueUnits, false);\n var colorProcessor = ctx.properties.valueTextColor;\n colorProcessor.update(ctx.values.value);\n ctx.api.font(element, valueTextFont, colorProcessor.color);\n ctx.api.text(element, valueText);\n}",
"actions": null
}
],
"behavior": [
{
"id": "value",
"name": "{i18n:scada.symbol.value}",
"hint": null,
"group": null,
"type": "value",
"valueType": "DOUBLE",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": {
"action": "GET_TIME_SERIES",
"defaultValue": null,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "waterLevel"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning-state}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical-state}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.critical-state-animation}",
"hint": "{i18n:scada.symbol.critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "minValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 0,
"required": true,
"subLabel": "{i18n:scada.symbol.min-value}",
"divider": true,
"min": -1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "maxValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 100,
"required": true,
"subLabel": "{i18n:scada.symbol.max-value}",
"max": 1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "backgroundColor",
"name": "{i18n:scada.symbol.background-color}",
"type": "color",
"default": "#FFFFFF",
"disabled": false,
"visible": true
},
{
"id": "progressBar",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "switch",
"default": true,
"disabled": false,
"visible": true
},
{
"id": "progressBarColor",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#4D94E1",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressBar",
"disabled": false,
"visible": true
},
{
"id": "progressArrow",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "switch",
"default": false,
"disabled": false,
"visible": true
},
{
"id": "progressArrowColor",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#1C943E",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressArrow",
"disabled": true,
"visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": true,
"disabled": false,
"visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#F3F3F3",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "%",
"subLabel": "{i18n:scada.symbol.units}",
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "valueTextFont",
"name": "{i18n:scada.symbol.value-text}",
"type": "font",
"default": {
"size": 14,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#0000008A",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 5,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
"min": 1,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "majorFont",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "font",
"default": {
"size": 12,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disabled": false,
"visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 10,
"subLabel": "{i18n:scada.symbol.intervals}",
"min": 1,
"disabled": false,
"visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
}
]
}]]></tb:metadata>
<path d="m16 200c-2.2091 0-4-1.791-4-4v-192c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v192c0 2.209-1.7909 4-4 4h-68z" fill="#E5E5E5" tb:tag="background"/><path d="m16 200c-2.2091 0-4-1.791-4-4v-192c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v192c0 2.209-1.7909 4-4 4h-68z" fill="url(#paint0_linear_3742_299961)"/><path d="m16 198.5c-1.3807 0-2.5-1.119-2.5-2.5v-192c0-1.3807 1.1193-2.5 2.5-2.5h68c1.3807 0 2.5 1.1193 2.5 2.5v192c0 1.381-1.1193 2.5-2.5 2.5h-68z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="scale">
<path d="m28.539 10.088v8.5605h-1.4121v-6.8848l-2.0918 0.709v-1.166l3.3339-1.2187zm8.7754 3.58v1.3946c0 0.6679-0.0665 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.2461 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5468 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.5859 0.7032 0.1641 0.2851 0.2891 0.6308 0.375 1.0371 0.086 0.4023 0.129 0.8711 0.129 1.4062zm-1.4122 1.5938v-1.8047c0-0.3398-0.0195-0.6387-0.0585-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1133-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4336 0.3984-0.1171 0.1836-0.207 0.4258-0.2695 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0195 0.6465 0.0586 0.9082s0.0977 0.4863 0.1758 0.6738c0.0781 0.1836 0.1719 0.3359 0.2812 0.457 0.1133 0.1172 0.2422 0.2032 0.3868 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2226 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441 0.0585-0.3047 0.0878-0.6699 0.0878-1.0957zm8.4825-1.5938v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z" fill="#000" fill-opacity=".38"/>
<path d="m37.332 40.682c0 0.5313-0.123 0.9785-0.3691 1.3418s-0.582 0.6387-1.0078 0.8262c-0.4219 0.1836-0.8985 0.2754-1.4297 0.2754-0.5313 0-1.0098-0.0918-1.4356-0.2754-0.4257-0.1875-0.7617-0.4629-1.0078-0.8262s-0.3691-0.8105-0.3691-1.3418c0-0.3515 0.0683-0.6699 0.2051-0.955 0.1367-0.2891 0.33-0.5372 0.58-0.7442 0.254-0.2109 0.5508-0.373 0.8907-0.4863 0.3437-0.1133 0.7187-0.1699 1.125-0.1699 0.539 0 1.0215 0.0996 1.4472 0.2988 0.4258 0.1992 0.7598 0.4746 1.002 0.8262 0.2461 0.3515 0.3691 0.7617 0.3691 1.2304zm-1.4179-0.0703c0-0.2851-0.0586-0.5351-0.1758-0.75-0.1172-0.2148-0.2813-0.3808-0.4922-0.498s-0.4551-0.1758-0.7324-0.1758c-0.2813 0-0.5254 0.0586-0.7325 0.1758-0.207 0.1172-0.3691 0.2832-0.4863 0.498-0.1133 0.2149-0.1699 0.4649-0.1699 0.75 0 0.2891 0.0566 0.5391 0.1699 0.75 0.1133 0.207 0.2754 0.3653 0.4863 0.4746 0.211 0.1094 0.459 0.1641 0.7442 0.1641 0.2851 0 0.5312-0.0547 0.7383-0.1641 0.207-0.1093 0.3672-0.2676 0.4804-0.4746 0.1133-0.2109 0.17-0.4609 0.17-0.75zm1.2246-3.8906c0 0.4258-0.1133 0.8047-0.3399 1.1367-0.2226 0.332-0.5312 0.5938-0.9258 0.7852-0.3945 0.1875-0.8437 0.2812-1.3476 0.2812-0.5078 0-0.9609-0.0937-1.3594-0.2812-0.3945-0.1914-0.7051-0.4532-0.9316-0.7852-0.2227-0.332-0.334-0.7109-0.334-1.1367 0-0.5078 0.1113-0.9356 0.334-1.2832 0.2265-0.3516 0.5371-0.6192 0.9316-0.8027 0.3945-0.1836 0.8457-0.2754 1.3535-0.2754s0.959 0.0918 1.3535 0.2754c0.3946 0.1835 0.7032 0.4511 0.9258 0.8027 0.2266 0.3476 0.3399 0.7754 0.3399 1.2832zm-1.4121 0.0469c0-0.2539-0.0508-0.4766-0.1524-0.668-0.0976-0.1953-0.2363-0.3477-0.416-0.457-0.1797-0.1094-0.3926-0.1641-0.6387-0.1641s-0.459 0.0527-0.6386 0.1582c-0.1797 0.1055-0.3184 0.2539-0.4161 0.4453-0.0976 0.1914-0.1464 0.4199-0.1464 0.6856 0 0.2617 0.0488 0.4902 0.1464 0.6855 0.0977 0.1914 0.2364 0.3418 0.4161 0.4512 0.1836 0.1094 0.3984 0.164 0.6445 0.164s0.459-0.0546 0.6387-0.164 0.3183-0.2598 0.416-0.4512c0.0976-0.1953 0.1465-0.4238 0.1465-0.6855zm8.6582 1.2597v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2382 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.336 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z" fill="#000" fill-opacity=".38"/>
<path d="m36.02 60.784h0.1758v1.1543h-0.0996c-0.5039 0-0.9395 0.078-1.3066 0.2343-0.3633 0.1563-0.6621 0.3711-0.8965 0.6446-0.2344 0.2734-0.4102 0.5937-0.5274 0.9609-0.1132 0.3633-0.1699 0.75-0.1699 1.1602v1.3418c0 0.3398 0.0371 0.6406 0.1113 0.9023 0.0743 0.2578 0.1778 0.4746 0.3106 0.6504 0.1367 0.1719 0.293 0.3027 0.4687 0.3926 0.1758 0.09 0.3653 0.1347 0.5684 0.1347 0.2109 0 0.4023-0.043 0.5742-0.1289 0.1719-0.09 0.3184-0.2129 0.4395-0.3691 0.1211-0.1563 0.2129-0.3418 0.2754-0.5567 0.0625-0.2148 0.0937-0.4492 0.0937-0.7031 0-0.2422-0.0312-0.4687-0.0937-0.6797-0.0586-0.2148-0.1465-0.4023-0.2637-0.5625-0.1172-0.164-0.2637-0.291-0.4395-0.3808-0.1718-0.094-0.3711-0.1406-0.5976-0.1406-0.2813 0-0.5371 0.066-0.7676 0.1992-0.2266 0.1328-0.4102 0.3066-0.5508 0.5215-0.1367 0.2109-0.2109 0.4355-0.2226 0.6738l-0.5391-0.1758c0.0313-0.3633 0.1113-0.6895 0.2402-0.9785 0.1328-0.2891 0.3067-0.5352 0.5215-0.7383 0.2149-0.2031 0.4629-0.3574 0.7442-0.4629 0.2851-0.1094 0.5976-0.164 0.9375-0.164 0.414 0 0.7734 0.078 1.0781 0.2343s0.5566 0.3692 0.7558 0.6387c0.2032 0.2656 0.3536 0.5703 0.4512 0.9141 0.1016 0.3398 0.1524 0.6953 0.1524 1.0664 0 0.4101-0.0625 0.7929-0.1875 1.1484-0.125 0.3516-0.3086 0.6602-0.5508 0.9258-0.2383 0.2656-0.5293 0.4726-0.8731 0.6211-0.3398 0.1484-0.7265 0.2226-1.1601 0.2226-0.4571 0-0.8653-0.088-1.2246-0.2636-0.3555-0.1758-0.6582-0.418-0.9082-0.7266-0.2461-0.3086-0.4336-0.6641-0.5625-1.0664s-0.1934-0.8281-0.1934-1.2773v-0.586c0-0.6484 0.082-1.2598 0.2461-1.834 0.1641-0.5781 0.416-1.0879 0.7559-1.5293 0.3437-0.4414 0.7832-0.7871 1.3183-1.0371 0.5352-0.2539 1.1738-0.3808 1.916-0.3808zm8.3653 3.6035v1.3945c0 0.668-0.0664 1.2383-0.1993 1.7109-0.1289 0.4688-0.3164 0.8497-0.5625 1.1426-0.246 0.293-0.541 0.5078-0.8847 0.6446-0.3399 0.1367-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.2929-0.2852-0.6425-0.375-1.0488-0.086-0.4062-0.1289-0.8769-0.1289-1.4121v-1.3945c0-0.6719 0.0664-1.2383 0.1992-1.6992 0.1328-0.4649 0.3222-0.8418 0.5683-1.1309 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.043 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3926 0.2305 0.1797 0.4258 0.4141 0.586 0.7031 0.164 0.2852 0.289 0.6309 0.375 1.0371 0.0859 0.4024 0.1289 0.8711 0.1289 1.4063zm-1.4121 1.5937v-1.8047c0-0.3398-0.0196-0.6386-0.0586-0.8964-0.0391-0.2618-0.0977-0.4825-0.1758-0.6622-0.0742-0.1835-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2011-0.3867-0.2519-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2148-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2968-0.0879 0.6582-0.0879 1.0839v1.8047c0 0.3438 0.0196 0.6465 0.0586 0.9082 0.0391 0.2618 0.0977 0.4864 0.1758 0.6739 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2031 0.3867 0.2578 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2226 0.4336-0.4101 0.1172-0.1914 0.2051-0.4395 0.2637-0.7442s0.0879-0.6699 0.0879-1.0957z" fill="#000" fill-opacity=".38"/>
<path d="m37.631 90.687v1.125h-6.1524l-0.0469-0.8496 3.6797-5.7656h1.1309l-1.2246 2.0977-2.1153 3.3925zm-1.0664-5.4902v8.5313h-1.4122v-8.5313zm7.8203 3.5508v1.3945c0 0.668-0.0664 1.2383-0.1993 1.711-0.1289 0.4687-0.3164 0.8496-0.5625 1.1425-0.246 0.293-0.541 0.5078-0.8847 0.6446-0.3399 0.1367-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.0429-0.9375-0.1289-0.2852-0.0859-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.7089-0.1602-0.293-0.2852-0.6426-0.375-1.0489-0.086-0.4062-0.1289-0.8769-0.1289-1.4121v-1.3945c0-0.6719 0.0664-1.2383 0.1992-1.6992 0.1328-0.4649 0.3222-0.8418 0.5683-1.1309 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.043 0.9375 0.1289 0.289 0.0821 0.5469 0.2129 0.7734 0.3926 0.2305 0.1797 0.4258 0.4141 0.586 0.7031 0.164 0.2852 0.289 0.6309 0.375 1.0371 0.0859 0.4024 0.1289 0.8711 0.1289 1.4063zm-1.4121 1.5937v-1.8047c0-0.3398-0.0196-0.6386-0.0586-0.8964-0.0391-0.2618-0.0977-0.4825-0.1758-0.6621-0.0742-0.1836-0.168-0.3321-0.2813-0.4454-0.1132-0.1171-0.2422-0.2011-0.3867-0.2519-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.0429-0.586 0.1289-0.1718 0.082-0.3164 0.2148-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2968-0.0879 0.6582-0.0879 1.0839v1.8047c0 0.3438 0.0196 0.6465 0.0586 0.9082 0.0391 0.2618 0.0977 0.4864 0.1758 0.6739 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2031 0.3867 0.2578 0.1484 0.0547 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.0429 0.5918-0.1289 0.1719-0.0859 0.3164-0.2226 0.4336-0.4101 0.1172-0.1914 0.2051-0.4395 0.2637-0.7442s0.0879-0.6699 0.0879-1.0957z" fill="#000" fill-opacity=".38"/>
<path d="m37.473 116.48v1.125h-5.7188v-0.967l2.7773-3.029c0.3047-0.344 0.545-0.641 0.7208-0.891 0.1757-0.25 0.2988-0.474 0.3691-0.673 0.0742-0.204 0.1113-0.401 0.1113-0.592 0-0.27-0.0508-0.506-0.1523-0.709-0.0977-0.207-0.2422-0.369-0.4336-0.487-0.1914-0.121-0.4238-0.181-0.6973-0.181-0.3164 0-0.582 0.068-0.7969 0.205-0.2148 0.137-0.3769 0.326-0.4863 0.568-0.1094 0.239-0.164 0.512-0.164 0.821h-1.4122c0-0.497 0.1133-0.95 0.3399-1.36 0.2265-0.414 0.5547-0.742 0.9844-0.984 0.4297-0.246 0.9472-0.369 1.5527-0.369 0.5703 0 1.0547 0.095 1.4531 0.287 0.3985 0.191 0.7012 0.463 0.9082 0.814 0.211 0.352 0.3164 0.768 0.3164 1.248 0 0.266-0.0429 0.53-0.1289 0.791-0.0859 0.262-0.209 0.524-0.3691 0.785-0.1563 0.258-0.3418 0.518-0.5567 0.78-0.2148 0.258-0.4511 0.519-0.7089 0.785l-1.8457 2.033zm6.9121-3.855v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.642-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.464 0.3222-0.841 0.5683-1.13 0.2461-0.293 0.5391-0.506 0.8789-0.639 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.212 0.7734 0.392 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.183-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.215-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.297-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.909 0.0391 0.261 0.0977 0.486 0.1758 0.673 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.204 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<path d="m44.385 139.02v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.465 0.3222-0.841 0.5683-1.131 0.2461-0.293 0.5391-0.505 0.8789-0.638 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.128 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z" fill="#000" fill-opacity=".38"/>
<line x1="52" x2="64" y1="10.5" y2="10.5" stroke="#747474"/>
<line x1="58" x2="64" y1="13.136" y2="13.136" stroke="#747474"/>
<line x1="58" x2="64" y1="15.772" y2="15.772" stroke="#747474"/>
<line x1="58" x2="64" y1="18.408" y2="18.408" stroke="#747474"/>
<line x1="58" x2="64" y1="21.044" y2="21.044" stroke="#747474"/>
<line x1="58" x2="64" y1="23.68" y2="23.68" stroke="#747474"/>
<line x1="58" x2="64" y1="26.316" y2="26.316" stroke="#747474"/>
<line x1="58" x2="64" y1="28.952" y2="28.952" stroke="#747474"/>
<line x1="58" x2="64" y1="31.588" y2="31.588" stroke="#747474"/>
<line x1="58" x2="64" y1="34.224" y2="34.224" stroke="#747474"/>
<line x1="58" x2="64" y1="39.496" y2="39.496" stroke="#747474"/>
<line x1="52" x2="64" y1="36.86" y2="36.86" stroke="#747474"/>
<line x1="58" x2="64" y1="42.132" y2="42.132" stroke="#747474"/>
<line x1="58" x2="64" y1="44.768" y2="44.768" stroke="#747474"/>
<line x1="58" x2="64" y1="47.404" y2="47.404" stroke="#747474"/>
<line x1="58" x2="64" y1="50.04" y2="50.04" stroke="#747474"/>
<line x1="58" x2="64" y1="52.676" y2="52.676" stroke="#747474"/>
<line x1="58" x2="64" y1="55.312" y2="55.312" stroke="#747474"/>
<line x1="58" x2="64" y1="57.948" y2="57.948" stroke="#747474"/>
<line x1="58" x2="64" y1="60.584" y2="60.584" stroke="#747474"/>
<line x1="58" x2="64" y1="65.856" y2="65.856" stroke="#747474"/>
<line x1="52" x2="64" y1="63.22" y2="63.22" stroke="#747474"/>
<line x1="58" x2="64" y1="68.492" y2="68.492" stroke="#747474"/>
<line x1="58" x2="64" y1="71.128" y2="71.128" stroke="#747474"/>
<line x1="58" x2="64" y1="73.764" y2="73.764" stroke="#747474"/>
<line x1="58" x2="64" y1="76.4" y2="76.4" stroke="#747474"/>
<line x1="58" x2="64" y1="81.672" y2="81.672" stroke="#747474"/>
<line x1="58" x2="64" y1="79.036" y2="79.036" stroke="#747474"/>
<line x1="58" x2="64" y1="84.308" y2="84.308" stroke="#747474"/>
<line x1="52" x2="64" y1="89.58" y2="89.58" stroke="#747474"/>
<line x1="58" x2="64" y1="86.944" y2="86.944" stroke="#747474"/>
<line x1="58" x2="64" y1="92.216" y2="92.216" stroke="#747474"/>
<line x1="58" x2="64" y1="94.852" y2="94.852" stroke="#747474"/>
<line x1="58" x2="64" y1="97.488" y2="97.488" stroke="#747474"/>
<line x1="58" x2="64" y1="100.12" y2="100.12" stroke="#747474"/>
<line x1="58" x2="64" y1="102.76" y2="102.76" stroke="#747474"/>
<line x1="58" x2="64" y1="105.4" y2="105.4" stroke="#747474"/>
<line x1="58" x2="64" y1="108.03" y2="108.03" stroke="#747474"/>
<line x1="58" x2="64" y1="110.67" y2="110.67" stroke="#747474"/>
<line x1="52" x2="64" y1="115.94" y2="115.94" stroke="#747474"/>
<line x1="58" x2="64" y1="113.3" y2="113.3" stroke="#747474"/>
<line x1="58" x2="64" y1="118.58" y2="118.58" stroke="#747474"/>
<line x1="58" x2="64" y1="121.21" y2="121.21" stroke="#747474"/>
<line x1="58" x2="64" y1="123.85" y2="123.85" stroke="#747474"/>
<line x1="58" x2="64" y1="126.48" y2="126.48" stroke="#747474"/>
<line x1="58" x2="64" y1="129.12" y2="129.12" stroke="#747474"/>
<line x1="58" x2="64" y1="131.76" y2="131.76" stroke="#747474"/>
<line x1="58" x2="64" y1="134.39" y2="134.39" stroke="#747474"/>
<line x1="58" x2="64" y1="137.03" y2="137.03" stroke="#747474"/>
<line x1="52" x2="64" y1="142.5" y2="142.5" stroke="#747474"/>
<line x1="58" x2="64" y1="139.66" y2="139.66" stroke="#747474"/>
<path d="m63 10v133h1v-133z" fill="#747474"/>
</g><g filter="url(#filter0_ii_3742_299961)" tb:tag="value-box">
<rect x="22" y="164" width="56" height="28" rx="4" fill="#FFFEFE" fill-opacity=".75" tb:tag="value-box-background"/>
<rect x="23" y="165" width="54" height="26" rx="3" stroke="#fff" stroke-width="2"/>
<text x="49.853027" y="179.5625" fill="#727171" font-family="Roboto" font-size="14px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan dominant-baseline="middle">37%</tspan></text>
</g><g tb:tag="progressBar">
<rect x="69" y="10.5" width="6" height="134" ry="1.12" stroke="#cecece" tb:tag="progressBorder"/>
<rect transform="scale(1,-1)" x="69" y="-144.5" width="6" height="50" ry="1.12" fill="#4d94e1" stroke="#4d94e1" tb:tag="progressFill"/>
<circle cx="72" cy="150" r="7" fill="#4d94e1" tb:tag="progressCircle"/>
</g><g transform="translate(0,132)" fill="#1c943e" style="display: none;" tb:tag="progress-indicator">
<path d="m64 10.562 10.5 6.062v-12.124z" style=""/>
</g><path d="m37.56 0s-25.56 0-25.56 33.5v164.18c0 1.3256 0.68053 2.3202 1.52 2.3202h72.96c0.83942 0 1.52-0.9946 1.52-2.3202v-164.18c0-33.5-25.107-33.5-25.107-33.5h-12.893zm25.487 40.6c-0.48969 0-0.88669 0.6268-0.88669 1.4v150.2c0 0.7732 0.39697 1.4 0.88669 1.4h5.5733c0.48969 0 0.88669-0.6268 0.88669-1.4v-150.2c0-0.7732-0.39697-1.4-0.88669-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<filter id="filter0_ii_3742_299961" x="20" y="162" width="60" height="32" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="-2" dy="2"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0.5 0"/>
<feBlend in2="shape" result="effect1_innerShadow_3742_299961"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="2" dy="-2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_3742_299961" result="effect2_innerShadow_3742_299961"/>
</filter>
<linearGradient id="paint0_linear_3742_299961" x1="88" x2="12" y1="100" y2="100.09" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".02629"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".11617"/>
<stop stop-color="#fff" stop-opacity=".3" offset=".49829"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".88762"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".96591"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

717
application/src/main/data/json/system/scada_symbols/small-right-center.svg

@ -0,0 +1,717 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="200" fill="none" version="1.1" viewBox="0 0 100 200"><tb:metadata xmlns=""><![CDATA[{
"title": "Small right meter",
"description": "Small right meter displays the current value with a moving pointer on the scale.",
"searchTags": [
"scale",
"level",
"progress",
"thermometer"
],
"widgetSizeX": 1,
"widgetSizeY": 2,
"tags": [
{
"tag": "background",
"stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "progress-indicator",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = initial - (normalizedValue * initial);\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\n\nif (ctx.properties.progressArrow && !ctx.properties.progressBar) {\n element.show();\n var initial = ctx.properties.valueBox ? 135 : 168;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n translateY: initial\n });\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressArrowColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill});\n \n var offset = calculateOffset(value, minValue, maxValue, initial);\n\n var elementOffset = element.remember('offset');\n if (offset !== elementOffset) {\n element.remember('offset', offset);\n ctx.api.cssAnimate(element, 500).transform({\n translateY: offset\n });\n }\n} else {\n element.hide();\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "progressBar",
"stateRenderFunction": "if (ctx.properties.progressBar) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "progressBorder",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.attr({'height': 137})\n}",
"actions": null
},
{
"tag": "progressCircle",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.attr({cy:152});\n}",
"actions": null
},
{
"tag": "progressFill",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.attr({y:-147});\n}\n\nfunction calculateOffset(value, minValue, maxValue, initial) {\n var clampedValue = Math.max(Math.min(value, Math.max(minValue, maxValue)), Math.min(minValue, maxValue));\n var normalizedValue = minValue < maxValue\n ? (clampedValue - minValue) / (maxValue - minValue)\n : (minValue - clampedValue) / (minValue - maxValue);\n var offset = normalizedValue * initial;\n return offset;\n}\n\nvar valueSet = element.remember('valueSet');\nif (ctx.properties.progressBar) {\n var initial = ctx.properties.valueBox ? 135: 168;\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 2});\n }\n \n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n var value = ctx.values.value;\n \n var colorProcessor = ctx.properties.progressBarColor;\n colorProcessor.update(value);\n var fill = colorProcessor.color;\n element.attr({fill: fill, stroke: fill});\n ctx.tags.progressCircle[0].fill(fill);\n \n var height = calculateOffset(value, minValue, maxValue, initial);\n\n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height+2});\n }\n} else {\n if (valueSet) {\n element.remember('valueSet', false);\n }\n}",
"actions": null
},
{
"tag": "scale",
"stateRenderFunction": "var scaleSet = element.remember('scaleSet');\nif (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n var minValue = ctx.properties.minValue;\n var maxValue = ctx.properties.maxValue;\n \n var start = 11;\n var end = ctx.properties.valueBox ? 134 : 167;\n var majorIntervalLength = end / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n element.add(ctx.svg.line(73, end+11, 73, 11).stroke({ width: 1 }).attr({class: 'majorTick'}));\n for (var i = 0; i < majorIntervals+1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(61, y, 73, y).stroke({ width: 1 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (maxValue - ((maxValue - (minValue)) / (majorIntervals) * i)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 55, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n}\n\nvar majorFont = ctx.properties.majorFont;\nvar majorColor = ctx.properties.majorColor;\nvar minorColor = ctx.properties.minorColor;\nif (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n} else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n}\n\nvar majorTicks = element.find('line.majorTick');\nmajorTicks.forEach(t => t.attr({stroke: majorColor}));\n\nvar majorTicksText = element.find('text.majorTickText');\nctx.api.font(majorTicksText, majorFont, majorColor);\n\nvar minorTicks = element.find('line.minorTick');\nminorTicks.forEach(t => t.attr({stroke: minorColor}));\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(67, minorY, 73, minorY).stroke({ width: 1 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
"tag": "value-box",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n element.show();\n} else {\n element.hide();\n}",
"actions": null
},
{
"tag": "value-box-background",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var colorProcessor = ctx.properties.valueBoxColor;\n colorProcessor.update(ctx.values.value);\n element.attr({fill: colorProcessor.color});\n}",
"actions": null
},
{
"tag": "value-text",
"stateRenderFunction": "if (ctx.properties.valueBox) {\n var valueTextFont = ctx.properties.valueTextFont;\n var valueTextColor = ctx.properties.valueTextColor;\n var currentVolume = ctx.values.value;\n var valueText = ctx.api.formatValue(currentVolume, 0, ctx.properties.valueUnits, false);\n var colorProcessor = ctx.properties.valueTextColor;\n colorProcessor.update(ctx.values.value);\n ctx.api.font(element, valueTextFont, colorProcessor.color);\n ctx.api.text(element, valueText);\n}",
"actions": null
}
],
"behavior": [
{
"id": "value",
"name": "{i18n:scada.symbol.value}",
"hint": null,
"group": null,
"type": "value",
"valueType": "DOUBLE",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": {
"action": "GET_TIME_SERIES",
"defaultValue": null,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "waterLevel"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "warning",
"name": "{i18n:scada.symbol.warning-state}",
"hint": "{i18n:scada.symbol.warning-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.warning}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"MAJOR",
"MINOR",
"WARNING",
"INDETERMINATE"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "critical",
"name": "{i18n:scada.symbol.critical-state}",
"hint": "{i18n:scada.symbol.critical-state-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.critical}",
"defaultGetValueSettings": {
"action": "GET_ALARM_STATUS",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "state"
},
"getAlarmStatus": {
"severityList": [
"CRITICAL"
],
"typeList": null
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "criticalAnimation",
"name": "{i18n:scada.symbol.critical-state-animation}",
"hint": "{i18n:scada.symbol.critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "minValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 0,
"required": true,
"subLabel": "{i18n:scada.symbol.min-value}",
"divider": true,
"min": -1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "maxValue",
"name": "{i18n:scada.symbol.min-max-value}",
"type": "number",
"default": 100,
"required": true,
"subLabel": "{i18n:scada.symbol.max-value}",
"max": 1000,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "backgroundColor",
"name": "{i18n:scada.symbol.background-color}",
"type": "color",
"default": "#FFFFFF",
"disabled": false,
"visible": true
},
{
"id": "progressBar",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "switch",
"default": true,
"disabled": false,
"visible": true
},
{
"id": "progressBarColor",
"name": "{i18n:scada.symbol.progress-bar}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#4D94E1",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressBar",
"disabled": false,
"visible": true
},
{
"id": "progressArrow",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "switch",
"default": false,
"disabled": false,
"visible": true
},
{
"id": "progressArrowColor",
"name": "{i18n:scada.symbol.progress-arrow}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#1C943E",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "progressArrow",
"disabled": false,
"visible": true
},
{
"id": "valueBox",
"name": "{i18n:scada.symbol.value-box}",
"type": "switch",
"default": false,
"disabled": false,
"visible": true
},
{
"id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#F3F3F3",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}",
"type": "units",
"default": "%",
"subLabel": "{i18n:scada.symbol.units}",
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "valueTextFont",
"name": "{i18n:scada.symbol.value-text}",
"type": "font",
"default": {
"size": 14,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}",
"type": "color_settings",
"default": {
"type": "constant",
"color": "#0000008A",
"gradient": {
"advancedMode": false,
"gradient": [
"rgba(0, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
],
"gradientAdvanced": [
{
"source": {
"type": "constant"
},
"color": "rgba(0, 255, 0, 1)"
},
{
"source": {
"type": "constant"
},
"color": "rgba(255, 0, 0, 1)"
}
],
"minValue": 0,
"maxValue": 100
},
"rangeList": {
"advancedMode": false,
"range": [],
"rangeAdvanced": []
},
"colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';"
},
"disableOnProperty": "valueBox",
"disabled": true,
"visible": true
},
{
"id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "number",
"default": 5,
"subLabel": "{i18n:scada.symbol.intervals}",
"divider": true,
"min": 1,
"step": 1,
"disabled": false,
"visible": true
},
{
"id": "majorFont",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "font",
"default": {
"size": 12,
"sizeUnit": "px",
"family": "Roboto",
"weight": "500",
"style": "normal"
},
"disabled": false,
"visible": true
},
{
"id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
},
{
"id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}",
"type": "number",
"default": 10,
"subLabel": "{i18n:scada.symbol.intervals}",
"min": 1,
"disabled": false,
"visible": true
},
{
"id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#747474",
"subLabel": "{i18n:scada.symbol.normal}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#FAA405",
"subLabel": "{i18n:scada.symbol.warning}",
"divider": true,
"disabled": false,
"visible": true
},
{
"id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color",
"default": "#D12730",
"subLabel": "{i18n:scada.symbol.critical}",
"disabled": false,
"visible": true
}
]
}]]></tb:metadata>
<path d="m28 200c-2.2091 0-4-1.791-4-4v-192c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v192c0 2.209-1.7909 4-4 4h-68z" fill="#E5E5E5" tb:tag="background"/><path d="m28 200c-2.2091 0-4-1.791-4-4v-192c0-2.2091 1.7909-4 4-4h68c2.2091 0 4 1.7909 4 4v192c0 2.209-1.7909 4-4 4h-68z" fill="url(#paint0_linear_3742_299973)"/><path d="m28 198.5c-1.3807 0-2.5-1.119-2.5-2.5v-192c0-1.3807 1.1193-2.5 2.5-2.5h68c1.3807 0 2.5 1.1193 2.5 2.5v192c0 1.381-1.1193 2.5-2.5 2.5h-68z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="scale">
<g transform="translate(23.965)" fill="#919191">
<path d="m14.539 10.088v8.5605h-1.4121v-6.8848l-2.0918 0.709v-1.166l3.3339-1.2187zm8.7754 3.58v1.3946c0 0.6679-0.0665 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.2461 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5468 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.5859 0.7032 0.1641 0.2851 0.2891 0.6308 0.375 1.0371 0.086 0.4023 0.129 0.8711 0.129 1.4062zm-1.4122 1.5938v-1.8047c0-0.3398-0.0195-0.6387-0.0585-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1133-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4336 0.3984-0.1171 0.1836-0.207 0.4258-0.2695 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0195 0.6465 0.0586 0.9082s0.0977 0.4863 0.1758 0.6738c0.0781 0.1836 0.1719 0.3359 0.2812 0.457 0.1133 0.1172 0.2422 0.2032 0.3868 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2226 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441 0.0585-0.3047 0.0878-0.6699 0.0878-1.0957zm8.4825-1.5938v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2383 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z"/>
<path d="m23.332 49.374c0 0.5313-0.123 0.9785-0.3691 1.3418s-0.582 0.6387-1.0078 0.8262c-0.4219 0.1836-0.8985 0.2754-1.4297 0.2754-0.5313 0-1.0098-0.0918-1.4356-0.2754-0.4257-0.1875-0.7617-0.4629-1.0078-0.8262s-0.3691-0.8105-0.3691-1.3418c0-0.3515 0.0683-0.6699 0.2051-0.955 0.1367-0.2891 0.33-0.5372 0.58-0.7442 0.254-0.2109 0.5508-0.373 0.8907-0.4863 0.3437-0.1133 0.7187-0.1699 1.125-0.1699 0.539 0 1.0215 0.0996 1.4472 0.2988 0.4258 0.1992 0.7598 0.4746 1.002 0.8262 0.2461 0.3515 0.3691 0.7617 0.3691 1.2304zm-1.4179-0.0703c0-0.2851-0.0586-0.5351-0.1758-0.75-0.1172-0.2148-0.2813-0.3808-0.4922-0.498s-0.4551-0.1758-0.7324-0.1758c-0.2813 0-0.5254 0.0586-0.7325 0.1758-0.207 0.1172-0.3691 0.2832-0.4863 0.498-0.1133 0.2149-0.1699 0.4649-0.1699 0.75 0 0.2891 0.0566 0.5391 0.1699 0.75 0.1133 0.207 0.2754 0.3653 0.4863 0.4746 0.211 0.1094 0.459 0.1641 0.7442 0.1641 0.2851 0 0.5312-0.0547 0.7383-0.1641 0.207-0.1093 0.3672-0.2676 0.4804-0.4746 0.1133-0.2109 0.17-0.4609 0.17-0.75zm1.2246-3.8906c0 0.4258-0.1133 0.8047-0.3399 1.1367-0.2226 0.332-0.5312 0.5938-0.9258 0.7852-0.3945 0.1875-0.8437 0.2812-1.3476 0.2812-0.5078 0-0.9609-0.0937-1.3594-0.2812-0.3945-0.1914-0.7051-0.4532-0.9316-0.7852-0.2227-0.332-0.334-0.7109-0.334-1.1367 0-0.5078 0.1113-0.9356 0.334-1.2832 0.2265-0.3516 0.5371-0.6192 0.9316-0.8027 0.3945-0.1836 0.8457-0.2754 1.3535-0.2754s0.959 0.0918 1.3535 0.2754c0.3946 0.1835 0.7032 0.4511 0.9258 0.8027 0.2266 0.3476 0.3399 0.7754 0.3399 1.2832zm-1.4121 0.0469c0-0.2539-0.0508-0.4766-0.1524-0.668-0.0976-0.1953-0.2363-0.3477-0.416-0.457-0.1797-0.1094-0.3926-0.1641-0.6387-0.1641s-0.459 0.0527-0.6386 0.1582c-0.1797 0.1055-0.3184 0.2539-0.4161 0.4453-0.0976 0.1914-0.1464 0.4199-0.1464 0.6856 0 0.2617 0.0488 0.4902 0.1464 0.6855 0.0977 0.1914 0.2364 0.3418 0.4161 0.4512 0.1836 0.1094 0.3984 0.164 0.6445 0.164s0.459-0.0546 0.6387-0.164 0.3183-0.2598 0.416-0.4512c0.0976-0.1953 0.1465-0.4238 0.1465-0.6855zm8.6582 1.2597v1.3946c0 0.6679-0.0664 1.2382-0.1993 1.7109-0.1289 0.4688-0.3164 0.8496-0.5625 1.1426-0.246 0.2929-0.541 0.5078-0.8847 0.6445-0.3399 0.1367-0.7207 0.2051-1.1426 0.2051-0.3359 0-0.6484-0.043-0.9375-0.1289-0.2852-0.086-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.293-0.2852-0.6426-0.375-1.0488-0.086-0.4063-0.1289-0.877-0.1289-1.4121v-1.3946c0-0.6718 0.0664-1.2382 0.1992-1.6992 0.1328-0.4648 0.3222-0.8418 0.5683-1.1308 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.0429 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3925 0.2305 0.1797 0.4258 0.4141 0.586 0.7032 0.164 0.2851 0.289 0.6308 0.375 1.0371 0.0859 0.4023 0.1289 0.8711 0.1289 1.4062zm-1.4121 1.5938v-1.8047c0-0.3398-0.0196-0.6387-0.0586-0.8965-0.0391-0.2617-0.0977-0.4824-0.1758-0.6621-0.0742-0.1836-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2012-0.3867-0.252-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.1289-0.1718 0.082-0.3164 0.2149-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2969-0.0879 0.6582-0.0879 1.084v1.8047c0 0.3437 0.0196 0.6465 0.0586 0.9082 0.0391 0.2617 0.0977 0.4863 0.1758 0.6738 0.0781 0.1836 0.1719 0.336 0.2813 0.457 0.1132 0.1172 0.2421 0.2032 0.3867 0.2579 0.1484 0.0546 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.1289 0.1719-0.086 0.3164-0.2227 0.4336-0.4102 0.1172-0.1914 0.2051-0.4394 0.2637-0.7441s0.0879-0.6699 0.0879-1.0957z"/>
<path d="m22.02 74.016h0.1758v1.1543h-0.0996c-0.5039 0-0.9395 0.0781-1.3066 0.2343-0.3633 0.1563-0.6621 0.3711-0.8965 0.6446-0.2344 0.2734-0.4102 0.5937-0.5274 0.9609-0.1132 0.3633-0.1699 0.75-0.1699 1.1602v1.3418c0 0.3398 0.0371 0.6406 0.1113 0.9023 0.0743 0.2578 0.1778 0.4746 0.3106 0.6504 0.1367 0.1719 0.293 0.3027 0.4687 0.3926 0.1758 0.0898 0.3653 0.1347 0.5684 0.1347 0.2109 0 0.4023-0.0429 0.5742-0.1289 0.1719-0.0898 0.3184-0.2129 0.4395-0.3691 0.1211-0.1563 0.2129-0.3418 0.2754-0.5567 0.0625-0.2148 0.0937-0.4492 0.0937-0.7031 0-0.2422-0.0312-0.4687-0.0937-0.6797-0.0586-0.2148-0.1465-0.4023-0.2637-0.5625-0.1172-0.164-0.2637-0.291-0.4395-0.3808-0.1718-0.0938-0.3711-0.1406-0.5976-0.1406-0.2813 0-0.5371 0.0664-0.7676 0.1992-0.2266 0.1328-0.4102 0.3066-0.5508 0.5215-0.1367 0.2109-0.2109 0.4355-0.2226 0.6738l-0.5391-0.1758c0.0313-0.3633 0.1113-0.6895 0.2402-0.9785 0.1328-0.2891 0.3067-0.5352 0.5215-0.7383 0.2149-0.2031 0.4629-0.3574 0.7442-0.4629 0.2851-0.1094 0.5976-0.164 0.9375-0.164 0.414 0 0.7734 0.0781 1.0781 0.2343 0.3047 0.1563 0.5566 0.3692 0.7558 0.6387 0.2032 0.2656 0.3536 0.5703 0.4512 0.9141 0.1016 0.3398 0.1524 0.6953 0.1524 1.0664 0 0.4101-0.0625 0.7929-0.1875 1.1484-0.125 0.3516-0.3086 0.6602-0.5508 0.9258-0.2383 0.2656-0.5293 0.4726-0.8731 0.6211-0.3398 0.1484-0.7265 0.2226-1.1601 0.2226-0.4571 0-0.8653-0.0879-1.2246-0.2636-0.3555-0.1758-0.6582-0.418-0.9082-0.7266-0.2461-0.3086-0.4336-0.6641-0.5625-1.0664s-0.1934-0.8281-0.1934-1.2773v-0.586c0-0.6484 0.082-1.2598 0.2461-1.834 0.1641-0.5781 0.416-1.0879 0.7559-1.5293 0.3437-0.4414 0.7832-0.7871 1.3183-1.0371 0.5352-0.2539 1.1738-0.3808 1.916-0.3808zm8.3653 3.6035v1.3945c0 0.668-0.0664 1.2383-0.1993 1.7109-0.1289 0.4688-0.3164 0.8497-0.5625 1.1426-0.246 0.293-0.541 0.5078-0.8847 0.6446-0.3399 0.1367-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.0429-0.9375-0.1289-0.2852-0.0859-0.543-0.2207-0.7734-0.4043-0.2305-0.1836-0.4278-0.4199-0.5918-0.709-0.1602-0.2929-0.2852-0.6425-0.375-1.0488-0.086-0.4062-0.1289-0.8769-0.1289-1.4121v-1.3945c0-0.6719 0.0664-1.2383 0.1992-1.6992 0.1328-0.4649 0.3222-0.8418 0.5683-1.1309 0.2461-0.293 0.5391-0.5059 0.8789-0.6387 0.3438-0.1328 0.7266-0.1992 1.1485-0.1992 0.3398 0 0.6523 0.043 0.9375 0.1289 0.289 0.082 0.5469 0.2129 0.7734 0.3926 0.2305 0.1797 0.4258 0.4141 0.586 0.7031 0.164 0.2852 0.289 0.6309 0.375 1.0371 0.0859 0.4024 0.1289 0.8711 0.1289 1.4063zm-1.4121 1.5937v-1.8047c0-0.3398-0.0196-0.6386-0.0586-0.8964-0.0391-0.2618-0.0977-0.4825-0.1758-0.6622-0.0742-0.1835-0.168-0.332-0.2813-0.4453-0.1132-0.1172-0.2422-0.2011-0.3867-0.2519-0.1445-0.0547-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.0429-0.586 0.1289-0.1718 0.082-0.3164 0.2148-0.4335 0.3984-0.1172 0.1836-0.2071 0.4258-0.2696 0.7266-0.0586 0.2968-0.0879 0.6582-0.0879 1.0839v1.8047c0 0.3438 0.0196 0.6465 0.0586 0.9082 0.0391 0.2618 0.0977 0.4864 0.1758 0.6739 0.0781 0.1836 0.1719 0.3359 0.2813 0.457 0.1132 0.1172 0.2421 0.2031 0.3867 0.2578 0.1484 0.0547 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.0429 0.5918-0.1289 0.1719-0.0859 0.3164-0.2226 0.4336-0.4101 0.1172-0.1914 0.2051-0.4395 0.2637-0.7442s0.0879-0.6699 0.0879-1.0957z"/>
<path d="m23.631 111.66v1.125h-6.1524l-0.0469-0.85 3.6797-5.765h1.1309l-1.2246 2.097-2.1153 3.393zm-1.0664-5.49v8.531h-1.4122v-8.531zm7.8203 3.55v1.395c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.292-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.395c0-0.671 0.0664-1.238 0.1992-1.699 0.1328-0.465 0.3222-0.842 0.5683-1.131 0.2461-0.293 0.5391-0.505 0.8789-0.638 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.042 0.9375 0.128 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.406zm-1.4121 1.594v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.118-0.2422-0.202-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.117 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z"/>
<path d="m23.473 145.48v1.125h-5.7188v-0.967l2.7773-3.029c0.3047-0.344 0.545-0.641 0.7208-0.891 0.1757-0.25 0.2988-0.474 0.3691-0.673 0.0742-0.204 0.1113-0.401 0.1113-0.592 0-0.27-0.0508-0.506-0.1523-0.709-0.0977-0.207-0.2422-0.369-0.4336-0.487-0.1914-0.121-0.4238-0.181-0.6973-0.181-0.3164 0-0.582 0.068-0.7969 0.205-0.2148 0.137-0.3769 0.326-0.4863 0.568-0.1094 0.239-0.164 0.512-0.164 0.821h-1.4122c0-0.497 0.1133-0.95 0.3399-1.36 0.2265-0.414 0.5547-0.742 0.9844-0.984 0.4297-0.246 0.9472-0.369 1.5527-0.369 0.5703 0 1.0547 0.095 1.4531 0.287 0.3985 0.191 0.7012 0.463 0.9082 0.814 0.211 0.352 0.3164 0.768 0.3164 1.248 0 0.266-0.0429 0.53-0.1289 0.791-0.0859 0.262-0.209 0.524-0.3691 0.785-0.1563 0.258-0.3418 0.518-0.5567 0.78-0.2148 0.258-0.4511 0.519-0.7089 0.785l-1.8457 2.033zm6.9121-3.855v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.642-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.464 0.3222-0.841 0.5683-1.13 0.2461-0.293 0.5391-0.506 0.8789-0.639 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.129 0.289 0.082 0.5469 0.212 0.7734 0.392 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.183-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.215-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.297-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.909 0.0391 0.261 0.0977 0.486 0.1758 0.673 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.204 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z"/>
<path d="m30.385 173.02v1.394c0 0.668-0.0664 1.238-0.1993 1.711-0.1289 0.469-0.3164 0.85-0.5625 1.143-0.246 0.293-0.541 0.507-0.8847 0.644-0.3399 0.137-0.7207 0.205-1.1426 0.205-0.3359 0-0.6484-0.043-0.9375-0.129-0.2852-0.086-0.543-0.22-0.7734-0.404-0.2305-0.184-0.4278-0.42-0.5918-0.709-0.1602-0.293-0.2852-0.643-0.375-1.049-0.086-0.406-0.1289-0.877-0.1289-1.412v-1.394c0-0.672 0.0664-1.239 0.1992-1.7 0.1328-0.465 0.3222-0.841 0.5683-1.131 0.2461-0.293 0.5391-0.505 0.8789-0.638 0.3438-0.133 0.7266-0.199 1.1485-0.199 0.3398 0 0.6523 0.043 0.9375 0.128 0.289 0.082 0.5469 0.213 0.7734 0.393 0.2305 0.18 0.4258 0.414 0.586 0.703 0.164 0.285 0.289 0.631 0.375 1.037 0.0859 0.403 0.1289 0.871 0.1289 1.407zm-1.4121 1.593v-1.804c0-0.34-0.0196-0.639-0.0586-0.897-0.0391-0.262-0.0977-0.482-0.1758-0.662-0.0742-0.184-0.168-0.332-0.2813-0.445-0.1132-0.117-0.2422-0.201-0.3867-0.252-0.1445-0.055-0.3066-0.082-0.4863-0.082-0.2188 0-0.4141 0.043-0.586 0.129-0.1718 0.082-0.3164 0.214-0.4335 0.398-0.1172 0.184-0.2071 0.426-0.2696 0.727-0.0586 0.296-0.0879 0.658-0.0879 1.084v1.804c0 0.344 0.0196 0.647 0.0586 0.908 0.0391 0.262 0.0977 0.487 0.1758 0.674 0.0781 0.184 0.1719 0.336 0.2813 0.457 0.1132 0.118 0.2421 0.203 0.3867 0.258 0.1484 0.055 0.3105 0.082 0.4863 0.082 0.2227 0 0.4199-0.043 0.5918-0.129s0.3164-0.222 0.4336-0.41c0.1172-0.191 0.2051-0.439 0.2637-0.744s0.0879-0.67 0.0879-1.096z"/>
</g>
<g transform="matrix(1 0 0 1.0194 24.515 -3.9105)" stroke="#747474">
<path d="m50.695 14.031v164.03h0.22794v-164.03z" fill="#747474" stroke-width=".77206"/>
<line x1="39" x2="51" y1="14.145" y2="14.145"/>
<line x1="45" x2="51" y1="17.76" y2="17.76"/>
<line x1="45" x2="51" y1="21.02" y2="21.02"/>
<line x1="45" x2="51" y1="24.28" y2="24.28"/>
<line x1="45" x2="51" y1="27.54" y2="27.54"/>
<line x1="45" x2="51" y1="30.8" y2="30.8"/>
<line x1="45" x2="51" y1="34.06" y2="34.06"/>
<line x1="45" x2="51" y1="37.32" y2="37.32"/>
<line x1="45" x2="51" y1="40.58" y2="40.58"/>
<line x1="45" x2="51" y1="43.84" y2="43.84"/>
<line x1="39" x2="51" y1="47.1" y2="47.1"/>
<line x1="45" x2="51" y1="50.36" y2="50.36"/>
<line x1="45" x2="51" y1="53.62" y2="53.62"/>
<line x1="45" x2="51" y1="56.88" y2="56.88"/>
<line x1="45" x2="51" y1="60.14" y2="60.14"/>
<line x1="45" x2="51" y1="63.4" y2="63.4"/>
<line x1="45" x2="51" y1="66.66" y2="66.66"/>
<line x1="45" x2="51" y1="69.92" y2="69.92"/>
<line x1="45" x2="51" y1="73.18" y2="73.18"/>
<line x1="45" x2="51" y1="76.44" y2="76.44"/>
<line x1="39" x2="51" y1="79.7" y2="79.7"/>
<line x1="45" x2="51" y1="82.96" y2="82.96"/>
<line x1="45" x2="51" y1="86.22" y2="86.22"/>
<line x1="45" x2="51" y1="89.48" y2="89.48"/>
<line x1="45" x2="51" y1="92.74" y2="92.74"/>
<line x1="45" x2="51" y1="96" y2="96"/>
<line x1="45" x2="51" y1="99.26" y2="99.26"/>
<line x1="45" x2="51" y1="102.52" y2="102.52"/>
<line x1="45" x2="51" y1="105.78" y2="105.78"/>
<line x1="45" x2="51" y1="109.04" y2="109.04"/>
<line x1="39" x2="51" y1="112.3" y2="112.3"/>
<line x1="45" x2="51" y1="115.56" y2="115.56"/>
<line x1="45" x2="51" y1="118.82" y2="118.82"/>
<line x1="45" x2="51" y1="122.08" y2="122.08"/>
<line x1="45" x2="51" y1="125.34" y2="125.34"/>
<line x1="45" x2="51" y1="128.6" y2="128.6"/>
<line x1="45" x2="51" y1="131.86" y2="131.86"/>
<line x1="45" x2="51" y1="135.12" y2="135.12"/>
<line x1="45" x2="51" y1="138.38" y2="138.38"/>
<line x1="45" x2="51" y1="141.64" y2="141.64"/>
<line x1="39" x2="51" y1="144.9" y2="144.9"/>
<line x1="45" x2="51" y1="148.16" y2="148.16"/>
<line x1="45" x2="51" y1="151.42" y2="151.42"/>
<line x1="45" x2="51" y1="154.68" y2="154.68"/>
<line x1="45" x2="51" y1="157.94" y2="157.94"/>
<line x1="45" x2="51" y1="161.2" y2="161.2"/>
<line x1="45" x2="51" y1="164.46" y2="164.46"/>
<line x1="45" x2="51" y1="167.72" y2="167.72"/>
<line x1="45" x2="51" y1="170.98" y2="170.98"/>
<line x1="45" x2="51" y1="174.24" y2="174.24"/>
<line x1="38.985" x2="50.985" y1="177.96" y2="177.96"/>
</g>
</g><g tb:tag="progressBar">
<rect x="79.5" y="10.5" width="6" height="169" ry="1.12" stroke="#cecece" tb:tag="progressBorder"/>
<rect transform="scale(1,-1)" x="79.5" y="-180" width="6" height="62" ry="1.12" fill="#4d94e1" stroke="#4d94e1" tb:tag="progressFill"/>
<circle cx="82.5" cy="185" r="7" fill="#4d94e1" tb:tag="progressCircle"/>
</g><g transform="translate(0,167)" fill="#1c943e" style="display: none;" tb:tag="progress-indicator">
<path d="m75 10.562 10.5 6.062v-12.124z" style=""/>
</g><g transform="translate(12)" filter="url(#filter0_ii_3742_299961)" style="display: none;" tb:tag="value-box">
<rect x="22" y="164" width="56" height="28" rx="4" fill="#fffefe" fill-opacity=".75" tb:tag="value-box-background" style=""/>
<rect x="23" y="165" width="54" height="26" rx="3" stroke="#fff" stroke-width="2" style=""/>
<text x="49.853027" y="179.5625" fill="#727171" font-family="Roboto" font-size="14px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve" style=""><tspan dominant-baseline="middle">37%</tspan></text>
</g><path d="m49.56 0s-25.56 0-25.56 33.5v164.18c0 1.3256 0.68053 2.3202 1.52 2.3202h72.96c0.83942 0 1.52-0.9946 1.52-2.3202v-164.18c0-33.5-25.107-33.5-25.107-33.5h-12.893zm25.487 40.6c-0.48969 0-0.88669 0.6268-0.88669 1.4v150.2c0 0.7732 0.39697 1.4 0.88669 1.4h5.5733c0.48969 0 0.88669-0.6268 0.88669-1.4v-150.2c0-0.7732-0.39697-1.4-0.88669-1.4z" fill-opacity="0" tb:tag="clickArea"/><path d="m8 200c2.2091 0 4-1.791 4-4v-192c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v192c0 2.209 1.7909 4 4 4h4z" fill="#93979B"/><path d="m8 200c2.2091 0 4-1.791 4-4v-192c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v192c0 2.209 1.7909 4 4 4h4z" fill="url(#paint1_linear_3742_299973)"/><path d="m8 198.5c1.3807 0 2.5-1.119 2.5-2.5v-192c0-1.3807-1.1193-2.5-2.5-2.5h-4c-1.3807 0-2.5 1.1193-2.5 2.5v192c0 1.381 1.1193 2.5 2.5 2.5h4z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m12 146c0 2.209 1.7908 4 3.9999 4h4c2.2092 0 4.0001-1.791 4.0001-4v-92c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v92z" fill="#647484"/><path d="m12 146c0 2.209 1.7908 4 3.9999 4h4c2.2092 0 4.0001-1.791 4.0001-4v-92c0-2.2091-1.7909-4-4-4h-4c-2.2091 0-4 1.7909-4 4v92z" fill="url(#paint2_linear_3742_299973)"/><path d="m13.5 146c0 1.38 1.1193 2.5 2.4999 2.5h4c1.3808 0 2.5001-1.119 2.5001-2.5v-92c0-1.3807-1.1193-2.5-2.5-2.5h-4c-1.3807 0-2.5 1.1193-2.5 2.5v92z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs>
<linearGradient id="paint0_linear_3742_299973" x1="100" x2="24" y1="100" y2="100.09" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".02629"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".11617"/>
<stop stop-color="#fff" stop-opacity=".3" offset=".49829"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".88762"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".96591"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<linearGradient id="paint1_linear_3742_299973" x1="1.9297" x2="46.003" y1="-14.344" y2="205.53" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".23574"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".39937"/>
<stop stop-color="#fff" stop-opacity=".3" offset=".49829"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".59721"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<linearGradient id="paint2_linear_3742_299973" x1="19.045" x2="14.639" y1="53.812" y2="145.03" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset="0"/>
<stop stop-color="#020202" stop-opacity=".18" offset=".090959"/>
<stop stop-color="#020202" stop-opacity=".1" offset=".1"/>
<stop stop-color="#020202" stop-opacity=".08" offset=".20513"/>
<stop stop-color="#020202" stop-opacity=".05" offset=".21555"/>
<stop stop-color="#020202" stop-opacity=".02" offset=".36962"/>
<stop stop-color="#fff" stop-opacity=".01" offset=".37768"/>
<stop stop-color="#fff" stop-opacity=".01" offset=".62413"/>
<stop stop-color="#020202" stop-opacity=".02" offset=".6313"/>
<stop stop-color="#020202" stop-opacity=".05" offset=".77601"/>
<stop stop-color="#020202" stop-opacity=".08" offset=".7898"/>
<stop stop-color="#020202" stop-opacity=".1" offset=".9"/>
<stop stop-color="#020202" stop-opacity=".18" offset=".90846"/>
<stop stop-color="#020202" stop-opacity=".2" offset="1"/>
</linearGradient>
<filter id="filter0_ii_3742_299961" x="20" y="162" width="60" height="32" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="-2" dy="2"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0 0.445833 0 0 0 0.5 0"/>
<feBlend in2="shape" result="effect1_innerShadow_3742_299961"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="2" dy="-2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_3742_299961" result="effect2_innerShadow_3742_299961"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 45 KiB

4
application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json

@ -26,6 +26,8 @@
"hp_bottom_tee_connector",
"hp_right_tee_connector",
"hp_left_tee_connector",
"hp_top_tee_connector"
"hp_top_tee_connector",
"hp_drawwork",
"hp_crane"
]
}

20
application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json

@ -0,0 +1,20 @@
{
"widgetsBundle": {
"alias": "high_performance_scada_oil_gas",
"title": "High-performance SCADA oil & gas",
"image": "tb-image:aHBfc2NhZGFfb2lsX2dhc19zeXN0ZW1fYnVuZGxlX2ltYWdlLnBuZw==:IkhpZ2gtcGVyZm9ybWFuY2UgU0NBREEgb2lsICYgZ2FzIiBzeXN0ZW0gYnVuZGxlIGltYWdl:SU1BR0U=;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAflBMVEXe3t7b29vf398AAADf39/e3t7e3t7////Gxsapqam5ubnR0NDj4+Otra2hoaHS0tLi4uK4uLjx8fGYmJikpKSdnZ2VlZWWlpbU1NS1tbWnp6eKioqysrLLy8uampra2tqvr6/X19eRkZF+fn7Dw8N1dXW/vr6FhYViYmJwcHCzXr36AAAABnRSTlPvIK8Av79l/pT7AAAI0UlEQVR42uza23LaMBSF4Z5msbyFpOFMAXcg0HT6/i/YGqvdNhBiYXtipf0v7CRX+UaSbWQ+fP745QNS79PHzx8+ps8A8JvxCe+idzCtwpC8Fwj+Q4bWvw0R7oQiJIbTAxAvwOmAIsFgiodYXxxPODecMYmGeD4fAOQoGtCYxEIMgcMQJbEQD5wlCvEYRLEQoig/wCBkMIgiIYKyE+claTBzKxJClO0AegDI8x0GUTREz5NTbvJUR0RwzjjAn1GpQlgcDif4nfiDAZAqBAIc8nBjNyY3ef6MQRR/HzlI7ZI7lCfHaIg5oA5Bu5bZzZaIKh6Cha9BHFq2HN2sb4gjra84rHAreCDKcjUr+n4b8n1WtHKW0LqDSGGwyFFmaOBMLGRv57uM4+moSWNONvuF7RrC8vhsBTBSEJwBEdfXUXRfux+RADIiJM4OSIIQVs8sHUmOiKlBSgd8pGS/2M8zrhutkemY2XyZ7TuGeKlBXHAJ4qN1q9mP+4ofs5WTXq5agipkqePzYLP7kBkiioIQIRrAifouSggCOsN0IaIQxyorJiOrb4uy5StrZFG2WVnTF8QZkAh5NjW4ebbKxqPo1pPdk2paQ7xHaGsAWMTMreXW7ibTUYum3DxtXBcQHRA5H6PmVjbqpKxLCEE9FUliEP49Uc9FNjGIBIdCwHSnFm8cwKQg9PqPXxgEr3ZkB4zp+tgBxNSWif7cDAJx3542nLa4+NrtkugAItefR3TxN4x2tthN1uPQ6G7jP01W2dyxszu7BXELAkIQk7Gh433I0ZaJ6fZZi8RtCEiPagN/aHS4hNSN2rAhwpch1qLasCEEX4JYCuIy4lbfXn+M36ycGHQMEby02K0aG27QbSbjadM7Bzfzhe0YAtYg6oAASGU7iLi8IaoD9OlAJBAUog5AkoHI1cBQHVEQu3nabyacNlsi2W6ebdkhRBe6QtQBIjKxDV4rzB0NtE4goiSFSI131SBf9ChEP+qKwV3IIF+9GWgsIRUHPNFDPUC8oJItDq7iePvX7U0hgmokIALt7b9L9yAEFENo6YwIL34TIk2IQPu796ulOrXC3q/29pethhDvoYV3PBzU3GoIEVRypr73W/TmcysaUjiqe7+hVCBE6OwIkPrfLTqvzxEpHApRSSojEiDBcXv/2qPv2kPoURQcNYhKBH3XHmJQFhw1COSsTAMixUEddQhcKSH6rj3EAlDHBaSUpDEiBCqOS0iQ0KPbeppa6riGBAn6rD3kynENKSWCfmsP4YXjGlJIHoYcs4t+ekQUARF1XENUQjyUrEcXrYmIYiDquIao5Fd797rcJgxEAbjX1UogBBRfErupm16mff8XbO2RWYuDqyCBx+30/EiMmTb6ugtSgYwtJcW2Q0hbUSSJkJ04ECJZO0rJRkE2hJkF4l4EYUMp0Qh5IswMEEPGvQDClAZ5RoglzAwQJpFch3DqRLK/WWsZEslVCFMqRE+4H5ILCSWWDf+OYQu0hDhGCDNh8iEFhRIbvhCCS5HYEiG1IUguRIZn+Cok55crqwm3R/IgRlrq/J3ZXLZW1u2exwnPCmRDROJj4PGB5Io8T3i+LA/CNJQUXFBRyf4syGEM0hFkvoqIpCJyFhxzttaeILNBRGKJ2BEVFsbupktcMwZpHEFyIc7RQGL8Y4GOxZHUW7KIjy3k8yE4NsvGT/bE4kjuraoFBS7kF4GQXR/fNcaRYcqGbJSKH+05EBwbnoUr3EVToxUGF/JLVETmePcCdco0ggv5hSDM67Wxds1MEEcTs79VaxkcWsU+6yg7sbVwIZ8PKQhi5UU2hFmpCeffnIpMg0z9+bYch9SWJEtBmC3z6UtuRar1D30lP9YVQXIgfPXNEcfEK9mdL0ezC868jS+KSJaoiBDYMmX1Fu/9iJvgiNeN9z06yoLEHb4iZPN6a3UugWpacbSstN98pvkgPPqWh4gkBdKVZ4falgKpWbXavzzQghXBhzNRPqWxjo1UbwXCx+1GmmshCD4uC39gSmP5MdccQI46aa5lWotDDUqcm9pYYxClW2muBSoy/kh5Ukm+6VO++Hm8d/jjpf3p9ydD4g6BiGQ6pPPnWqmCHOyyQ3UzQRi3QghKOAHSBPPIEhBLkqIigIhkIoR3/ajbAeTpEvKFZ28tcQhEJNMghWbuIY0uw5m91rqHMAslB1KEDoSgxMV/sOmUuoAoHa61tuoColTH+RDH6BBIIBayiToeVAhRtRaHLlUIUQ8mG2LAARCQRHuLH9UAIlO5nwp1K5CjhLMh4AAISKIV2akAwuW5DnLyrTmAqEMuhMEBEJDEIPsygPgRw7cA0la5FQEHQFDCsStyoxBuL9cqTQhRXT5EHAhBSfy0ZcsBpJHp4wgYh9Q2C+IcOBCCEhO/iyCQmi8ndN2vHEOI+pAFMehACEpc/MqiQJp2DNI2A4jNg6ADISjh+JVFgWjlcxKx8tEDSJcFYXQgBCU2slgEiPzPkHvWrBCDDoSghJMgzWnzGuSQBbEVRSDTJXoA2fbNFKzn6wFklQMxa0qBUGGi04hAlEBgc67WKigNQuYFV60bfcpOha3V9mtgfUrjPZxVkVQIT3xkQ2b2VqvRVHRzSHRGtFuFkUVWG7k2nwLhFEj8SvbTHxyqHd27o2UrktZbrhtxlP0/fqMgmyILYigJEl/JM0iai3bbNuBgyoLwUhByq1JdpNXBZv3UBpvPUo+bV4QpkvWu7BkffsAh8bWntN2B6I4hRHa1b5ibx9WmYK/qx87UrR6ZWW+0JclNWivpSjaz75oPD+L4+sFPxzIJ3q4i+Xepv5yXJKuCIFkQHAtLrP9yTpEKkXBX6YfDLsJIgcBf+bG8mo9YstTMD4HD7JO6mk80iKGFkt9aUyD3VRForSkQlyiZHYJn0EmQxN5aAIIjmQi5n4oMIX9tRUaer66vZkunfCafe4LISKAU8cLcU2tV3Odj+cJ85z7mBpL/H+x41/kPubf8Mx+s++od/RN58+98iPbb13//x2i/ev/67S8YXTU9MiU8PgAAAABJRU5ErkJggg==",
"scada": true,
"description": "Bundle with high-performance SCADA symbols for oil and gas system",
"order": 9405,
"name": "High-performance SCADA oil & gas"
},
"widgetTypeFqns": [
"hp_drilling_rig",
"hp_hook",
"hp_rotor",
"hp_preventer",
"hp_drill",
"hp_drilling_line",
"hp_platform"
]
}

4
application/src/main/data/json/system/widget_bundles/scada_fluid_system.json

@ -42,6 +42,10 @@
"vertical_inline_flow_meter",
"left_analog_water_level_meter",
"right_analog_water_level_meter",
"meter",
"small_meter",
"small_right_meter",
"small_left_meter",
"leak_sensor",
"centrifugal_pump",
"small_right_motor_pump",

24
application/src/main/data/json/system/widget_types/gateway_configuration.json

File diff suppressed because one or more lines are too long

24
application/src/main/data/json/system/widget_types/gateway_configuration__single_device_.json

File diff suppressed because one or more lines are too long

23
application/src/main/data/json/system/widget_types/gateway_connectors.json

File diff suppressed because one or more lines are too long

23
application/src/main/data/json/system/widget_types/gateway_custom_statistics.json

File diff suppressed because one or more lines are too long

23
application/src/main/data/json/system/widget_types/gateway_general_chart_statistics.json

File diff suppressed because one or more lines are too long

30
application/src/main/data/json/system/widget_types/gateway_general_configuration.json

File diff suppressed because one or more lines are too long

23
application/src/main/data/json/system/widget_types/gateway_logs.json

File diff suppressed because one or more lines are too long

44
application/src/main/data/json/system/widget_types/gateway_status.json

@ -0,0 +1,44 @@
{
"fqn": "gateway_widgets.gateway_status",
"name": "Gateway status",
"deprecated": false,
"image": "tb-image;/api/images/system/gateway_status_system_widget_image.png",
"description": "Indicator of Gateway connection status.",
"descriptor": {
"type": "latest",
"sizeX": 7.5,
"sizeY": 3,
"resources": [
{
"url": "tb-resource;/api/resource/js_module/system/gateway-management-extension.js",
"isModule": true
}
],
"templateHtml": "<tb-gateway-status [deviceId]=\"entityId\" *ngIf=\"entityId\" [ctx]=\"ctx\"></tb-gateway-status>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n }\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.gatewayStatus?.onDataUpdated();\n};",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-gateway-status",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"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\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}"
},
"tags": [
"router",
"bridge",
"hub",
"access point",
"relay",
"opc ua",
"opc-ua",
"modbus",
"bacnet",
"odbc",
"ftp",
"snmp",
"mqtt",
"xmpp",
"ocpp",
"ble",
"bluetooth"
]
}

23
application/src/main/data/json/system/widget_types/service_rpc.json

File diff suppressed because one or more lines are too long

4432
application/src/main/data/resources/dashboards/gateways_dashboard.json

File diff suppressed because it is too large

8
application/src/main/data/resources/js_modules/gateway-management-extension.js

File diff suppressed because one or more lines are too long

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

@ -23,7 +23,7 @@ UPDATE user_credentials c SET failed_login_attempts = (SELECT (additional_info::
WHERE failed_login_attempts IS NULL;
UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - 'failedLoginAttempts' - 'userCredentialsEnabled')::text
WHERE additional_info IS NOT NULL AND additional_info != 'null';
WHERE additional_info IS NOT NULL AND additional_info != 'null' AND jsonb_typeof(additional_info::jsonb) = 'object';
-- UPDATE RULE NODE DEBUG MODE TO DEBUG STRATEGY START

14
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -35,6 +35,8 @@ import org.thingsboard.server.service.install.migrate.TsLatestMigrateService;
import org.thingsboard.server.service.install.update.CacheCleanupService;
import org.thingsboard.server.service.install.update.DataUpdateService;
import static org.thingsboard.server.service.install.update.DefaultDataUpdateService.getEnv;
@Service
@Profile("install")
@Slf4j
@ -94,11 +96,11 @@ public class ThingsboardInstallService {
public void performInstall() {
try {
if (isUpgrade) {
log.info("Starting ThingsBoard Upgrade from version {} ...", upgradeFromVersion);
if ("cassandra-latest-to-postgres".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ...");
latestMigrateService.migrate();
} else if (upgradeFromVersion.equals("3.9.0-resources")) {
installScripts.updateResourcesUsage();
} else {
// TODO DON'T FORGET to update SUPPORTED_VERSIONS_FROM in DefaultDatabaseSchemaSettingsService
databaseSchemaVersionService.validateSchemaSettings();
@ -118,6 +120,14 @@ public class ThingsboardInstallService {
entityDatabaseSchemaService.createDatabaseIndexes();
// Runs upgrade scripts that are not possible in plain SQL.
// TODO: cleanup update code after each release
if (!getEnv("SKIP_RESOURCES_USAGE_MIGRATION", false)) {
installScripts.setUpdateResourcesUsage(true);
} else {
log.info("Skipping resources usage migration. Run the upgrade with fromVersion as '3.9.0-resources' to migrate");
}
if (installScripts.isUpdateResourcesUsage()) {
installScripts.updateResourcesUsage();
}
dataUpdateService.updateData();
log.info("Updating system data...");
dataUpdateService.upgradeRuleNodes();

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

@ -349,7 +349,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private void startSyncProcess(TenantId tenantId, EdgeId edgeId, UUID requestId, String requestServiceId) {
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null) {
if (!session.isSyncCompleted()) {
if (session.isSyncInProgress()) {
clusterService.pushEdgeSyncResponseToCore(new FromEdgeSyncResponse(requestId, tenantId, edgeId, false, "Sync process is active at the moment"), requestServiceId);
} else {
boolean success = false;
@ -368,7 +368,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
UUID requestId = request.getId();
EdgeGrpcSession session = sessions.get(request.getEdgeId());
if (session != null && !session.isSyncCompleted()) {
if (session != null && session.isSyncInProgress()) {
responseConsumer.accept(new FromEdgeSyncResponse(requestId, request.getTenantId(), request.getEdgeId(), false, "Sync process is active at the moment"));
} else {
log.trace("[{}][{}] Processing sync edge request [{}], serviceId [{}]", request.getTenantId(), request.getId(), request.getEdgeId(), request.getServiceId());
@ -411,7 +411,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
newEventLock.lock();
try {
if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) {
log.trace("[{}][{}] Set session new events flag to false", tenantId, edgeId.getId());
log.trace("[{}][{}] set session new events flag to false", tenantId, edgeId.getId());
sessionNewEvents.put(edgeId, false);
session.processHighPriorityEvents();
processEdgeEventMigrationIfNeeded(session, edgeId);
@ -420,6 +420,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
@Override
public void onSuccess(Boolean newEventsAdded) {
if (Boolean.TRUE.equals(newEventsAdded)) {
log.trace("[{}][{}] new events added. set session new events flag to true", tenantId, edgeId.getId());
sessionNewEvents.put(edgeId, true);
}
scheduleEdgeEventsCheck(session);

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

@ -28,6 +28,7 @@ import org.springframework.data.util.Pair;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId;
@ -130,13 +131,12 @@ public abstract class EdgeGrpcSession implements Closeable {
private Long previousStartTs;
private Long newStartSeqId;
private Long previousStartSeqId;
private Long seqIdEnd;
private StreamObserver<RequestMsg> inputStream;
private StreamObserver<ResponseMsg> outputStream;
private volatile boolean connected;
private volatile boolean syncCompleted;
private volatile boolean syncInProgress;
private EdgeVersion edgeVersion;
private int maxInboundMessageSize;
@ -191,7 +191,7 @@ public abstract class EdgeGrpcSession implements Closeable {
}
startSyncProcess(fullSync);
} else {
syncCompleted = true;
syncInProgress = false;
}
}
if (requestMsg.getMsgType().equals(RequestMsgType.UPLINK_RPC_MESSAGE)) {
@ -246,10 +246,14 @@ public abstract class EdgeGrpcSession implements Closeable {
}
public void startSyncProcess(boolean fullSync) {
log.info("[{}][{}][{}] Staring edge sync process", tenantId, edge.getId(), sessionId);
syncCompleted = false;
interruptGeneralProcessingOnSync();
doSync(new EdgeSyncCursor(ctx, edge, fullSync));
if (!syncInProgress) {
log.info("[{}][{}][{}] Staring edge sync process", tenantId, edge.getId(), sessionId);
syncInProgress = true;
interruptGeneralProcessingOnSync();
doSync(new EdgeSyncCursor(ctx, edge, fullSync));
} else {
log.info("[{}][{}][{}] Sync is already started, skipping starting it now", tenantId, edge.getId(), sessionId);
}
}
private void doSync(EdgeSyncCursor cursor) {
@ -292,6 +296,7 @@ public abstract class EdgeGrpcSession implements Closeable {
protected void processEdgeEvents(EdgeEventFetcher fetcher, PageLink pageLink, SettableFuture<Pair<Long, Long>> result) {
try {
log.trace("[{}] Start processing edge events, fetcher = {}, pageLink = {}", sessionId, fetcher.getClass().getSimpleName(), pageLink);
processHighPriorityEvents();
PageData<EdgeEvent> pageData = fetcher.fetchEdgeEvents(edge.getTenantId(), edge, pageLink);
if (isConnected() && !pageData.getData().isEmpty()) {
@ -327,7 +332,7 @@ public abstract class EdgeGrpcSession implements Closeable {
}
}, ctx.getGrpcCallbackExecutorService());
} else {
log.trace("[{}] no event(s) found. Stop processing edge events", sessionId);
log.trace("[{}] no event(s) found. Stop processing edge events, fetcher = {}, pageLink = {}", sessionId, fetcher.getClass().getSimpleName(), pageLink);
result.set(null);
}
} catch (Exception e) {
@ -406,6 +411,8 @@ public abstract class EdgeGrpcSession implements Closeable {
|| sessionState.getScheduledSendDownlinkTask() != null && !sessionState.getScheduledSendDownlinkTask().isCancelled()) {
log.debug("[{}][{}][{}] Previous send downlink future was not properly completed, stopping it now!", tenantId, edge.getId(), sessionId);
stopCurrentSendDownlinkMsgsTask(true);
} else {
log.trace("[{}][{}][{}] Previous send downlink future is not active", tenantId, edge.getId(), sessionId);
}
}
@ -519,12 +526,12 @@ public abstract class EdgeGrpcSession implements Closeable {
try {
if (msg.getSuccess()) {
sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId());
log.debug("[{}][{}] Msg has been processed successfully! Msg Id: [{}], Msg: {}", tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg);
log.debug("[{}][{}][{}] Msg has been processed successfully! Msg Id: [{}], Msg: {}", tenantId, edge.getId(), sessionId, msg.getDownlinkMsgId(), msg);
} else {
log.error("[{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg.getErrorMsg());
log.error("[{}][{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", tenantId, edge.getId(), sessionId, msg.getDownlinkMsgId(), msg.getErrorMsg());
}
if (sessionState.getPendingMsgsMap().isEmpty()) {
log.debug("[{}][{}] Pending msgs map is empty. Stopping current iteration", tenantId, edge.getRoutingKey());
log.debug("[{}][{}][{}] Pending msgs map is empty. Stopping current iteration", tenantId, edge.getId(), sessionId);
stopCurrentSendDownlinkMsgsTask(false);
}
} catch (Exception e) {
@ -534,7 +541,7 @@ public abstract class EdgeGrpcSession implements Closeable {
public void processHighPriorityEvents() {
try {
if (isConnected() && isSyncCompleted()) {
if (isConnected() && !isSyncInProgress()) {
if (highPriorityQueue.isEmpty()) {
return;
}
@ -543,6 +550,7 @@ public abstract class EdgeGrpcSession implements Closeable {
while ((event = highPriorityQueue.poll()) != null) {
highPriorityEvents.add(event);
}
log.trace("[{}][{}] Sending high priority events {}", tenantId, sessionId, highPriorityEvents.size());
List<DownlinkMsg> downlinkMsgsPack = convertToDownlinkMsgsPack(highPriorityEvents);
sendDownlinkMsgsPack(downlinkMsgsPack).get();
}
@ -553,18 +561,18 @@ public abstract class EdgeGrpcSession implements Closeable {
public ListenableFuture<Boolean> processEdgeEvents() throws Exception {
SettableFuture<Boolean> result = SettableFuture.create();
log.trace("[{}][{}] starting processing edge events", tenantId, sessionId);
if (isConnected() && isSyncCompleted()) {
if (isConnected() && !isSyncInProgress()) {
Pair<Long, Long> startTsAndSeqId = getQueueStartTsAndSeqId().get();
previousStartTs = startTsAndSeqId.getFirst();
previousStartSeqId = startTsAndSeqId.getSecond();
GeneralEdgeEventFetcher fetcher = new GeneralEdgeEventFetcher(
previousStartTs,
previousStartSeqId,
seqIdEnd,
false,
Integer.toUnsignedLong(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount()),
ctx.getEdgeEventService());
log.trace("[{}][{}] starting processing edge events, previousStartTs = {}, previousStartSeqId = {}",
tenantId, sessionId, previousStartTs, previousStartSeqId);
Futures.addCallback(startProcessingEdgeEvents(fetcher), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Pair<Long, Long> newStartTsAndSeqId) {
@ -574,18 +582,16 @@ public abstract class EdgeGrpcSession implements Closeable {
@Override
public void onSuccess(@Nullable List<Long> list) {
log.debug("[{}][{}] queue offset was updated [{}]", tenantId, sessionId, newStartTsAndSeqId);
boolean newEventsAvailable;
if (fetcher.isSeqIdNewCycleStarted()) {
seqIdEnd = fetcher.getSeqIdEnd();
boolean newEventsAvailable = isNewEdgeEventsAvailable();
result.set(newEventsAvailable);
newEventsAvailable = isNewEdgeEventsAvailable();
} else {
seqIdEnd = null;
boolean newEventsAvailable = isSeqIdStartedNewCycle();
newEventsAvailable = isSeqIdStartedNewCycle();
if (!newEventsAvailable) {
newEventsAvailable = isNewEdgeEventsAvailable();
}
result.set(newEventsAvailable);
}
result.set(newEventsAvailable);
}
@Override
@ -607,7 +613,7 @@ public abstract class EdgeGrpcSession implements Closeable {
}
}, ctx.getGrpcCallbackExecutorService());
} else {
if (!isSyncCompleted()) {
if (isSyncInProgress()) {
log.trace("[{}][{}] edge sync is not completed yet. Skipping iteration", tenantId, sessionId);
result.set(Boolean.TRUE);
} else {
@ -672,9 +678,12 @@ public abstract class EdgeGrpcSession implements Closeable {
private boolean isSeqIdStartedNewCycle() {
try {
log.trace("[{}][{}][{}] Checking if seq id started new cycle", tenantId, edge.getId(), sessionId);
TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), 0, null, null, newStartTs, System.currentTimeMillis());
PageData<EdgeEvent> edgeEvents = ctx.getEdgeEventService().findEdgeEvents(edge.getTenantId(), edge.getId(), 0L, previousStartSeqId == 0 ? null : previousStartSeqId - 1, pageLink);
return !edgeEvents.getData().isEmpty();
boolean result = !edgeEvents.getData().isEmpty();
log.trace("[{}][{}][{}] Result of check if seq id started new cycle, result = {}", tenantId, edge.getId(), sessionId, result);
return result;
} catch (Exception e) {
log.error("[{}][{}][{}] Failed to execute isSeqIdStartedNewCycle", tenantId, edge.getId(), sessionId, e);
}
@ -683,9 +692,12 @@ public abstract class EdgeGrpcSession implements Closeable {
private boolean isNewEdgeEventsAvailable() {
try {
log.trace("[{}][{}][{}] Checking if new edge events available", tenantId, edge.getId(), sessionId);
TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), 0, null, null, newStartTs, System.currentTimeMillis());
PageData<EdgeEvent> edgeEvents = ctx.getEdgeEventService().findEdgeEvents(edge.getTenantId(), edge.getId(), newStartSeqId, null, pageLink);
return !edgeEvents.getData().isEmpty() || !highPriorityQueue.isEmpty();
boolean result = !edgeEvents.getData().isEmpty() || !highPriorityQueue.isEmpty();
log.trace("[{}][{}][{}] Result of check if new edge events available, result = {}", tenantId, edge.getId(), sessionId, result);
return result;
} catch (Exception e) {
log.error("[{}][{}][{}] Failed to execute isNewEdgeEventsAvailable", tenantId, edge.getId(), sessionId, e);
}
@ -724,7 +736,7 @@ public abstract class EdgeGrpcSession implements Closeable {
}
private void markSyncCompletedSendEdgeEventUpdate() {
syncCompleted = true;
syncInProgress = false;
ctx.getClusterService().onEdgeEventUpdate(new EdgeEventUpdateMsg(edge.getTenantId(), edge.getId()));
}
@ -737,29 +749,27 @@ public abstract class EdgeGrpcSession implements Closeable {
}
}
private void sendDownlinkMsg(ResponseMsg downlinkMsg) {
if (downlinkMsg.getDownlinkMsg().getWidgetTypeUpdateMsgCount() > 0) {
log.trace("[{}][{}] Sending downlink widgetTypeUpdateMsg, downlinkMsgId = {}", tenantId, sessionId, downlinkMsg.getDownlinkMsg().getDownlinkMsgId());
} else {
log.trace("[{}][{}] Sending downlink msg [{}]", tenantId, sessionId, downlinkMsg);
}
private void sendDownlinkMsg(ResponseMsg responseMsg) {
if (isConnected()) {
String responseMsgStr = StringUtils.truncate(responseMsg.toString(), 10000);
log.trace("[{}][{}] Sending downlink msg [{}]", tenantId, sessionId, responseMsgStr);
downlinkMsgLock.lock();
String downlinkMsgStr = responseMsg.hasDownlinkMsg() ? String.valueOf(responseMsg.getDownlinkMsg().getDownlinkMsgId()) : responseMsgStr;
try {
outputStream.onNext(downlinkMsg);
outputStream.onNext(responseMsg);
} catch (Exception e) {
log.error("[{}][{}] Failed to send downlink message [{}]", tenantId, sessionId, downlinkMsg, e);
log.error("[{}][{}] Failed to send downlink message [{}]", tenantId, sessionId, downlinkMsgStr, e);
connected = false;
sessionCloseListener.accept(edge, sessionId);
} finally {
downlinkMsgLock.unlock();
}
log.trace("[{}][{}] Response msg successfully sent. downlinkMsgId = {}", tenantId, sessionId, downlinkMsg.getDownlinkMsg().getDownlinkMsgId());
log.trace("[{}][{}] downlink msg successfully sent [{}]", tenantId, sessionId, downlinkMsgStr);
}
}
protected DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) {
log.trace("[{}] Executing convertEntityEventToDownlink, edgeEvent [{}], action [{}]", edgeEvent.getTenantId(), edgeEvent, edgeEvent.getAction());
private DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) {
log.trace("[{}][{}] Executing convertEntityEventToDownlink, edgeEvent [{}], action [{}]", tenantId, sessionId, edgeEvent, edgeEvent.getAction());
return switch (edgeEvent.getType()) {
case EDGE -> ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent);
case DEVICE -> ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent, edgeVersion);
@ -789,7 +799,7 @@ public abstract class EdgeGrpcSession implements Closeable {
case OAUTH2_CLIENT -> ctx.getOAuth2EdgeProcessor().convertOAuth2ClientEventToDownlink(edgeEvent, edgeVersion);
case DOMAIN -> ctx.getOAuth2EdgeProcessor().convertOAuth2DomainEventToDownlink(edgeEvent, edgeVersion);
default -> {
log.warn("[{}] Unsupported edge event type [{}]", edgeEvent.getTenantId(), edgeEvent);
log.warn("[{}][{}] Unsupported edge event type [{}]", tenantId, sessionId, edgeEvent);
yield null;
}
};
@ -923,8 +933,8 @@ public abstract class EdgeGrpcSession implements Closeable {
}
} catch (Exception e) {
String failureMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg);
log.error("[{}][{}] Can't process uplink msg [{}]", edge.getTenantId(), sessionId, uplinkMsg, e);
ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(edge.getTenantId()).edgeId(edge.getId())
log.error("[{}][{}] Can't process uplink msg [{}]", tenantId, sessionId, uplinkMsg, e);
ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId())
.customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build());
return Futures.immediateFailedFuture(e);
}

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java

@ -73,7 +73,7 @@ public class KafkaEdgeGrpcSession extends EdgeGrpcSession {
private void processMsgs(List<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> msgs, TbQueueConsumer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> consumer) {
log.trace("[{}][{}] starting processing edge events", tenantId, sessionId);
if (isConnected() && isSyncCompleted() && !isHighPriorityProcessing) {
if (isConnected() && !isSyncInProgress() && !isHighPriorityProcessing) {
List<EdgeEvent> edgeEvents = new ArrayList<>();
for (TbProtoQueueMsg<ToEdgeEventNotificationMsg> msg : msgs) {
EdgeEvent edgeEvent = ProtoUtils.fromProto(msg.getValue().getEdgeEventMsg());

9
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java

@ -33,8 +33,6 @@ public class GeneralEdgeEventFetcher implements EdgeEventFetcher {
private final Long queueStartTs;
private Long seqIdStart;
@Getter
private Long seqIdEnd;
@Getter
private boolean seqIdNewCycleStarted;
private Long maxReadRecordsCount;
private final EdgeEventService edgeEventService;
@ -53,10 +51,11 @@ public class GeneralEdgeEventFetcher implements EdgeEventFetcher {
@Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
try {
PageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edge.getId(), seqIdStart, seqIdEnd, (TimePageLink) pageLink);
log.trace("[{}] Finding general edge events [{}], seqIdStart = {}, pageLink = {}",
tenantId, edge.getId(), seqIdStart, pageLink);
PageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edge.getId(), seqIdStart, null, (TimePageLink) pageLink);
if (edgeEvents.getData().isEmpty()) {
this.seqIdEnd = Math.max(this.maxReadRecordsCount, seqIdStart - this.maxReadRecordsCount);
edgeEvents = edgeEventService.findEdgeEvents(tenantId, edge.getId(), 0L, seqIdEnd, (TimePageLink) pageLink);
edgeEvents = edgeEventService.findEdgeEvents(tenantId, edge.getId(), 0L, Math.max(this.maxReadRecordsCount, seqIdStart - this.maxReadRecordsCount), (TimePageLink) pageLink);
if (edgeEvents.getData().stream().anyMatch(ee -> ee.getSeqId() < seqIdStart)) {
log.info("[{}] seqId column of edge_event table started new cycle [{}]", tenantId, edge.getId());
this.seqIdNewCycleStarted = true;

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java

@ -73,7 +73,7 @@ public abstract class RelationEdgeProcessor extends BaseRelationProcessor implem
public ListenableFuture<Void> processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EntityRelation relation = JacksonUtil.fromString(edgeNotificationMsg.getBody(), EntityRelation.class);
if (relation == null || (relation.getFrom().getEntityType().equals(EntityType.EDGE) || relation.getTo().getEntityType().equals(EntityType.EDGE))) {
if (relation == null) {
return Futures.immediateFuture(null);
}
EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());

31
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -235,6 +235,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
if (relationsList != null && !relationsList.isEmpty()) {
List<ListenableFuture<Void>> futures = new ArrayList<>();
for (List<EntityRelation> entityRelations : relationsList) {
if (entityRelations.isEmpty()) {
continue;
}
log.trace("[{}][{}][{}][{}] relation(s) are going to be pushed to edge.", tenantId, edge.getId(), entityId, entityRelations.size());
for (EntityRelation relation : entityRelations) {
try {
@ -255,19 +258,23 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
}
}
}
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<Void> voids) {
futureToSet.set(null);
}
if (futures.isEmpty()) {
futureToSet.set(null);
} else {
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<Void> voids) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable throwable) {
String errMsg = String.format("[%s][%s] Exception during saving edge events [%s]!", tenantId, edge.getId(), relationRequestMsg);
log.error(errMsg, throwable);
futureToSet.setException(new RuntimeException(errMsg, throwable));
}
}, dbCallbackExecutorService);
@Override
public void onFailure(Throwable throwable) {
String errMsg = String.format("[%s][%s] Exception during saving edge events [%s]!", tenantId, edge.getId(), relationRequestMsg);
log.error(errMsg, throwable);
futureToSet.setException(new RuntimeException(errMsg, throwable));
}
}, dbCallbackExecutorService);
}
} else {
futureToSet.set(null);
}

5
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ResourceSubType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
@ -74,7 +75,7 @@ public class DashboardSyncService {
List<RepoFile> resources = listFiles("resources");
for (RepoFile resourceFile : resources) {
byte[] data = getFileContent(resourceFile.path());
resourceService.createOrUpdateSystemResource(ResourceType.JS_MODULE, resourceFile.name(), data);
resourceService.createOrUpdateSystemResource(ResourceType.JS_MODULE, ResourceSubType.EXTENSION, resourceFile.name(), data);
}
List<RepoFile> images = listFiles("images");
for (RepoFile imageFile : images) {
@ -89,7 +90,7 @@ public class DashboardSyncService {
widgetsBundleService.updateSystemWidgets(widgetsBundles, widgetTypes);
RepoFile dashboardFile = listFiles("dashboards").get(0);
resourceService.createOrUpdateSystemResource(ResourceType.DASHBOARD, GATEWAYS_DASHBOARD_KEY, getFileContent(dashboardFile.path()));
resourceService.createOrUpdateSystemResource(ResourceType.DASHBOARD, null, GATEWAYS_DASHBOARD_KEY, getFileContent(dashboardFile.path()));
log.info("Gateways dashboard sync completed");
}

14
application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java

@ -17,6 +17,8 @@ package org.thingsboard.server.service.install;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.core.JdbcTemplate;
@ -39,6 +41,9 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
private final BuildProperties buildProperties;
private final JdbcTemplate jdbcTemplate;
@Value("${install.upgrade.from_version:}")
private String upgradeFromVersion;
private String packageSchemaVersion;
private String schemaVersionFromDb;
@ -102,6 +107,15 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
@Override
public String getDbSchemaVersion() {
if (schemaVersionFromDb == null) {
if (StringUtils.isNotBlank(upgradeFromVersion)) {
/*
* TODO - Remove after the release of 3.9.0:
* This a temporary workaround due to the issue that schema version in the
* tb_schema_settings was set as 3.6.4 during the install of 3.8.1.
* */
schemaVersionFromDb = upgradeFromVersion;
return schemaVersionFromDb;
}
Long version = getSchemaVersionFromDb();
if (version == null) {
onSchemaSettingsError("Upgrade failed: the database schema version is missing.");

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

@ -123,6 +123,9 @@ public class InstallScripts {
@Getter @Setter
private boolean updateImages = false;
@Getter @Setter
private boolean updateResourcesUsage = false;
@Autowired
private ImageService imageService;
@ -416,9 +419,9 @@ public class InstallScripts {
}
Path resourcesDir = Path.of(getDataDir(), RESOURCES_DIR);
loadSystemResources(resourcesDir.resolve("images"), ResourceType.IMAGE);
loadSystemResources(resourcesDir.resolve("js_modules"), ResourceType.JS_MODULE);
loadSystemResources(resourcesDir.resolve("dashboards"), ResourceType.DASHBOARD);
loadSystemResources(resourcesDir.resolve("images"), ResourceType.IMAGE, null);
loadSystemResources(resourcesDir.resolve("js_modules"), ResourceType.JS_MODULE, ResourceSubType.EXTENSION);
loadSystemResources(resourcesDir.resolve("dashboards"), ResourceType.DASHBOARD, null);
}
public void loadDashboards(TenantId tenantId, CustomerId customerId) {
@ -514,7 +517,7 @@ public class InstallScripts {
resourcesUpdater.updateWidgetsResources();
}
private void loadSystemResources(Path dir, ResourceType resourceType) {
private void loadSystemResources(Path dir, ResourceType resourceType, ResourceSubType resourceSubType) {
listDir(dir).forEach(resourceFile -> {
String resourceKey = resourceFile.getFileName().toString();
try {
@ -522,7 +525,7 @@ public class InstallScripts {
if (resourceType == ResourceType.IMAGE) {
imageService.createOrUpdateSystemImage(resourceKey, data);
} else {
resourceService.createOrUpdateSystemResource(resourceType, resourceKey, data);
resourceService.createOrUpdateSystemResource(resourceType, resourceSubType, resourceKey, data);
}
} catch (Exception e) {
throw new RuntimeException("Unable to load system resource " + resourceFile, e);

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

@ -65,7 +65,6 @@ public class DefaultDataUpdateService implements DataUpdateService {
public void updateData() throws Exception {
log.info("Updating data ...");
//TODO: should be cleaned after each release
installScripts.updateResourcesUsage();
log.info("Data updated.");
}

4
application/src/main/java/org/thingsboard/server/service/install/update/ResourcesUpdater.java

@ -113,7 +113,7 @@ public class ResourcesUpdater {
}
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.MINUTES)) {
if (!executor.awaitTermination(5, TimeUnit.HOURS)) {
throw new RuntimeException("Dashboards resources update timeout"); // just in case, should happen
}
log.info("Updated {} dashboards", updatedCount);
@ -142,7 +142,7 @@ public class ResourcesUpdater {
}
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.MINUTES)) {
if (!executor.awaitTermination(5, TimeUnit.HOURS)) {
throw new RuntimeException("Widgets resources update timeout");
}
log.info("Updated {} widgets", updatedCount);

7
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -122,6 +122,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (notificationTemplate == null) {
throw new IllegalArgumentException("Template is missing");
}
NotificationType notificationType = notificationTemplate.getNotificationType();
Set<NotificationDeliveryMethod> deliveryMethods = new HashSet<>();
List<NotificationTarget> targets = new ArrayList<>();
@ -143,13 +144,13 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
try {
channels.get(deliveryMethod).check(tenantId);
} catch (Exception e) {
if (ruleId == null) {
if (ruleId == null && !notificationType.isSystem()) {
throw new IllegalArgumentException(e.getMessage());
} else {
return; // if originated by rule - just ignore delivery method
return; // if originated by rule or notification type is system - just ignore delivery method
}
}
if (ruleId == null && !notificationTemplate.getNotificationType().isSystem()) {
if (ruleId == null && !notificationType.isSystem()) {
if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) {
throw new IllegalArgumentException("Recipients for " + deliveryMethod.getName() + " delivery method not chosen");
}

10
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.ttl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@ -66,7 +67,7 @@ public class AlarmsCleanUpService {
try {
cleanUp(tenantId);
} catch (Exception e) {
log.warn("Failed to clean up alarms by ttl for tenant {}", tenantId, e);
getLogger().warn("Failed to clean up alarms by ttl for tenant {}", tenantId, e);
}
}
}
@ -105,8 +106,13 @@ public class AlarmsCleanUpService {
alarmService.delAlarmTypes(tenantId, typesToRemove);
if (totalRemoved > 0) {
log.info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
getLogger().info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
}
}
// wrapper for tests to spy on static logger
Logger getLogger() {
return log;
}
}

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

@ -604,7 +604,7 @@ cache:
maxSize: "${CACHE_SPECS_EDGE_SESSIONS_MAX_SIZE:10000}" # 0 means the cache is disabled
relatedEdges:
timeToLiveInMinutes: "${CACHE_SPECS_RELATED_EDGES_TTL:1440}" # Related Edges cache TTL
maxSize: "${CACHE_SPECS_RELATED_EDGES_MAX_SIZE:10000}" # 0 means the cache is disabled
maxSize: "${CACHE_SPECS_RELATED_EDGES_MAX_SIZE:0}" # 0 means the cache is disabled
repositorySettings:
timeToLiveInMinutes: "${CACHE_SPECS_REPOSITORY_SETTINGS_TTL:1440}" # Repository settings cache TTL
maxSize: "${CACHE_SPECS_REPOSITORY_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled
@ -1497,9 +1497,7 @@ swagger:
# Queue configuration parameters
queue:
# in-memory or kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:in-memory}"
type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka)
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory:
stats:
@ -1628,122 +1626,6 @@ queue:
print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}"
# Time to wait for the stats-loading requests to Kafka to finish
kafka-response-timeout-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_RESPONSE_TIMEOUT_MS:1000}"
aws_sqs:
# Use the default credentials provider for AWS SQS
use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
# Access key ID from AWS IAM user
access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
# Secret access key from AWS IAM user
secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
# Region from AWS account
region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"
# Number of threads per each AWS SQS queue in consumer
threads_per_topic: "${TB_QUEUE_AWS_SQS_THREADS_PER_TOPIC:1}"
# Thread pool size for aws_sqs queue producer executor provider. Default value equals to AmazonSQSAsyncClient.DEFAULT_THREAD_POOL_SIZE
producer_thread_pool_size: "${TB_QUEUE_AWS_SQS_EXECUTOR_THREAD_POOL_SIZE:50}"
queue-properties:
# AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
rule-engine: "${TB_QUEUE_AWS_SQS_RE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
core: "${TB_QUEUE_AWS_SQS_CORE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
transport-api: "${TB_QUEUE_AWS_SQS_TA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# AWS SQS queue properties. VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
notifications: "${TB_QUEUE_AWS_SQS_NOTIFICATIONS_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800
js-executor: "${TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
ota-updates: "${TB_QUEUE_AWS_SQS_OTA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
version-control: "${TB_QUEUE_AWS_SQS_VC_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
edge: "${TB_QUEUE_AWS_SQS_EDGE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
pubsub:
# Project ID from Google Cloud
project_id: "${TB_QUEUE_PUBSUB_PROJECT_ID:YOUR_PROJECT_ID}"
# API Credentials in JSON format
service_account: "${TB_QUEUE_PUBSUB_SERVICE_ACCOUNT:YOUR_SERVICE_ACCOUNT}"
# Message size for PubSub queue.Value in bytes
max_msg_size: "${TB_QUEUE_PUBSUB_MAX_MSG_SIZE:1048576}"
# Number of messages per consumer
max_messages: "${TB_QUEUE_PUBSUB_MAX_MESSAGES:1000}"
# Thread pool size for pubsub queue executor provider. If set to 0 - default pubsub executor provider value will be used (5 * number of available processors)
executor_thread_pool_size: "${TB_QUEUE_PUBSUB_EXECUTOR_THREAD_POOL_SIZE:0}"
queue-properties:
# Pub/Sub properties for Rule Engine subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
rule-engine: "${TB_QUEUE_PUBSUB_RE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# Pub/Sub properties for Core subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
core: "${TB_QUEUE_PUBSUB_CORE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# Pub/Sub properties for Transport API subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
transport-api: "${TB_QUEUE_PUBSUB_TA_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# Pub/Sub properties for Version Control subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
notifications: "${TB_QUEUE_PUBSUB_NOTIFICATIONS_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# PubSub queue properties
js-executor: "${TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# Pub/Sub properties for Transport Api subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
version-control: "${TB_QUEUE_PUBSUB_VC_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# Pub/Sub properties for Edge subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
edge: "${TB_QUEUE_PUBSUB_EDGE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
service_bus:
# Azure namespace
namespace_name: "${TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME:YOUR_NAMESPACE_NAME}"
# Azure Service Bus Shared Access Signatures key name
sas_key_name: "${TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME:YOUR_SAS_KEY_NAME}"
# Azure Service Bus Shared Access Signatures key
sas_key: "${TB_QUEUE_SERVICE_BUS_SAS_KEY:YOUR_SAS_KEY}"
# Number of messages per a consumer
max_messages: "${TB_QUEUE_SERVICE_BUS_MAX_MESSAGES:1000}"
queue-properties:
# Azure Service Bus properties for Rule Engine queues
rule-engine: "${TB_QUEUE_SERVICE_BUS_RE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus properties for Core queues
core: "${TB_QUEUE_SERVICE_BUS_CORE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus properties for Transport Api queues
transport-api: "${TB_QUEUE_SERVICE_BUS_TA_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus properties for Notification queues
notifications: "${TB_QUEUE_SERVICE_BUS_NOTIFICATIONS_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus queue properties
js-executor: "${TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus properties for Version Control queues
version-control: "${TB_QUEUE_SERVICE_BUS_VC_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus properties for Edge queues
edge: "${TB_QUEUE_SERVICE_BUS_EDGE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
rabbitmq:
# By default empty
exchange_name: "${TB_QUEUE_RABBIT_MQ_EXCHANGE_NAME:}"
# RabbitMQ host used to establish connection
host: "${TB_QUEUE_RABBIT_MQ_HOST:localhost}"
# RabbitMQ host used to establish a connection
port: "${TB_QUEUE_RABBIT_MQ_PORT:5672}"
# Virtual hosts provide logical grouping and separation of resources
virtual_host: "${TB_QUEUE_RABBIT_MQ_VIRTUAL_HOST:/}"
# Username for RabbitMQ user account
username: "${TB_QUEUE_RABBIT_MQ_USERNAME:YOUR_USERNAME}"
# User password for RabbitMQ user account
password: "${TB_QUEUE_RABBIT_MQ_PASSWORD:YOUR_PASSWORD}"
# Network connection between clients and RabbitMQ nodes can fail. RabbitMQ Java client supports automatic recovery of connections and topology (queues, exchanges, bindings, and consumers)
automatic_recovery_enabled: "${TB_QUEUE_RABBIT_MQ_AUTOMATIC_RECOVERY_ENABLED:false}"
# The connection timeout for the RabbitMQ connection factory
connection_timeout: "${TB_QUEUE_RABBIT_MQ_CONNECTION_TIMEOUT:60000}"
# RabbitMQ has a timeout for connection handshake. When clients run in heavily constrained environments, it may be necessary to increase the timeout
handshake_timeout: "${TB_QUEUE_RABBIT_MQ_HANDSHAKE_TIMEOUT:10000}"
# The maximum number of messages returned in a single call of doPoll() method
max_poll_messages: "${TB_QUEUE_RABBIT_MQ_MAX_POLL_MESSAGES:1}"
queue-properties:
# RabbitMQ properties for Rule Engine queues
rule-engine: "${TB_QUEUE_RABBIT_MQ_RE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ properties for Core queues
core: "${TB_QUEUE_RABBIT_MQ_CORE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ properties for Transport API queues
transport-api: "${TB_QUEUE_RABBIT_MQ_TA_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ properties for Notification queues
notifications: "${TB_QUEUE_RABBIT_MQ_NOTIFICATIONS_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ queue properties
js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ properties for Version Control queues
version-control: "${TB_QUEUE_RABBIT_MQ_VC_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ properties for Edge queues
edge: "${TB_QUEUE_RABBIT_MQ_EDGE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
partitions:
hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256
transport_api:
@ -1764,7 +1646,7 @@ queue:
# Interval in milliseconds to poll api response from transport microservices
response_poll_interval: "${TB_QUEUE_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}"
core:
# Default topic name of Kafka, RabbitMQ, etc. queue
# Default topic name
topic: "${TB_QUEUE_CORE_TOPIC:tb_core}"
# Interval in milliseconds to poll messages by Core microservices
poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
@ -1781,7 +1663,7 @@ queue:
pack-interval-ms: "${TB_QUEUE_CORE_OTA_PACK_INTERVAL_MS:60000}"
# The size of OTA updates notifications fetched from the queue. The queue stores pairs of firmware and device ids
pack-size: "${TB_QUEUE_CORE_OTA_PACK_SIZE:100}"
# Stats topic name for queue Kafka, RabbitMQ, etc.
# Stats topic name
usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}"
stats:
# Enable/disable statistics for Core microservices
@ -1812,7 +1694,7 @@ queue:
print-interval-ms: "${TB_HOUSEKEEPER_STATS_PRINT_INTERVAL_MS:60000}"
vc:
# Default topic name for Kafka, RabbitMQ, etc.
# Default topic name
topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}"
# Number of partitions to associate with this queue. Used for scaling the number of messages that can be processed in parallel
partitions: "${TB_QUEUE_VC_PARTITIONS:10}"
@ -1822,7 +1704,7 @@ queue:
pack-processing-timeout: "${TB_QUEUE_VC_PACK_PROCESSING_TIMEOUT_MS:180000}"
# Timeout for a request to VC-executor (for a request for the version of the entity, for a commit charge, etc.)
request-timeout: "${TB_QUEUE_VC_REQUEST_TIMEOUT:180000}"
# Queue settings for Kafka, RabbitMQ, etc. Limit for single message size
# Limit for single queue message size
msg-chunk-size: "${TB_QUEUE_VC_MSG_CHUNK_SIZE:250000}"
js:
# JS Eval request topic
@ -1863,7 +1745,7 @@ queue:
# Interval in milliseconds to poll messages
poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
edge:
# Default topic name for Kafka, RabbitMQ, etc.
# Default topic name
topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}"
# Amount of partitions used by Edge services
partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}"

11
application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java

@ -24,6 +24,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ -32,6 +33,10 @@ import static org.assertj.core.api.Assertions.assertThat;
@ContextConfiguration(classes = CaffeineCacheDefaultConfigurationTest.class, loader = SpringBootContextLoader.class)
@ComponentScan({"org.thingsboard.server.cache"})
@EnableConfigurationProperties
@TestPropertySource(properties = {
"cache.specs.edgeSessions.timeToLiveInMinutes=1",
"cache.specs.relatedEdges.maxSize=1"
})
@Slf4j
public class CaffeineCacheDefaultConfigurationTest {
@ -46,11 +51,7 @@ public class CaffeineCacheDefaultConfigurationTest {
SoftAssertions softly = new SoftAssertions();
cacheSpecsMap.getSpecs().forEach((name, cacheSpecs) -> {
softly.assertThat(name).as("cache name").isNotEmpty();
if (name.equals("edgeSessions")) {
softly.assertThat(cacheSpecs.getTimeToLiveInMinutes()).as("cache %s time to live", name).isEqualTo(0);
} else {
softly.assertThat(cacheSpecs.getTimeToLiveInMinutes()).as("cache %s time to live", name).isGreaterThan(0);
}
softly.assertThat(cacheSpecs.getTimeToLiveInMinutes()).as("cache %s time to live", name).isGreaterThan(0);
softly.assertThat(cacheSpecs.getMaxSize()).as("cache %s max size", name).isGreaterThan(0);
});
softly.assertAll();

31
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -149,10 +149,6 @@ import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.ArrayList;
@ -1053,33 +1049,6 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus());
}
protected static <T> T getFieldValue(Object target, String fieldName) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(target);
}
protected static void setStaticFieldValue(Class<?> targetCls, String fieldName, Object value) throws Exception {
Field field = targetCls.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(null, value);
}
protected static void setStaticFinalFieldValue(Class<?> targetCls, String fieldName, Object value) throws Exception {
Field field = targetCls.getDeclaredField(fieldName);
field.setAccessible(true);
// Get the VarHandle for the 'modifiers' field in the Field class
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
VarHandle modifiersHandle = lookup.findVarHandle(Field.class, "modifiers", int.class);
// Remove the final modifier from the field
int currentModifiers = field.getModifiers();
modifiersHandle.set(field, currentModifiers & ~Modifier.FINAL);
// Set the new value
field.set(null, value);
}
protected int getDeviceActorSubscriptionCount(DeviceId deviceId, FeatureType featureType) {
DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId);
Map<UUID, SessionInfo> subscriptions = (Map<UUID, SessionInfo>) ReflectionTestUtils.getField(processor, getMapName(featureType));

2
application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java

@ -359,7 +359,7 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
transportConfiguration.setBootstrapServerUpdateEnable(true);
TelemetryMappingConfiguration observeAttrConfiguration =
JacksonUtil.fromString(AbstractLwM2MIntegrationTest.OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class);
JacksonUtil.fromString(AbstractLwM2MIntegrationTest.TELEMETRY_WITHOUT_OBSERVE, TelemetryMappingConfiguration.class);
transportConfiguration.setObserveAttr(observeAttrConfiguration);
List<LwM2MBootstrapServerCredential> bootstrap = new ArrayList<>();

75
application/src/test/java/org/thingsboard/server/edge/RelationEdgeTest.java

@ -21,8 +21,10 @@ import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.service.DaoSqlTest;
@ -31,6 +33,8 @@ import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import java.util.UUID;
@DaoSqlTest
public class RelationEdgeTest extends AbstractEdgeTest {
@ -58,13 +62,14 @@ public class RelationEdgeTest extends AbstractEdgeTest {
// delete relation
edgeImitator.expectMessageAmount(1);
var deletedRelation = doDelete("/api/v2/relation?" +
"fromId=" + relation.getFrom().getId().toString() +
"&fromType=" + relation.getFrom().getEntityType().name() +
"&relationType=" + relation.getType() +
"&relationTypeGroup=" + relation.getTypeGroup().name() +
"&toId=" + relation.getTo().getId().toString() +
"&toType=" + relation.getTo().getEntityType().name(), EntityRelation.class);
String deleteUrl = String.format("/api/v2/relation?fromId=%s&fromType=%s&relationType=%s&relationTypeGroup=%s&toId=%s&toType=%s",
device.getId().toString(), EntityType.DEVICE.name(), "test",
RelationTypeGroup.COMMON.name(), asset.getId().toString(), EntityType.ASSET.name()
);
var deletedRelation = doDelete(deleteUrl, EntityRelation.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
@ -94,13 +99,13 @@ public class RelationEdgeTest extends AbstractEdgeTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
EntityRelation relation = doGet("/api/relation?" +
"&fromId=" + device2.getUuidId() +
"&fromType=" + device2.getId().getEntityType().name() +
"&relationType=" + "test" +
"&relationTypeGroup=" + RelationTypeGroup.COMMON.name() +
"&toId=" + device1.getUuidId() +
"&toType=" + device1.getId().getEntityType().name(), EntityRelation.class);
String getUrl = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&relationTypeGroup=%s&toId=%s&toType=%s",
device2.getUuidId(), EntityType.DEVICE.name(), "test",
RelationTypeGroup.COMMON.name(), device1.getUuidId(), EntityType.DEVICE.name()
);
var relation = doGet(getUrl, EntityRelation.class);
Assert.assertNotNull(relation);
}
@ -154,6 +159,47 @@ public class RelationEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, relationUpdateMsg.getMsgType());
}
@Test
public void testRelationFromEdgeToDevice() throws Exception {
// create relation
Device device = findDeviceByName("Edge Device 1");
EdgeId edgeId = new EdgeId(new UUID(edgeImitator.getConfiguration().getEdgeIdMSB(), edgeImitator.getConfiguration().getEdgeIdLSB()));
EntityRelation relation = new EntityRelation();
relation.setType("test");
relation.setFrom(edgeId);
relation.setTo(device.getId());
relation.setTypeGroup(RelationTypeGroup.COMMON);
edgeImitator.expectMessageAmount(1);
relation = doPost("/api/v2/relation", relation, EntityRelation.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage;
EntityRelation entityRelation = JacksonUtil.fromString(relationUpdateMsg.getEntity(), EntityRelation.class, true);
Assert.assertNotNull(entityRelation);
Assert.assertEquals(relation, entityRelation);
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, relationUpdateMsg.getMsgType());
// delete relation
edgeImitator.expectMessageAmount(1);
String deleteUrl = String.format("/api/v2/relation?fromId=%s&fromType=%s&relationType=%s&relationTypeGroup=%s&toId=%s&toType=%s",
edgeId, EntityType.EDGE.name(), "test",
RelationTypeGroup.COMMON.name(), device.getId().toString(), EntityType.DEVICE.name()
);
var deletedRelation = doDelete(deleteUrl, EntityRelation.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
relationUpdateMsg = (RelationUpdateMsg) latestMessage;
entityRelation = JacksonUtil.fromString(relationUpdateMsg.getEntity(), EntityRelation.class, true);
Assert.assertNotNull(entityRelation);
Assert.assertEquals(deletedRelation, entityRelation);
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, relationUpdateMsg.getMsgType());
}
private EntityRelation buildEntityRelationForUplinkMsg(DeviceId deviceId1, DeviceId deviceId2) {
EntityRelation relation = new EntityRelation();
relation.setType("test");
@ -163,4 +209,5 @@ public class RelationEdgeTest extends AbstractEdgeTest {
relation.setAdditionalInfo(TextNode.valueOf("{}"));
return relation;
}
}

20
application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java

@ -39,7 +39,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -50,8 +49,8 @@ public class RuleChainEdgeTest extends AbstractEdgeTest {
@Test
public void testRuleChains() throws Exception {
// create rule chain
edgeImitator.expectMessageAmount(2);
// create rule chain: 2 messages from create rule chain, 2 messages from load metadata
edgeImitator.expectMessageAmount(4);
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge Test Rule Chain");
ruleChain.setType(RuleChainType.EDGE);
@ -60,15 +59,21 @@ public class RuleChainEdgeTest extends AbstractEdgeTest {
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
createRuleChainMetadata(savedRuleChain);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get();
List<RuleChainUpdateMsg> ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class);
Assert.assertEquals(2, ruleChainUpdateMsgs.size());
List<RuleChainMetadataUpdateMsg> ruleChainMetadataUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainMetadataUpdateMsg.class);
Assert.assertEquals(2, ruleChainMetadataUpdateMsgs.size());
RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgs.get(0);
RuleChain ruleChainMsg = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true);
Assert.assertNotNull(ruleChainMsg);
Assert.assertTrue(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType()) ||
UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType()));
Assert.assertEquals(savedRuleChain.getId(), ruleChainMsg.getId());
Assert.assertEquals(savedRuleChain.getName(), ruleChainMsg.getName());
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ruleChainMetadataUpdateMsgs.get(0);
RuleChainMetaData ruleChainMetaData = JacksonUtil.fromString(ruleChainMetadataUpdateMsg.getEntity(), RuleChainMetaData.class, true);
Assert.assertNotNull(ruleChainMetaData);
Assert.assertEquals(ruleChainMetaData.getRuleChainId(), savedRuleChain.getId());
testRuleChainMetadataRequestMsg(savedRuleChain.getId());
@ -77,7 +82,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest {
doDelete("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
@ -262,4 +267,5 @@ public class RuleChainEdgeTest extends AbstractEdgeTest {
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages(5));
}
}

5
application/src/test/java/org/thingsboard/server/service/script/AbstractTbelInvokeTest.java

@ -16,8 +16,10 @@
package org.thingsboard.server.service.script;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.ScriptType;
import org.thingsboard.script.api.tbel.DefaultTbelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.controller.AbstractControllerTest;
@ -28,7 +30,8 @@ import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST;
public abstract class AbstractTbelInvokeTest extends AbstractControllerTest {
@SpringBootTest(classes = DefaultTbelInvokeService.class)
public abstract class AbstractTbelInvokeTest {
@Autowired
protected TbelInvokeService invokeService;

82
application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.service.script;
import org.junit.jupiter.api.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.tbel.TbDate;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@ -34,7 +33,6 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
@DaoSqlTest
class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
private String decoderStr;
@ -1469,6 +1467,26 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected, actual);
}
// hexToBytes List or Array
@Test
public void hexToBytes_Test() throws ExecutionException, InterruptedException {
msgStr = "{}";
decoderStr = """
var validInputList = "0x01752B0367FA000500010488FFFFFFFFFFFFFFFF33";
var validInputArray = "AABBCCDDEE";
return {
"hexToBytes": hexToBytes(validInputList),
"hexToBytesArray": hexToBytesArray(validInputArray),
}
""";
Object actual = invokeScript(evalScript(decoderStr), msgStr);
LinkedHashMap<String, Object> expected = new LinkedHashMap<>();
expected.put("hexToBytes", bytesToList(new byte[]{1, 117, 43, 3, 103, -6, 0, 5, 0, 1, 4, -120, -1, -1, -1, -1, -1, -1, -1, -1, 51}));
// [-86, -69, -52, -35, -18] == new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE}
expected.put("hexToBytesArray", bytesToList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE}));
assertEquals( expected, actual);
}
// parseBinaryArray
@Test
public void parseBinaryArray_Test() throws ExecutionException, InterruptedException {
@ -1697,6 +1715,62 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(4.2d, actual);
}
// Bitwise Operations
@Test
public void bitwiseOperationsBoolean_Test() throws ExecutionException, InterruptedException {
msgStr = "{}";
decoderStr = """
var x = true;
var y = false;
return {
"andResult": x & y,
"orResult": x | y,
"xorResult": x ^ y,
"leftShift": x << y,
"rightShift": x >> y,
"rightUnShift": x >>> y
}
""";
LinkedHashMap<String, Object> expected = new LinkedHashMap<>();
expected.put("andResult", 0);
expected.put("orResult", 1);
expected.put("xorResult", 1);
expected.put("leftShift", 1);
expected.put("rightShift", 1);
expected.put("rightUnShift", 1);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected, actual);
}
@Test
public void bitwiseOperationsMix_Test() throws ExecutionException, InterruptedException {
msgStr = "{}";
decoderStr = """
var x = true;
var y = false;
var i = 10;
var b = -14;
var l = 9223372036854775807;
return {
"andResult": x & b,
"orResult": i | y,
"xorResult": i ^ l,
"leftShift": l << i,
"rightShift": l >> b,
"rightUnShift": i >>> x
}
""";
LinkedHashMap<String, Object> expected = new LinkedHashMap<>();
expected.put("andResult", 0);
expected.put("orResult", 10);
expected.put("xorResult", 9223372036854775797L);
expected.put("leftShift", -1024L);
expected.put("rightShift", 8191L);
expected.put("rightUnShift", 5);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected, actual);
}
// base64
@Test
public void base64_Test() throws ExecutionException, InterruptedException {
@ -1705,13 +1779,15 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
return {
"base64ToHex": base64ToHex("Kkk="),
"bytesToBase64": bytesToBase64([42, 73]),
"base64ToBytes": base64ToBytes("Kkk=")
"base64ToBytes": base64ToBytes("Kkk="),
"base64ToBytesList": base64ToBytesList("AQIDBAU=")
}
""";
LinkedHashMap<String, Object> expected = new LinkedHashMap<>();
expected.put("base64ToHex", "2A49");
expected.put("bytesToBase64", "Kkk=");
expected.put("base64ToBytes", bytesToList(new byte[]{42, 73}));
expected.put("base64ToBytesList", bytesToList(new byte[]{1, 2, 3, 4, 5}));
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected, actual);
}

19
application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java

@ -22,9 +22,9 @@ import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.tbel.TbelScript;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.io.Serializable;
import java.util.ArrayList;
@ -38,7 +38,6 @@ import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DaoSqlTest
@TestPropertySource(properties = {
"tbel.max_script_body_size=100",
"tbel.max_total_args_size=50",
@ -120,9 +119,9 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
scriptsIds.add(scriptId);
}
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Map<String, TbelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
Map<UUID, String> scriptIdToHash = (Map<UUID, String>) ReflectionTestUtils.getField(invokeService, "scriptIdToHash");
Map<String, TbelScript> scriptMap = (Map<String, TbelScript>) ReflectionTestUtils.getField(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = (Cache<String, Serializable>) ReflectionTestUtils.getField(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
@ -140,9 +139,9 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
scriptsIds.add(scriptId);
}
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Map<String, TbelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
Map<UUID, String> scriptIdToHash = (Map<UUID, String>) ReflectionTestUtils.getField(invokeService, "scriptIdToHash");
Map<String, TbelScript> scriptMap = (Map<String, TbelScript>) ReflectionTestUtils.getField(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = (Cache<String, Serializable>) ReflectionTestUtils.getField(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
for (int i = 0; i < 9; i++) {
@ -163,8 +162,8 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
@Ignore("This test is based on assumption that Caffeine cache is LRU based but in fact it is based on " +
"Tiny LFU which is the cause that the tests fail sometime: https://arxiv.org/pdf/1512.00727.pdf")
public void whenCompiledScriptsCacheIsTooBig_thenRemoveRarelyUsedScripts() throws Exception {
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
Map<UUID, String> scriptIdToHash = (Map<UUID, String>) ReflectionTestUtils.getField(invokeService, "scriptIdToHash");
Cache<String, Serializable> compiledScriptsCache = (Cache<String, Serializable>) ReflectionTestUtils.getField(invokeService, "compiledScriptsCache");
List<UUID> scriptsIds = new ArrayList<>();
for (int i = 0; i < 110; i++) { // tbel.compiled_scripts_cache_size = 100

17
application/src/test/java/org/thingsboard/server/service/ttl/AlarmsCleanUpServiceTest.java

@ -15,7 +15,7 @@
*/
package org.thingsboard.server.service.ttl;
import org.junit.BeforeClass;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@ -49,19 +50,19 @@ import static org.mockito.Mockito.verify;
})
public class AlarmsCleanUpServiceTest extends AbstractControllerTest {
@Autowired
@SpyBean
private AlarmsCleanUpService alarmsCleanUpService;
@SpyBean
private AlarmService alarmService;
@Autowired
private AlarmDao alarmDao;
private static Logger cleanUpServiceLogger;
private Logger cleanUpServiceLoggerSpy;
@BeforeClass
public static void before() throws Exception {
cleanUpServiceLogger = Mockito.spy(LoggerFactory.getLogger(AlarmsCleanUpService.class));
setStaticFinalFieldValue(AlarmsCleanUpService.class, "log", cleanUpServiceLogger);
@Before
public void beforeEach() throws Exception {
cleanUpServiceLoggerSpy = Mockito.spy(LoggerFactory.getLogger(AlarmsCleanUpService.class));
willReturn(cleanUpServiceLoggerSpy).given(alarmsCleanUpService).getLogger();
}
@Test
@ -110,7 +111,7 @@ public class AlarmsCleanUpServiceTest extends AbstractControllerTest {
verify(alarmService, never()).delAlarm(eq(tenantId), eq(freshAlarm), eq(false));
}
verify(cleanUpServiceLogger).info(startsWith("Removed {} outdated alarm"), eq((long) count), eq(tenantId), any());
verify(cleanUpServiceLoggerSpy).info(startsWith("Removed {} outdated alarm"), eq((long) count), eq(tenantId), any());
}
}

8
application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java

@ -54,6 +54,14 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg
protected final byte[] EMPTY_PAYLOAD = new byte[0];
protected CoapTestClient client;
protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
protected static final String PAYLOAD_VALUES_STR_01 = "{\"key2\":\"value2\", \"key3\":false, \"key4\": 4.0, \"key5\": 5," +
" \"key6\": {\"someNumber_02\": 52, \"someArray_02\": [1,2,3,4], \"someNestedObject_02\": {\"key_02\": \"value_02\"}}}";
protected void processBeforeTest() throws Exception {
loginTenantAdmin();
}
protected void processAfterTest() throws Exception {
if (client != null) {

2
application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java

@ -63,8 +63,6 @@ import static org.thingsboard.server.common.data.query.EntityKeyType.SHARED_ATTR
@DaoSqlTest
public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
private static final List<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5");
private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";

291
application/src/test/java/org/thingsboard/server/transport/coap/security/AbstractCoapSecurityIntegrationTest.java

@ -0,0 +1,291 @@
/**
* Copyright © 2016-2024 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.transport.coap.security;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.junit.Assert;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
import org.thingsboard.server.transport.coap.x509.CertPrivateKey;
import org.thingsboard.server.transport.coap.x509.CoapClientX509Test;
import org.thingsboard.server.transport.coap.CoapTestConfigProperties;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@TestPropertySource(properties = {
"coap.enabled=true",
"coap.dtls.enabled=true",
"coap.dtls.credentials.pem.cert_file=coap/credentials/server/cert.pem",
"device.connectivity.coaps.enabled=true",
"service.integrations.supported=ALL",
"transport.coap.enabled=true",
})
public abstract class AbstractCoapSecurityIntegrationTest extends AbstractCoapIntegrationTest {
private static final String COAPS_BASE_URL = "coaps://localhost:5684/api/v1/";
protected final String CREDENTIALS_PATH = "coap/credentials/";
protected final String CREDENTIALS_PATH_CLIENT = CREDENTIALS_PATH + "client/";
protected final String CREDENTIALS_PATH_CLIENT_CERT_PEM = CREDENTIALS_PATH_CLIENT + "cert.pem";
protected final String CREDENTIALS_PATH_CLIENT_KEY_PEM = CREDENTIALS_PATH_CLIENT + "key.pem";
protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name")
protected final PrivateKey clientPrivateKeyFromCertTrustNo;
protected static final String CLIENT_JKS_FOR_TEST = "coapclientTest";
protected static final String CLIENT_STORE_PWD = "client_ks_password";
protected static final String CLIENT_ALIAS_CERT_TRUST_NO = "client_alias_trust_no";
protected AbstractCoapSecurityIntegrationTest() {
try {
// Get certificates from key store
char[] clientKeyStorePwd = CLIENT_STORE_PWD.toCharArray();
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream clientKeyStoreFile =
this.getClass().getClassLoader().
getResourceAsStream(CREDENTIALS_PATH + CLIENT_JKS_FOR_TEST + ".jks")) {
clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd);
}
// No trust
clientPrivateKeyFromCertTrustNo = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST_NO, clientKeyStorePwd);
clientX509CertTrustNo = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST_NO);
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
protected Device createDeviceWithX509(String deviceName, DeviceProfileId deviceProfileId, X509Certificate clientX509Cert) throws Exception {
Device device = new Device();
device.setName(deviceName);
device.setType(deviceName);
device.setDeviceProfileId(deviceProfileId);
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE);
String pemFormatCert = CertPrivateKey.convertCertToPEM(clientX509Cert);
deviceCredentials.setCredentialsValue(pemFormatCert);
SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, deviceCredentials);
Device deviceX509 = readResponse(doPost("/api/device-with-credentials", saveRequest)
.andExpect(status().isOk()), Device.class);
DeviceCredentials savedDeviceCredentials =
doGet("/api/device/" + deviceX509.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertNotNull(savedDeviceCredentials);
Assert.assertNotNull(savedDeviceCredentials.getId());
Assert.assertEquals(deviceX509.getId(), savedDeviceCredentials.getDeviceId());
Assert.assertEquals(DeviceCredentialsType.X509_CERTIFICATE, savedDeviceCredentials.getCredentialsType());
accessToken = savedDeviceCredentials.getCredentialsId();
assertNotNull(accessToken);
return deviceX509;
}
protected void clientX509FromJksUpdateAttributesTest() throws Exception {
CertPrivateKey certPrivateKey = new CertPrivateKey(clientX509CertTrustNo, clientPrivateKeyFromCertTrustNo);
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
.coapDeviceType(CoapDeviceType.DEFAULT)
.transportPayloadType(TransportPayloadType.JSON)
.build();
DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties);
assertNotNull(deviceProfile);
CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey,
"CoapX509TrustNo_" + FeatureType.ATTRIBUTES.name(), deviceProfile.getId(), null);
clientX509.disconnect();
}
protected void clientX509FromPathUpdateFeatureTypeTest(FeatureType featureType) throws Exception {
CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM);
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
.coapDeviceType(CoapDeviceType.DEFAULT)
.transportPayloadType(TransportPayloadType.JSON)
.build();
DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties);
assertNotNull(deviceProfile);
CoapClientX509Test clientX509 = clientX509UpdateTest(featureType, certPrivateKey,
"CoapX509TrustNo_" + featureType.name(), deviceProfile.getId(), null);
clientX509.disconnect();
}
protected void twoClientWithSamePortX509FromPathConnectTest() throws Exception {
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
.coapDeviceType(CoapDeviceType.DEFAULT)
.transportPayloadType(TransportPayloadType.JSON)
.build();
DeviceProfile deviceProfile = createCoapDeviceProfile(configProperties);
CertPrivateKey certPrivateKey = new CertPrivateKey(CREDENTIALS_PATH_CLIENT_CERT_PEM, CREDENTIALS_PATH_CLIENT_KEY_PEM);
CertPrivateKey certPrivateKey_01 = new CertPrivateKey(CREDENTIALS_PATH_CLIENT + "cert_01.pem",
CREDENTIALS_PATH_CLIENT + "key_01.pem");
Integer fixedPort = getFreePort();
CoapClientX509Test clientX509 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey,
"CoapX509TrustNo_" + FeatureType.TELEMETRY.name(), deviceProfile.getId(), fixedPort);
clientX509.disconnect();
await("Need to make port " + fixedPort + " free")
.atMost(40, TimeUnit.SECONDS)
.until(() -> isPortAvailable(fixedPort));
CoapClientX509Test clientX509_01 = clientX509UpdateTest(FeatureType.ATTRIBUTES, certPrivateKey_01,
"CoapX509TrustNo_" + FeatureType.TELEMETRY.name() + "_01", deviceProfile.getId(),
fixedPort, PAYLOAD_VALUES_STR_01);
clientX509_01.disconnect();
}
private CoapClientX509Test clientX509UpdateTest(FeatureType featureType, CertPrivateKey certPrivateKey,
String deviceName, DeviceProfileId deviceProfileId, Integer fixedPort) throws Exception {
return clientX509UpdateTest(featureType, certPrivateKey, deviceName, deviceProfileId, fixedPort, null);
}
private CoapClientX509Test clientX509UpdateTest(FeatureType featureType, CertPrivateKey certPrivateKey,
String deviceName, DeviceProfileId deviceProfileId, Integer fixedPort, String payload) throws Exception {
String payloadValuesStr = payload == null ? PAYLOAD_VALUES_STR : payload;
Device deviceX509 = createDeviceWithX509(deviceName, deviceProfileId, certPrivateKey.getCert());
CoapClientX509Test clientX509 = new CoapClientX509Test(certPrivateKey, featureType, COAPS_BASE_URL, fixedPort);
CoapResponse coapResponseX509 = clientX509.postMethod(payloadValuesStr);
assertNotNull(coapResponseX509);
assertEquals(CoAP.ResponseCode.CREATED, coapResponseX509.getCode());
if (FeatureType.ATTRIBUTES.equals(featureType)) {
DeviceId deviceId = deviceX509.getId();
JsonNode expectedNode = JacksonUtil.toJsonNode(payloadValuesStr);
List<String> expectedKeys = getKeysFromNode(expectedNode);
List<String> actualKeys = getActualKeysList(deviceId, expectedKeys, "attributes/CLIENT_SCOPE");
assertNotNull(actualKeys);
Set<String> actualKeySet = new HashSet<>(actualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys);
assertEquals(expectedKeySet, actualKeySet);
String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet, "attributes/CLIENT_SCOPE");
List<Map<String, Object>> actualValues = doGetAsyncTyped(getAttributesValuesUrl, new TypeReference<>() {
});
assertValuesList(actualValues, expectedNode);
}
return clientX509;
}
private List<String> getActualKeysList(DeviceId deviceId, List<String> expectedKeys, String apiSuffix) throws Exception {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 5000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/" + apiSuffix, new TypeReference<>() {
});
if (actualKeys.size() == expectedKeys.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
return actualKeys;
}
private String getAttributesValuesUrl(DeviceId deviceId, Set<String> actualKeySet, String apiSuffix) {
return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/" + apiSuffix + "?keys=" + String.join(",", actualKeySet);
}
private List getKeysFromNode(JsonNode jNode) {
List<String> jKeys = new ArrayList<>();
Iterator<String> fieldNames = jNode.fieldNames();
while (fieldNames.hasNext()) {
jKeys.add(fieldNames.next());
}
return jKeys;
}
protected void assertValuesList(List<Map<String, Object>> actualValues, JsonNode expectedValues) {
assertTrue(actualValues.size() > 0);
assertEquals(expectedValues.size(), actualValues.size());
for (Map<String, Object> map : actualValues) {
String key = (String) map.get("key");
Object actualValue = map.get("value");
assertTrue(expectedValues.has(key));
JsonNode expectedValue = expectedValues.get(key);
assertExpectedActualValue(expectedValue, actualValue);
}
}
protected void assertExpectedActualValue(JsonNode expectedValue, Object actualValue) {
switch (expectedValue.getNodeType()) {
case STRING:
assertEquals(expectedValue.asText(), actualValue);
break;
case NUMBER:
if (expectedValue.isInt()) {
assertEquals(expectedValue.asInt(), actualValue);
} else if (expectedValue.isLong()) {
assertEquals(expectedValue.asLong(), actualValue);
} else if (expectedValue.isFloat() || expectedValue.isDouble()) {
assertEquals(expectedValue.asDouble(), actualValue);
}
break;
case BOOLEAN:
assertEquals(expectedValue.asBoolean(), actualValue);
break;
case ARRAY:
case OBJECT:
expectedValue.toString().equals(JacksonUtil.toString(actualValue));
break;
default:
break;
}
}
private static int getFreePort() throws IOException {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
}
}
private static boolean isPortAvailable(int port) {
try (ServerSocket serverSocket = new ServerSocket(port)) {
serverSocket.setReuseAddress(true);
return true;
} catch (IOException e) {
return false;
}
}
}

44
application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityJksIntegrationTest.java

@ -0,0 +1,44 @@
/**
* Copyright © 2016-2024 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.transport.coap.security.sql;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.coap.security.AbstractCoapSecurityIntegrationTest;
@Slf4j
@DaoSqlTest
@TestPropertySource(properties = {
"coap.dtls.credentials.type=KEYSTORE",
"coap.dtls.credentials.keystore.store_file=coap/credentials/coapserverTest.jks",
"coap.dtls.credentials.keystore.key_password=server_ks_password",
"coap.dtls.credentials.keystore.key_alias=server",
})
public class CoapClientX509SecurityJksIntegrationTest extends AbstractCoapSecurityIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest();
}
@Test
public void testX509NoTrustFromJksConnectCoapSuccessUpdateAttributesSuccess() throws Exception {
clientX509FromJksUpdateAttributesTest();
}
}

44
application/src/test/java/org/thingsboard/server/transport/coap/security/sql/CoapClientX509SecurityPemIntegrationTest.java

@ -0,0 +1,44 @@
/**
* Copyright © 2016-2024 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.transport.coap.security.sql;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.coap.security.AbstractCoapSecurityIntegrationTest;
@Slf4j
@DaoSqlTest
public class CoapClientX509SecurityPemIntegrationTest extends AbstractCoapSecurityIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest();
}
@Test
public void testX509NoTrustFromPathConnectCoapSuccessUpdateAttributesSuccess() throws Exception {
clientX509FromPathUpdateFeatureTypeTest(FeatureType.ATTRIBUTES);
}
@Test
public void testX509NoTrustFromPathConnectCoapSuccessUpdateTelemetrySuccess() throws Exception {
clientX509FromPathUpdateFeatureTypeTest(FeatureType.TELEMETRY);
} @Test
public void testTwoDevicesWithSamePortX509NoTrustFromPathConnectCoapSuccess() throws Exception {
twoClientWithSamePortX509FromPathConnectTest();
}
}

3
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java

@ -44,9 +44,6 @@ import static org.junit.Assert.assertTrue;
@DaoSqlTest
public class CoapAttributesIntegrationTest extends AbstractCoapIntegrationTest {
private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
@Before
public void beforeTest() throws Exception {
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()

3
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java

@ -40,9 +40,6 @@ import static org.junit.Assert.assertNotNull;
@Slf4j
public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoapIntegrationTest {
private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
@Before
public void beforeTest() throws Exception {
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()

82
application/src/test/java/org/thingsboard/server/transport/coap/x509/CertPrivateKey.java

@ -0,0 +1,82 @@
/**
* Copyright © 2016-2024 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.transport.coap.x509;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.thingsboard.common.util.SslUtil;
import java.io.File;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.List;
public class CertPrivateKey {
private final X509Certificate cert;
private PrivateKey privateKey;
public CertPrivateKey(String certFilePathPem, String keyFilePathPem) throws Exception {
List<X509Certificate> certs = SslUtil.readCertFile(fileRead(certFilePathPem));
this.cert = certs.get(0);
this.privateKey = SslUtil.readPrivateKey(fileRead(keyFilePathPem), null);
if (this.privateKey instanceof BCECPrivateKey) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(this.privateKey.getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("EC");
this.privateKey = keyFactory.generatePrivate(keySpec);
}
if (!(this.privateKey instanceof ECPrivateKey)) {
throw new RuntimeException("Private key generation must be of type java.security.interfaces.ECPrivateKey, which is used in the standard Java API!");
}
}
public CertPrivateKey(X509Certificate cert, PrivateKey privateKey) {
this.cert = cert;
this.privateKey = privateKey;
}
public X509Certificate getCert() {
return this.cert;
}
public PrivateKey getPrivateKey() {
return this.privateKey;
}
private String fileRead(String fileName) throws IOException {
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource(fileName).getFile());
return FileUtils.readFileToString(file, "UTF-8");
}
public static String convertCertToPEM(X509Certificate certificate) throws Exception {
StringBuilder pemBuilder = new StringBuilder();
pemBuilder.append("-----BEGIN CERTIFICATE-----\n");
// Copy cert to Base64
String base64EncodedCert = Base64.getEncoder().encodeToString(certificate.getEncoded());
int index = 0;
while (index < base64EncodedCert.length()) {
pemBuilder.append(base64EncodedCert, index, Math.min(index + 64, base64EncodedCert.length()));
pemBuilder.append("\n");
index += 64;
}
pemBuilder.append("-----END CERTIFICATE-----\n");
return pemBuilder.toString();
}
}

239
application/src/test/java/org/thingsboard/server/transport/coap/x509/CoapClientX509Test.java

@ -0,0 +1,239 @@
/**
* Copyright © 2016-2024 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.transport.coap.x509;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.config.CoapConfig;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.config.ValueException;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConfig.SignatureAndHashAlgorithmsDefinition;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm;
import org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.HashAlgorithm;
import org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SignatureAlgorithm;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.californium.scandium.dtls.x509.CertificateProvider;
import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.coap.CoapTestCallback;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.eclipse.californium.core.config.CoapConfig.DEFAULT_BLOCKWISE_STATUS_LIFETIME_IN_SECONDS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_AUTO_HANDSHAKE_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CIPHER_SUITES;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_FRAGMENTED_HANDSHAKE_MESSAGE_LENGTH;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_PENDING_HANDSHAKE_RESULT_JOBS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_RETRANSMISSIONS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_RETRANSMISSION_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECEIVE_BUFFER_SIZE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_SIGNATURE_AND_HASH_ALGORITHMS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_USE_HELLO_VERIFY_REQUEST;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_USE_MULTI_HANDSHAKE_MESSAGE_RECORDS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.CLIENT_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.MODULE;
import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_ECDSA;
import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA256_WITH_RSA;
import static org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm.SHA384_WITH_ECDSA;
@Slf4j
public class CoapClientX509Test {
private static final long CLIENT_REQUEST_TIMEOUT = 60000L;
private final CoapClient clientX509;
private final DTLSConnector dtlsConnector;
private final Configuration config;
private final CertPrivateKey certPrivateKey;
private final String coapsBaseUrl;
@Getter
private CoAP.Type type = CoAP.Type.CON;
public CoapClientX509Test(CertPrivateKey certPrivateKey, FeatureType featureType, String coapsBaseUrl, Integer fixedPort) {
this.certPrivateKey = certPrivateKey;
this.coapsBaseUrl = coapsBaseUrl;
this.config = createConfiguration();
this.dtlsConnector = createDTLSConnector(fixedPort);
this.clientX509 = createClient(getFeatureTokenUrl(featureType));
}
public void disconnect() {
if (clientX509 != null) {
clientX509.shutdown();
}
}
public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException {
return this.postMethod(requestBody.getBytes());
}
public CoapResponse postMethod(byte[] requestBodyBytes) throws ConnectorException, IOException {
return clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(requestBodyBytes, MediaTypeRegistry.APPLICATION_JSON);
}
public void postMethod(CoapHandler handler, String payload, int format) {
clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(handler, payload, format);
}
public void postMethod(CoapHandler handler, byte[] payload) {
clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(handler, payload, MediaTypeRegistry.APPLICATION_JSON);
}
public void postMethod(CoapHandler handler, byte[] payload, int format) {
clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).post(handler, payload, format);
}
public CoapResponse getMethod() throws ConnectorException, IOException {
return clientX509.setTimeout(CLIENT_REQUEST_TIMEOUT).get();
}
public CoapObserveRelation getObserveRelation(CoapTestCallback callback) {
return getObserveRelation(callback, true);
}
public CoapObserveRelation getObserveRelation(CoapTestCallback callback, boolean confirmable) {
Request request = Request.newGet().setObserve();
request.setType(confirmable ? CoAP.Type.CON : CoAP.Type.NON);
return clientX509.observe(request, callback);
}
public void setURI(String featureTokenUrl) {
if (clientX509 == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
clientX509.setURI(featureTokenUrl);
}
public void setURI(String accessToken, FeatureType featureType) {
if (featureType == null) {
featureType = FeatureType.ATTRIBUTES;
}
setURI(getFeatureTokenUrl(accessToken, featureType));
}
public void useCONs() {
if (clientX509 == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
type = CoAP.Type.CON;
clientX509.useCONs();
}
public void useNONs() {
if (clientX509 == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
type = CoAP.Type.NON;
clientX509.useNONs();
}
private Configuration createConfiguration() {
Configuration clientCoapConfig = new Configuration();
clientCoapConfig.set(CoapConfig.BLOCKWISE_STRICT_BLOCK2_OPTION, true);
clientCoapConfig.set(CoapConfig.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true);
clientCoapConfig.set(CoapConfig.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME_IN_SECONDS, TimeUnit.SECONDS);
clientCoapConfig.set(CoapConfig.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024);
clientCoapConfig.set(CoapConfig.RESPONSE_MATCHING, CoapConfig.MatcherMode.RELAXED);
clientCoapConfig.set(CoapConfig.PREFERRED_BLOCK_SIZE, 1024);
clientCoapConfig.set(CoapConfig.MAX_MESSAGE_SIZE, 1024);
clientCoapConfig.set(DTLS_ROLE, CLIENT_ONLY);
clientCoapConfig.set(DTLS_MAX_RETRANSMISSIONS, 2);
clientCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, 5000, MILLISECONDS);
clientCoapConfig.set(DTLS_MAX_RETRANSMISSION_TIMEOUT, 60000, TimeUnit.MILLISECONDS);
clientCoapConfig.set(DTLS_USE_HELLO_VERIFY_REQUEST, false);
clientCoapConfig.set(DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT, false);
clientCoapConfig.set(DTLS_MAX_FRAGMENTED_HANDSHAKE_MESSAGE_LENGTH, 22490);
clientCoapConfig.set(DTLS_AUTO_HANDSHAKE_TIMEOUT, 100000, TimeUnit.MILLISECONDS);
clientCoapConfig.set(DTLS_MAX_PENDING_HANDSHAKE_RESULT_JOBS, 64);
clientCoapConfig.set(DTLS_USE_MULTI_HANDSHAKE_MESSAGE_RECORDS, false);
clientCoapConfig.set(DTLS_RECEIVE_BUFFER_SIZE, 8192);
clientCoapConfig.setTransient(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY);
SignatureAndHashAlgorithmsDefinition algorithmsDefinition = new SignatureAndHashAlgorithmsDefinition(MODULE + "SIGNATURE_AND_HASH_ALGORITHMS", "List of DTLS signature- and hash-algorithms.\nValues e.g SHA256withECDSA or ED25519.");
SignatureAndHashAlgorithm SHA384_WITH_RSA = new SignatureAndHashAlgorithm(HashAlgorithm.SHA384,
SignatureAlgorithm.RSA);
List<SignatureAndHashAlgorithm> algorithms = null;
try {
algorithms = algorithmsDefinition.checkValue(Arrays.asList(SHA256_WITH_ECDSA, SHA256_WITH_RSA, SHA384_WITH_ECDSA, SHA384_WITH_RSA));
} catch (ValueException e) {
throw new RuntimeException(e);
}
clientCoapConfig.setTransient(DTLS_SIGNATURE_AND_HASH_ALGORITHMS);
clientCoapConfig.set(DTLS_SIGNATURE_AND_HASH_ALGORITHMS, algorithms);
clientCoapConfig.setTransient(DTLS_CIPHER_SUITES);
clientCoapConfig.set(DTLS_CIPHER_SUITES, Arrays.asList(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256));
clientCoapConfig.setTransient(DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT);
clientCoapConfig.set(DTLS_VERIFY_SERVER_CERTIFICATES_SUBJECT, false);
return clientCoapConfig;
}
private DTLSConnector createDTLSConnector(Integer fixedPort) {
try {
// Create DTLS config client
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(this.config);
configBuilder.setAdvancedCertificateVerifier(new TbAdvancedCertificateVerifier());
X509Certificate[] certificateChainClient = new X509Certificate[]{this.certPrivateKey.getCert()};
CertificateProvider certificateProvider = new SingleCertificateProvider(this.certPrivateKey.getPrivateKey(), certificateChainClient, Collections.singletonList(CertificateType.X_509));
configBuilder.setCertificateIdentityProvider(certificateProvider);
if (fixedPort != null) {
InetSocketAddress localAddress = new InetSocketAddress("0.0.0.0", fixedPort);
configBuilder.setAddress(localAddress);
configBuilder.setReuseAddress(true);
}
return new DTLSConnector(configBuilder.build());
} catch (Exception e) {
throw new RuntimeException("", e);
}
}
private CoapClient createClient(String featureTokenUrl) {
CoapClient client = new CoapClient(featureTokenUrl);
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setConnector(dtlsConnector);
client.setEndpoint(builder.build());
return client;
}
public String getFeatureTokenUrl(FeatureType featureType) {
return this.coapsBaseUrl + featureType.name().toLowerCase();
}
public String getFeatureTokenUrl(String token, FeatureType featureType) {
return this.coapsBaseUrl + token + "/" + featureType.name().toLowerCase();
}
}

129
application/src/test/java/org/thingsboard/server/transport/coap/x509/TbAdvancedCertificateVerifier.java

@ -0,0 +1,129 @@
/**
* Copyright © 2016-2024 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.transport.coap.x509;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.scandium.dtls.AlertMessage;
import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription;
import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel;
import org.eclipse.californium.scandium.dtls.CertificateMessage;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.CertificateVerificationResult;
import org.eclipse.californium.scandium.dtls.ConnectionId;
import org.eclipse.californium.scandium.dtls.HandshakeException;
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
import org.eclipse.californium.scandium.util.ServerNames;
import javax.security.auth.x500.X500Principal;
import java.net.InetSocketAddress;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class TbAdvancedCertificateVerifier implements NewAdvancedCertificateVerifier {
private HandshakeResultHandler resultHandler;
/**
* Get the list of supported certificate types in order of preference.
*
* @return the list of supported certificate types.
* @since 3.0 (renamed from getSupportedCertificateType)
*/
@Override
public List<CertificateType> getSupportedCertificateTypes() {
return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY);
}
/**
* Validates the certificate provided by the the peer as part of the
* certificate message.
* <p>
* If a x509 certificate chain is provided in the certificate message,
* validate the chain and key usage. If a RawPublicKey certificate is
* provided, check, if this public key is trusted.
*
* @param cid connection ID
* @param serverName indicated server names. May be {@code null}, if not
* available or SNI is not enabled.
* @param remotePeer socket address of remote peer
* @param clientUsage indicator to check certificate usage. {@code true},
* check key usage for client, {@code false} for server.
* @param verifySubject {@code true} to verify the certificate's subjects,
* {@code false}, if not.
* @param truncateCertificatePath {@code true} truncate certificate path at
* a trusted certificate before validation.
* @param message certificate message to be validated
* @return certificate verification result, or {@code null}, if result is
* provided asynchronous.
* @since 3.0 (removed DTLSSession session, added remotePeer and
* verifySubject)
*/
@Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, InetSocketAddress remotePeer,
boolean clientUsage, boolean verifySubject, boolean truncateCertificatePath,
CertificateMessage message) {
CertPath certChain = message.getCertificateChain();
CertificateVerificationResult result;
if (certChain == null) {
PublicKey publicKey = message.getPublicKey();
result = new CertificateVerificationResult(cid, publicKey, null);
} else {
if (message.getCertificateChain().getCertificates().isEmpty()) {
result = new CertificateVerificationResult(cid, new HandshakeException("Empty certificate chain",
new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE)), null);
} else {
result = new CertificateVerificationResult(cid, certChain, null);
}
}
return result;
}
/**
* Return an list of certificate authorities which are trusted
* for authenticating peers.
*
* @return a non-null (possibly empty) list of accepted CA issuers.
*/
@Override
public List<X500Principal> getAcceptedIssuers() {
log.trace("getAcceptedIssuers: return null");
return null;
}
/**
* Set the handler for asynchronous handshake results.
* <p>
* Called during initialization of the {link DTLSConnector}. Synchronous
* implementations may just ignore this using an empty implementation.
*
* @param resultHandler handler for asynchronous master secret results. This
* handler MUST NOT be called from the thread calling
* {@link #verifyCertificate(ConnectionId, ServerNames, InetSocketAddress, boolean, boolean, boolean, CertificateMessage)},
* instead just return the result there.
*/
@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
if (this.resultHandler != null && resultHandler != null && this.resultHandler != resultHandler) {
throw new IllegalStateException("handshake result handler already set!");
}
this.resultHandler = resultHandler;
}
}

38
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -147,8 +147,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
" \"telemetry\": [],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
public static String OBSERVE_ATTRIBUTES_WITH_PARAMS =
public static String TELEMETRY_WITHOUT_OBSERVE =
" {\n" +
" \"keyName\": {\n" +
" \"/3_1.2/0/9\": \"batteryLevel\"\n" +
@ -161,6 +160,39 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
public static String TELEMETRY_WITH_ONE_OBSERVE =
" {\n" +
" \"keyName\": {\n" +
" \"/3_1.2/0/9\": \"batteryLevel\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/3_1.2/0/9\"\n" +
" ],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"/3_1.2/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
public static String TELEMETRY_WITH_MANY_OBSERVE =
" {\n" +
" \"keyName\": {\n" +
" \"/3_1.2/0/9\": \"batteryLevel\",\n" +
" \"/3_1.2/0/20\": \"batteryStatus\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/3_1.2/0/9\",\n" +
" \"/3_1.2/0/20\"\n" +
" ],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [\n" +
" \"/3_1.2/0/9\",\n" +
" \"/3_1.2/0/20\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
public static final String CLIENT_LWM2M_SETTINGS =
" {\n" +
@ -217,7 +249,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
LwM2MDeviceCredentials deviceCredentials,
String endpoint,
boolean queueMode) throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITHOUT_OBSERVE, getBootstrapServerCredentialsNoSec(NONE));
DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration);
Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId());

6
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -458,6 +458,12 @@ public class LwM2MTestClient {
}
}
public void stop(boolean deregister) {
if (leshanClient != null) {
leshanClient.stop(deregister);
}
}
private void awaitClientAfterStartConnectLw() {
LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(endpoint);
Mockito.doAnswer(invocationOnMock -> null).when(defaultLwM2mUplinkMsgHandlerTest).initAttributes(lwM2MClient, true);

1
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -93,6 +93,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
try {
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(9);
fireResourceChange(20);
}
, 1, 1, TimeUnit.SECONDS); // 2 sec
// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java

@ -51,7 +51,7 @@ public class Ota5LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile_IsNotSupported() throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITHOUT_OBSERVE, getBootstrapServerCredentialsNoSec(NONE));
DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO));
final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, deviceProfile.getId());

8
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java

@ -73,13 +73,13 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
* @throws Exception
*/
@Test
public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_Equal_Greater_3_After_Cancel_Count_2() throws Exception {
sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr());
String idVer_3_0 = objectInstanceIdVer_3;
sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0);
int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
verify(defaultUplinkMsgHandlerTest, timeout(10000).atLeast(cntUpdate))
.updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9), eq(null));
}
@ -88,13 +88,13 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
* @throws Exception
*/
@Test
public void testObserveOneObject_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
public void testObserveOneObject_Result_CONTENT_Value_Count_Equal_Greater_3_After_Cancel_Count_2() throws Exception {
sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr());
String idVer_3_0 = objectInstanceIdVer_3;
sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0);
int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
verify(defaultUplinkMsgHandlerTest, timeout(10000).atLeast(cntUpdate))
.updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9), eq(null));
}

3
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java

@ -196,7 +196,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M
false);
}
protected void basicTestConnection(Security security, Security securityBs,
protected Device basicTestConnection(Security security, Security securityBs,
LwM2MDeviceCredentials deviceCredentials,
String endpoint,
Lwm2mDeviceProfileTransportConfiguration transportConfiguration,
@ -227,6 +227,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M
return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS);
});
Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatuses));
return device;
}

81
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java

@ -16,10 +16,13 @@
package org.thingsboard.server.transport.lwm2m.security.sql;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
@ -27,7 +30,6 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import java.nio.charset.StandardCharsets;
import static org.eclipse.leshan.client.object.Security.psk;
import static org.eclipse.leshan.client.object.Security.pskBootstrap;
import static org.junit.Assert.assertEquals;
@ -37,6 +39,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClient
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@Slf4j
public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@ -66,6 +69,82 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes
ON_REGISTRATION_SUCCESS,
true);
}
@Test
public void testWithPskConnectLwm2mOneObserveSuccessUpdateProfileManyObserveUpdateRegistrationSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK;
String identity = CLIENT_PSK_IDENTITY;
String keyPsk = CLIENT_PSK_KEY;
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setIdentity(identity);
clientCredentials.setKey(keyPsk);
Security security = psk(SECURE_URI,
shortServerId,
identity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(keyPsk.toCharArray()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_ONE_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
String awaitAlias = "await on client state (Psk_Lwm2m)";
Device lwm2mDevice = this.basicTestConnection(security,
null,
deviceCredentials,
clientEndpoint,
transportConfiguration,
awaitAlias,
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString());
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + lwm2mDevice.getDeviceProfileId().getId().toString(), DeviceProfile.class);
transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_MANY_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE));
foundDeviceProfile.getProfileData().setTransportConfiguration(transportConfiguration);
DeviceProfile lwm2mDeviceProfileManyParams = doPost("/api/deviceProfile", foundDeviceProfile, DeviceProfile.class);
Assert.assertNotNull(lwm2mDeviceProfileManyParams);
awaitObserveReadAll(2, lwm2mDevice.getId().getId().toString());
awaitUpdateReg(3);
}
@Test
public void testWithPskConnectLwm2mSuccessObserveSuccessUnRegClientUpdateProfileObserveConnectLwm2mSuccessOWithNewObserve() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK;
String identity = CLIENT_PSK_IDENTITY;
String keyPsk = CLIENT_PSK_KEY;
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setIdentity(identity);
clientCredentials.setKey(keyPsk);
Security security = psk(SECURE_URI,
shortServerId,
identity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(keyPsk.toCharArray()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_ONE_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
String awaitAlias = "await on client state (Psk_Lwm2m)";
Device lwm2mDevice = this.basicTestConnection(security,
null,
deviceCredentials,
clientEndpoint,
transportConfiguration,
awaitAlias,
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString());
lwM2MTestClient.stop(true);
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + lwm2mDevice.getDeviceProfileId().getId().toString(), DeviceProfile.class);
transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_MANY_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE));
foundDeviceProfile.getProfileData().setTransportConfiguration(transportConfiguration);
DeviceProfile lwm2mDeviceProfileManyParams = doPost("/api/deviceProfile", foundDeviceProfile, DeviceProfile.class);
Assert.assertNotNull(lwm2mDeviceProfileManyParams);
lwM2MTestClient.start(true);
awaitObserveReadAll(2, lwm2mDevice.getId().getId().toString());
awaitUpdateReg(3);
}
@Test
public void testWithPskConnectLwm2mBadPskKeyByLength_BAD_REQUEST() throws Exception {

13
application/src/test/resources/coap/credentials/client/cert.pem

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB/TCCAaOgAwIBAgIIVNrVgKT9OE8wCgYIKoZIzj0EAwIwWjEOMAwGA1UEAxMF
Y2YtY2ExFDASBgNVBAsTC0NhbGlmb3JuaXVtMRQwEgYDVQQKEwtFY2xpcHNlIElv
VDEPMA0GA1UEBxMGT3R0YXdhMQswCQYDVQQGEwJDQTAeFw0yMzEwMjYwODA4MjJa
Fw0yNTEwMjUwODA4MjJaMF4xEjAQBgNVBAMTCWNmLWNsaWVudDEUMBIGA1UECxML
Q2FsaWZvcm5pdW0xFDASBgNVBAoTC0VjbGlwc2UgSW9UMQ8wDQYDVQQHEwZPdHRh
d2ExCzAJBgNVBAYTAkNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQxYO5/M5
ie6+3QPOaAy5MD6CkFILZwIb2rOBCX/EWPaocX1H+eynUnaEEbmqxeN6rnI/pH19
j4PtsegfHLrzzaNPME0wHQYDVR0OBBYEFKwEDLTJ+5cQoZfbjWN1vJ2ssgK+MAsG
A1UdDwQEAwIHgDAfBgNVHSMEGDAWgBSxVzoI1TL87++hsUb9vQwqODzgUTAKBggq
hkjOPQQDAgNIADBFAiA2KCOw3n2AK9Vm8u2u1bQREIEs3tKAU7eFjpNFn929NwIh
AInhBGoEwS2Xlu5bdZSfWnujoRrEQiIiQpStmLxVcIsH
-----END CERTIFICATE-----

14
application/src/test/resources/coap/credentials/client/cert_01.pem

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICIzCCAcmgAwIBAgIUZZCGYm65c9vU0Xfvd/pAnLVDouUwCgYIKoZIzj0EAwIw
ZzELMAkGA1UEBhMCVUExDTALBgNVBAgMBEtpeXYxDTALBgNVBAcMBEtpeXYxFDAS
BgNVBAoMC1RoaW5nc2JvYXJkMRIwEAYDVQQLDAlkZXZlbG9wZXIxEDAOBgNVBAMM
B2NlcnRfMDEwHhcNMjQxMjE4MTU1NjE1WhcNMjUxMjE4MTU1NjE1WjBnMQswCQYD
VQQGEwJVQTENMAsGA1UECAwES2l5djENMAsGA1UEBwwES2l5djEUMBIGA1UECgwL
VGhpbmdzYm9hcmQxEjAQBgNVBAsMCWRldmVsb3BlcjEQMA4GA1UEAwwHY2VydF8w
MTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNU1tE6o/QpqJJqpy+m+UoPuQe5g
eTgS4M3x0iQS6pzNEJBhzbnOp/BysGMB4wKiAWTRuKdH/gcRXDBTjLd/d7ijUzBR
MB0GA1UdDgQWBBSiao1iNWYzlsrSbxYqbda116HG1jAfBgNVHSMEGDAWgBSiao1i
NWYzlsrSbxYqbda116HG1jAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA
MEUCIB2aCM/nvDqic9NkoSX/71GwksLiAKiFNkt2BZQykrcHAiEAr2h5IMdkyurN
Jy/idx2y44CP0tMq/3QV0QLCQFJIi6s=
-----END CERTIFICATE-----

4
application/src/test/resources/coap/credentials/client/key.pem

@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDn0+4CuLeX7xwBs0ts
UUEDB3+HRwRKdIPeJlIbKuvvEQ==
-----END PRIVATE KEY-----

8
application/src/test/resources/coap/credentials/client/key_01.pem

@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJldU1MBuJUJnNHa9Ob5NGlXc/Os6put9eh1TlIbuScnoAoGCCqGSM49
AwEHoUQDQgAE1TW0Tqj9CmokmqnL6b5Sg+5B7mB5OBLgzfHSJBLqnM0QkGHNuc6n
8HKwYwHjAqIBZNG4p0f+BxFcMFOMt393uA==
-----END EC PRIVATE KEY-----

BIN
application/src/test/resources/coap/credentials/coapclientTest.jks

Binary file not shown.

BIN
application/src/test/resources/coap/credentials/coapserverTest.jks

Binary file not shown.

35
application/src/test/resources/coap/credentials/server/cert.pem

@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIBaDCCAQ2gAwIBAgIUCY+goBAOhowBs7BHs/qXdAX8XFgwCgYIKoZIzj0EAwIw
ETEPMA0GA1UEAwwGUm9vdENBMB4XDTI0MTIxOTEzNTY1OFoXDTM0MTIxNzEzNTY1
OFowETEPMA0GA1UEAwwGU2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
/qief3Kjnz0FpkQVaKRqJq3kHmCqqs+y1EGYLEZZAqLFvxmv7xoL6muG4Mj8tzqk
Ll94JJuz97hG1FiEZsq7O6NDMEEwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsG
AQUFBwMBMB0GA1UdDgQWBBTK/UPsN0I2ErVPILWKMRV6TSeAmTAKBggqhkjOPQQD
AgNJADBGAiEA8EhlOwvTbwGlxo55UIOJp9LBbCp0BEIWojlu8PzOVSsCIQDlV24S
3BUJVCuMRujO5lTfJLxaSKkOEIgRANwIGi88WA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBGzCBwgIUP/PGQOKa5EyvsIXNgvv9PNietyEwCgYIKoZIzj0EAwMwEDEOMAwG
A1UEAwwFVFJVU1QwHhcNMjQxMjE5MTM1NjU4WhcNMzQxMjE3MTM1NjU4WjARMQ8w
DQYDVQQDDAZSb290Q0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT+qJ5/cqOf
PQWmRBVopGomreQeYKqqz7LUQZgsRlkCosW/Ga/vGgvqa4bgyPy3OqQuX3gkm7P3
uEbUWIRmyrs7MAoGCCqGSM49BAMDA0gAMEUCIQD2DY3UDXbzaIBKrsCtohKlEunH
ip9LkSeYfSKCnfm23gIgA8AEJdunpRmPkilxgy6wZSLLROqDpGDnhnyv8dsR8cc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBLTCB1AIUcsuauXAqvIS2RQcNPYysETJUAvwwCgYIKoZIzj0EAwMwIzEhMB8G
A1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTI0MTIxOTEzNTY1OFoX
DTM0MTIxNzEzNTY1OFowEDEOMAwGA1UEAwwFVFJVU1QwWTATBgcqhkjOPQIBBggq
hkjOPQMBBwNCAAT+qJ5/cqOfPQWmRBVopGomreQeYKqqz7LUQZgsRlkCosW/Ga/v
Ggvqa4bgyPy3OqQuX3gkm7P3uEbUWIRmyrs7MAoGCCqGSM49BAMDA0gAMEUCIQCM
DV8sfoArfWiXAUF2LNS3kkHD7sgb91jr2+poEHgBBgIgXf9VeJp3K5jHX6lJwtE8
nd+jW7T9nhTc/5njHg7xons=
-----END CERTIFICATE-----
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIB+Z69so6HqCCWo5VOFxGsLXOlTWIYijOtzt+SeNGrgPoAoGCCqGSM49
AwEHoUQDQgAE/qief3Kjnz0FpkQVaKRqJq3kHmCqqs+y1EGYLEZZAqLFvxmv7xoL
6muG4Mj8tzqkLl94JJuz97hG1FiEZsq7Ow==
-----END EC PRIVATE KEY-----

3
common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java

@ -17,7 +17,6 @@ package org.thingsboard.server.coapserver;
import org.eclipse.californium.core.CoapServer;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.ConcurrentMap;
@ -25,5 +24,5 @@ public interface CoapServerService {
CoapServer getCoapServer() throws UnknownHostException;
ConcurrentMap<InetSocketAddress, TbCoapDtlsSessionInfo> getDtlsSessionsMap();
ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> getDtlsSessionsMap();
}

2
common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java

@ -78,7 +78,7 @@ public class DefaultCoapServerService implements CoapServerService {
}
@Override
public ConcurrentMap<InetSocketAddress, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
public ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionsMap() : null;
}

5
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java

@ -109,7 +109,8 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
if (msg != null && strCert.equals(msg.getCredentials())) {
DeviceProfile deviceProfile = msg.getDeviceProfile();
if (msg.hasDeviceInfo() && deviceProfile != null) {
tbCoapDtlsSessionInMemoryStorage.put(remotePeer, new TbCoapDtlsSessionInfo(msg, deviceProfile));
TbCoapDtlsSessionKey tbCoapDtlsSessionKey = new TbCoapDtlsSessionKey(remotePeer, msg.getCredentials());
tbCoapDtlsSessionInMemoryStorage.put(tbCoapDtlsSessionKey, new TbCoapDtlsSessionInfo(msg, deviceProfile));
}
break;
}
@ -138,7 +139,7 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri
public void setResultHandler(HandshakeResultHandler resultHandler) {
}
public ConcurrentMap<InetSocketAddress, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionsMap() {
public ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionsMap() {
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionsMap();
}

9
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionInMemoryStorage.java

@ -18,7 +18,6 @@ package org.thingsboard.server.coapserver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -26,7 +25,7 @@ import java.util.concurrent.ConcurrentMap;
@Data
public class TbCoapDtlsSessionInMemoryStorage {
private final ConcurrentMap<InetSocketAddress, TbCoapDtlsSessionInfo> dtlsSessionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<TbCoapDtlsSessionKey, TbCoapDtlsSessionInfo> dtlsSessionsMap = new ConcurrentHashMap<>();
private long dtlsSessionInactivityTimeout;
private long dtlsSessionReportTimeout;
@ -36,9 +35,9 @@ public class TbCoapDtlsSessionInMemoryStorage {
this.dtlsSessionReportTimeout = dtlsSessionReportTimeout;
}
public void put(InetSocketAddress remotePeer, TbCoapDtlsSessionInfo dtlsSessionInfo) {
log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", remotePeer, dtlsSessionInfo.getLastActivityTime());
dtlsSessionsMap.putIfAbsent(remotePeer, dtlsSessionInfo);
public void put(TbCoapDtlsSessionKey tbCoapDtlsSessionKey, TbCoapDtlsSessionInfo dtlsSessionInfo) {
log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", tbCoapDtlsSessionKey, dtlsSessionInfo.getLastActivityTime());
dtlsSessionsMap.putIfAbsent(tbCoapDtlsSessionKey, dtlsSessionInfo);
}
public void evictTimeoutSessions() {

22
common/queue/src/main/java/org/thingsboard/server/queue/sqs/AwsSqsTbQueueMsgMetadata.java → common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSessionKey.java

@ -13,16 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue.sqs;
package org.thingsboard.server.coapserver;
import com.amazonaws.http.SdkHttpMetadata;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import java.net.InetSocketAddress;
import java.util.Objects;
@Data
@AllArgsConstructor
public class AwsSqsTbQueueMsgMetadata implements TbQueueMsgMetadata {
public record TbCoapDtlsSessionKey(InetSocketAddress peerAddress, String credentials) {
private final SdkHttpMetadata metadata;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TbCoapDtlsSessionKey that = (TbCoapDtlsSessionKey) o;
return Objects.equals(peerAddress, that.peerAddress) &&
Objects.equals(credentials, that.credentials);
}
}

3
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.resource;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.ResourceSubType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
@ -88,6 +89,6 @@ public interface ResourceService extends EntityDaoService {
Collection<TbResourceInfo> getUsedResources(TenantId tenantId, WidgetTypeDetails widgetTypeDetails);
TbResource createOrUpdateSystemResource(ResourceType resourceType, String resourceKey, byte[] data);
TbResource createOrUpdateSystemResource(ResourceType resourceType, ResourceSubType resourceSubType, String resourceKey, byte[] data);
}

4
common/data/src/main/java/org/thingsboard/server/common/data/mobile/app/StoreInfo.java

@ -15,12 +15,16 @@
*/
package org.thingsboard.server.common.data.mobile.app;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StoreInfo {
@NoXss

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

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

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

@ -41,6 +41,7 @@ enum EdgeVersion {
V_3_7_0 = 7;
V_3_8_0 = 8;
V_3_9_0 = 9;
V_4_0_0 = 10;
}
/**

8
common/queue/pom.xml

@ -64,18 +64,10 @@
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sqs</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-pubsub</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-servicebus</artifactId>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>

56
common/queue/src/main/java/org/thingsboard/server/queue/RuleEngineTbQueueAdminFactory.java

@ -19,21 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thingsboard.server.queue.azure.servicebus.TbServiceBusAdmin;
import org.thingsboard.server.queue.azure.servicebus.TbServiceBusQueueConfigs;
import org.thingsboard.server.queue.azure.servicebus.TbServiceBusSettings;
import org.thingsboard.server.queue.kafka.TbKafkaAdmin;
import org.thingsboard.server.queue.kafka.TbKafkaSettings;
import org.thingsboard.server.queue.kafka.TbKafkaTopicConfigs;
import org.thingsboard.server.queue.pubsub.TbPubSubAdmin;
import org.thingsboard.server.queue.pubsub.TbPubSubSettings;
import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings;
import org.thingsboard.server.queue.rabbitmq.TbRabbitMqAdmin;
import org.thingsboard.server.queue.rabbitmq.TbRabbitMqQueueArguments;
import org.thingsboard.server.queue.rabbitmq.TbRabbitMqSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes;
import org.thingsboard.server.queue.sqs.TbAwsSqsSettings;
@Configuration
public class RuleEngineTbQueueAdminFactory {
@ -43,56 +31,12 @@ public class RuleEngineTbQueueAdminFactory {
@Autowired(required = false)
private TbKafkaSettings kafkaSettings;
@Autowired(required = false)
private TbAwsSqsQueueAttributes awsSqsQueueAttributes;
@Autowired(required = false)
private TbAwsSqsSettings awsSqsSettings;
@Autowired(required = false)
private TbPubSubSubscriptionSettings pubSubSubscriptionSettings;
@Autowired(required = false)
private TbPubSubSettings pubSubSettings;
@Autowired(required = false)
private TbRabbitMqQueueArguments rabbitMqQueueArguments;
@Autowired(required = false)
private TbRabbitMqSettings rabbitMqSettings;
@Autowired(required = false)
private TbServiceBusQueueConfigs serviceBusQueueConfigs;
@Autowired(required = false)
private TbServiceBusSettings serviceBusSettings;
@ConditionalOnExpression("'${queue.type:null}'=='kafka'")
@Bean
public TbQueueAdmin createKafkaAdmin() {
return new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs());
}
@ConditionalOnExpression("'${queue.type:null}'=='aws-sqs'")
@Bean
public TbQueueAdmin createAwsSqsAdmin() {
return new TbAwsSqsAdmin(awsSqsSettings, awsSqsQueueAttributes.getRuleEngineAttributes());
}
@ConditionalOnExpression("'${queue.type:null}'=='pubsub'")
@Bean
public TbQueueAdmin createPubSubAdmin() {
return new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getRuleEngineSettings());
}
@ConditionalOnExpression("'${queue.type:null}'=='rabbitmq'")
@Bean
public TbQueueAdmin createRabbitMqAdmin() {
return new TbRabbitMqAdmin(rabbitMqSettings, rabbitMqQueueArguments.getRuleEngineArgs());
}
@ConditionalOnExpression("'${queue.type:null}'=='service-bus'")
@Bean
public TbQueueAdmin createServiceBusAdmin() {
return new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getRuleEngineConfigs());
}
@ConditionalOnExpression("'${queue.type:null}'=='in-memory'")
@Bean
public TbQueueAdmin createInMemoryAdmin() {

137
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusAdmin.java

@ -1,137 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.azure.servicebus;
import com.microsoft.azure.servicebus.management.ManagementClient;
import com.microsoft.azure.servicebus.management.QueueDescription;
import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder;
import com.microsoft.azure.servicebus.primitives.MessagingEntityAlreadyExistsException;
import com.microsoft.azure.servicebus.primitives.ServiceBusException;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.util.PropertyUtils;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Deprecated(forRemoval = true, since = "3.9") // for removal in 4.0
public class TbServiceBusAdmin implements TbQueueAdmin {
private final String MAX_SIZE = "maxSizeInMb";
private final String MESSAGE_TIME_TO_LIVE = "messageTimeToLiveInSec";
private final String LOCK_DURATION = "lockDurationInSec";
private final Map<String, String> queueConfigs;
private final Set<String> queues = ConcurrentHashMap.newKeySet();
private final ManagementClient client;
public TbServiceBusAdmin(TbServiceBusSettings serviceBusSettings, Map<String, String> queueConfigs) {
this.queueConfigs = queueConfigs;
ConnectionStringBuilder builder = new ConnectionStringBuilder(
serviceBusSettings.getNamespaceName(),
"queues",
serviceBusSettings.getSasKeyName(),
serviceBusSettings.getSasKey());
client = new ManagementClient(builder);
try {
client.getQueues().forEach(queueDescription -> queues.add(queueDescription.getPath()));
} catch (ServiceBusException | InterruptedException e) {
log.error("Failed to get queues.", e);
throw new RuntimeException("Failed to get queues.", e);
}
}
@Override
public void createTopicIfNotExists(String topic, String properties) {
if (queues.contains(topic)) {
return;
}
try {
QueueDescription queueDescription = new QueueDescription(topic);
queueDescription.setRequiresDuplicateDetection(false);
setQueueConfigs(queueDescription, PropertyUtils.getProps(queueConfigs, properties));
client.createQueue(queueDescription);
queues.add(topic);
} catch (ServiceBusException | InterruptedException e) {
if (e instanceof MessagingEntityAlreadyExistsException) {
queues.add(topic);
log.info("[{}] queue already exists.", topic);
} else {
log.error("Failed to create queue: [{}]", topic, e);
}
}
}
@Override
public void deleteTopic(String topic) {
if (queues.contains(topic)) {
doDelete(topic);
} else {
try {
if (client.getQueue(topic) != null) {
doDelete(topic);
} else {
log.warn("Azure Service Bus Queue [{}] is not exist.", topic);
}
} catch (ServiceBusException | InterruptedException e) {
log.error("Failed to delete Azure Service Bus queue [{}]", topic, e);
}
}
}
private void doDelete(String topic) {
try {
client.deleteTopic(topic);
} catch (ServiceBusException | InterruptedException e) {
log.error("Failed to delete Azure Service Bus queue [{}]", topic, e);
}
}
private void setQueueConfigs(QueueDescription queueDescription, Map<String, String> queueConfigs) {
queueConfigs.forEach((confKey, confValue) -> {
switch (confKey) {
case MAX_SIZE:
queueDescription.setMaxSizeInMB(Long.parseLong(confValue));
break;
case MESSAGE_TIME_TO_LIVE:
queueDescription.setDefaultMessageTimeToLive(Duration.ofSeconds(Long.parseLong(confValue)));
break;
case LOCK_DURATION:
queueDescription.setLockDuration(Duration.ofSeconds(Long.parseLong(confValue)));
break;
default:
log.error("Unknown config: [{}]", confKey);
}
});
}
public void destroy() {
try {
client.close();
} catch (IOException e) {
log.error("Failed to close ManagementClient.");
}
}
}

175
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java

@ -1,175 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.azure.servicebus;
import com.google.gson.Gson;
import com.google.protobuf.InvalidProtocolBufferException;
import com.microsoft.azure.servicebus.TransactionContext;
import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder;
import com.microsoft.azure.servicebus.primitives.CoreMessageReceiver;
import com.microsoft.azure.servicebus.primitives.MessageWithDeliveryTag;
import com.microsoft.azure.servicebus.primitives.MessagingEntityType;
import com.microsoft.azure.servicebus.primitives.MessagingFactory;
import com.microsoft.azure.servicebus.primitives.SettleModePair;
import lombok.extern.slf4j.Slf4j;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode;
import org.apache.qpid.proton.amqp.transport.SenderSettleMode;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.TbQueueMsgDecoder;
import org.thingsboard.server.queue.common.AbstractTbQueueConsumerTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
public class TbServiceBusConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQueueConsumerTemplate<MessageWithDeliveryTag, T> {
private final TbQueueAdmin admin;
private final TbQueueMsgDecoder<T> decoder;
private final TbServiceBusSettings serviceBusSettings;
private final Gson gson = new Gson();
private Set<CoreMessageReceiver> receivers;
private final Map<CoreMessageReceiver, Collection<MessageWithDeliveryTag>> pendingMessages = new ConcurrentHashMap<>();
private volatile int messagesPerQueue;
public TbServiceBusConsumerTemplate(TbQueueAdmin admin, TbServiceBusSettings serviceBusSettings, String topic, TbQueueMsgDecoder<T> decoder) {
super(topic);
this.admin = admin;
this.decoder = decoder;
this.serviceBusSettings = serviceBusSettings;
}
@Override
protected List<MessageWithDeliveryTag> doPoll(long durationInMillis) {
List<CompletableFuture<Collection<MessageWithDeliveryTag>>> messageFutures =
receivers.stream()
.map(receiver -> receiver
.receiveAsync(messagesPerQueue, Duration.ofMillis(durationInMillis))
.whenComplete((messages, err) -> {
if (!CollectionUtils.isEmpty(messages)) {
pendingMessages.put(receiver, messages);
} else if (err != null) {
log.error("Failed to receive messages.", err);
}
}))
.collect(Collectors.toList());
try {
return fromList(messageFutures)
.get()
.stream()
.flatMap(messages -> CollectionUtils.isEmpty(messages) ? Stream.empty() : messages.stream())
.collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
if (stopped) {
log.info("[{}] Service Bus consumer is stopped.", getTopic());
} else {
log.error("Failed to receive messages", e);
}
return Collections.emptyList();
}
}
@Override
protected void doSubscribe(List<String> topicNames) {
createReceivers();
messagesPerQueue = receivers.size() / Math.max(partitions.size(), 1);
}
@Override
protected void doCommit() {
pendingMessages.forEach((receiver, msgs) ->
msgs.forEach(msg -> receiver.completeMessageAsync(msg.getDeliveryTag(), TransactionContext.NULL_TXN)));
pendingMessages.clear();
}
@Override
protected void doUnsubscribe() {
receivers.forEach(CoreMessageReceiver::closeAsync);
}
private void createReceivers() {
List<CompletableFuture<CoreMessageReceiver>> receiverFutures = partitions.stream()
.map(TopicPartitionInfo::getFullTopicName)
.map(queue -> {
MessagingFactory factory;
try {
factory = MessagingFactory.createFromConnectionStringBuilder(createConnection(queue));
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to create factory for the queue [{}]", queue);
throw new RuntimeException("Failed to create the factory", e);
}
return CoreMessageReceiver.create(factory, queue, queue, 0,
new SettleModePair(SenderSettleMode.UNSETTLED, ReceiverSettleMode.SECOND),
MessagingEntityType.QUEUE);
}).collect(Collectors.toList());
try {
receivers = new HashSet<>(fromList(receiverFutures).get());
} catch (InterruptedException | ExecutionException e) {
if (stopped) {
log.info("[{}] Service Bus consumer is stopped.", getTopic());
} else {
log.error("Failed to create receivers", e);
}
}
}
private ConnectionStringBuilder createConnection(String queue) {
admin.createTopicIfNotExists(queue);
return new ConnectionStringBuilder(
serviceBusSettings.getNamespaceName(),
queue,
serviceBusSettings.getSasKeyName(),
serviceBusSettings.getSasKey());
}
private <V> CompletableFuture<List<V>> fromList(List<CompletableFuture<V>> futures) {
@SuppressWarnings("unchecked")
CompletableFuture<Collection<V>>[] arrayFuture = new CompletableFuture[futures.size()];
futures.toArray(arrayFuture);
return CompletableFuture
.allOf(arrayFuture)
.thenApply(v -> futures
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}
@Override
protected T decode(MessageWithDeliveryTag data) throws InvalidProtocolBufferException {
DefaultTbQueueMsg msg = gson.fromJson(new String(((Data) data.getMessage().getBody()).getValue().getArray()), DefaultTbQueueMsg.class);
return decoder.decode(msg);
}
}

110
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusProducerTemplate.java

@ -1,110 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.azure.servicebus;
import com.google.gson.Gson;
import com.microsoft.azure.servicebus.IMessage;
import com.microsoft.azure.servicebus.Message;
import com.microsoft.azure.servicebus.QueueClient;
import com.microsoft.azure.servicebus.ReceiveMode;
import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder;
import com.microsoft.azure.servicebus.primitives.ServiceBusException;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class TbServiceBusProducerTemplate<T extends TbQueueMsg> implements TbQueueProducer<T> {
private final String defaultTopic;
private final Gson gson = new Gson();
private final TbQueueAdmin admin;
private final TbServiceBusSettings serviceBusSettings;
private final Map<String, QueueClient> clients = new ConcurrentHashMap<>();
private final ExecutorService executorService;
public TbServiceBusProducerTemplate(TbQueueAdmin admin, TbServiceBusSettings serviceBusSettings, String defaultTopic) {
this.admin = admin;
this.defaultTopic = defaultTopic;
this.serviceBusSettings = serviceBusSettings;
executorService = Executors.newCachedThreadPool();
}
@Override
public void init() {
}
@Override
public String getDefaultTopic() {
return defaultTopic;
}
@Override
public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) {
IMessage message = new Message(gson.toJson(new DefaultTbQueueMsg(msg)));
CompletableFuture<Void> future = getClient(tpi.getFullTopicName()).sendAsync(message);
future.whenCompleteAsync((success, err) -> {
if (err != null) {
callback.onFailure(err);
} else {
callback.onSuccess(null);
}
}, executorService);
}
@Override
public void stop() {
clients.forEach((t, client) -> {
try {
client.close();
} catch (ServiceBusException e) {
log.error("Failed to close QueueClient.", e);
}
});
if (executorService != null) {
executorService.shutdownNow();
}
}
private QueueClient getClient(String topic) {
return clients.computeIfAbsent(topic, k -> {
admin.createTopicIfNotExists(topic);
ConnectionStringBuilder builder =
new ConnectionStringBuilder(
serviceBusSettings.getNamespaceName(),
topic,
serviceBusSettings.getSasKeyName(),
serviceBusSettings.getSasKey());
try {
return new QueueClient(builder, ReceiveMode.PEEKLOCK);
} catch (InterruptedException | ServiceBusException e) {
log.error("Failed to create new client for the Queue: [{}]", topic, e);
throw new RuntimeException("Failed to create new client for the Queue", e);
}
});
}
}

72
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java

@ -1,72 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.azure.servicebus;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.queue.util.PropertyUtils;
import java.util.Map;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='service-bus'")
public class TbServiceBusQueueConfigs {
@Value("${queue.service-bus.queue-properties.core:}")
private String coreProperties;
@Value("${queue.service-bus.queue-properties.rule-engine:}")
private String ruleEngineProperties;
@Value("${queue.service-bus.queue-properties.transport-api:}")
private String transportApiProperties;
@Value("${queue.service-bus.queue-properties.notifications:}")
private String notificationsProperties;
@Value("${queue.service-bus.queue-properties.js-executor:}")
private String jsExecutorProperties;
@Value("${queue.service-bus.queue-properties.version-control:}")
private String vcProperties;
@Value("${queue.service-bus.queue-properties.edge:}")
private String edgeProperties;
@Getter
private Map<String, String> coreConfigs;
@Getter
private Map<String, String> ruleEngineConfigs;
@Getter
private Map<String, String> transportApiConfigs;
@Getter
private Map<String, String> notificationsConfigs;
@Getter
private Map<String, String> jsExecutorConfigs;
@Getter
private Map<String, String> vcConfigs;
@Getter
private Map<String, String> edgeConfigs;
@PostConstruct
private void init() {
coreConfigs = PropertyUtils.getProps(coreProperties);
ruleEngineConfigs = PropertyUtils.getProps(ruleEngineProperties);
transportApiConfigs = PropertyUtils.getProps(transportApiProperties);
notificationsConfigs = PropertyUtils.getProps(notificationsProperties);
jsExecutorConfigs = PropertyUtils.getProps(jsExecutorProperties);
vcConfigs = PropertyUtils.getProps(vcProperties);
edgeConfigs = PropertyUtils.getProps(edgeProperties);
}
}

37
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusSettings.java

@ -1,37 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.azure.servicebus;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
@Slf4j
@ConditionalOnExpression("'${queue.type:null}'=='service-bus'")
@Component
@Data
public class TbServiceBusSettings {
@Value("${queue.service_bus.namespace_name}")
private String namespaceName;
@Value("${queue.service_bus.sas_key_name}")
private String sasKeyName;
@Value("${queue.service_bus.sas_key}")
private String sasKey;
@Value("${queue.service_bus.max_messages}")
private int maxMessages;
}

317
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java

@ -1,317 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import com.google.protobuf.util.JsonFormat;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest;
import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueEdgeSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes;
import org.thingsboard.server.queue.sqs.TbAwsSqsSettings;
import java.nio.charset.StandardCharsets;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && '${service.type:null}'=='monolith'")
public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory {
private final TopicService topicService;
private final TbQueueCoreSettings coreSettings;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueTransportApiSettings transportApiSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbAwsSqsSettings sqsSettings;
private final TbQueueVersionControlSettings vcSettings;
private final TbQueueEdgeSettings edgeSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin otaAdmin;
private final TbQueueAdmin vcAdmin;
private final TbQueueAdmin edgeAdmin;
public AwsSqsMonolithQueueFactory(TopicService topicService, TbQueueCoreSettings coreSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TbServiceInfoProvider serviceInfoProvider,
TbQueueTransportApiSettings transportApiSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbAwsSqsSettings sqsSettings,
TbQueueVersionControlSettings vcSettings,
TbQueueEdgeSettings edgeSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes,
TbQueueRemoteJsInvokeSettings jsInvokeSettings) {
this.topicService = topicService;
this.coreSettings = coreSettings;
this.serviceInfoProvider = serviceInfoProvider;
this.ruleEngineSettings = ruleEngineSettings;
this.transportApiSettings = transportApiSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.sqsSettings = sqsSettings;
this.vcSettings = vcSettings;
this.edgeSettings = edgeSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes());
this.jsExecutorAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getJsExecutorAttributes());
this.transportApiAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getTransportApiAttributes());
this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes());
this.otaAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getOtaAttributes());
this.vcAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getVcAttributes());
this.edgeAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getEdgeAttributes());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(transportNotificationSettings.getNotificationsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings,
topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName());
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToVersionControlServiceMsg>> createToVersionControlMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(vcAdmin, sqsSettings, topicService.buildTopicName(vcSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders())
);
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration) {
return new TbAwsSqsConsumerTemplate<>(ruleEngineAdmin, sqsSettings, topicService.buildTopicName(configuration.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createToRuleEngineNotificationsMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings,
topicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> createToCoreMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreNotificationMsg>> createToCoreNotificationsMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings,
topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> createTransportApiRequestConsumer() {
return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, topicService.buildTopicName(transportApiSettings.getRequestsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, topicService.buildTopicName(transportApiSettings.getResponsesTopic()));
}
@Override
@Bean
public TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> createRemoteJsRequestTemplate() {
TbQueueProducer<TbProtoJsQueueMsg<RemoteJsRequest>> producer = new TbAwsSqsProducerTemplate<>(jsExecutorAdmin, sqsSettings, jsInvokeSettings.getRequestTopic());
TbQueueConsumer<TbProtoQueueMsg<RemoteJsResponse>> consumer = new TbAwsSqsConsumerTemplate<>(jsExecutorAdmin, sqsSettings,
jsInvokeSettings.getResponseTopic() + "_" + serviceInfoProvider.getServiceId(),
msg -> {
RemoteJsResponse.Builder builder = RemoteJsResponse.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder);
return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders());
});
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> builder = DefaultTbQueueRequestTemplate.builder();
builder.queueAdmin(jsExecutorAdmin);
builder.requestTemplate(producer);
builder.responseTemplate(consumer);
builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests());
builder.maxRequestTimeout(jsInvokeSettings.getMaxRequestsTimeout());
builder.pollInterval(jsInvokeSettings.getResponsePollInterval());
return builder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(otaAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(otaAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToVersionControlServiceMsg>> createVersionControlMsgProducer() {
return new TbAwsSqsProducerTemplate<>(vcAdmin, sqsSettings, topicService.buildTopicName(vcSettings.getTopic()));
}
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(edgeAdmin, sqsSettings, topicService.buildTopicName(edgeSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgProducer() {
return new TbAwsSqsProducerTemplate<>(edgeAdmin, sqsSettings, topicService.buildTopicName(edgeSettings.getTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createToEdgeNotificationsMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createEdgeNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> createEdgeEventMsgProducer() {
return null;
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
if (jsExecutorAdmin != null) {
jsExecutorAdmin.destroy();
}
if (transportApiAdmin != null) {
transportApiAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (otaAdmin != null) {
otaAdmin.destroy();
}
if (vcAdmin != null) {
vcAdmin.destroy();
}
if (edgeAdmin != null) {
edgeAdmin.destroy();
}
}
}

291
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java

@ -1,291 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import com.google.protobuf.util.JsonFormat;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueEdgeSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes;
import org.thingsboard.server.queue.sqs.TbAwsSqsSettings;
import java.nio.charset.StandardCharsets;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && '${service.type:null}'=='tb-core'")
public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
private final TbAwsSqsSettings sqsSettings;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueCoreSettings coreSettings;
private final TbQueueTransportApiSettings transportApiSettings;
private final TopicService topicService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueVersionControlSettings vcSettings;
private final TbQueueEdgeSettings edgeSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin otaAdmin;
private final TbQueueAdmin vcAdmin;
private final TbQueueAdmin edgeAdmin;
public AwsSqsTbCoreQueueFactory(TbAwsSqsSettings sqsSettings,
TbQueueCoreSettings coreSettings,
TbQueueTransportApiSettings transportApiSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TopicService topicService,
TbQueueVersionControlSettings vcSettings,
TbQueueEdgeSettings edgeSettings,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes,
TbQueueTransportNotificationSettings transportNotificationSettings) {
this.sqsSettings = sqsSettings;
this.coreSettings = coreSettings;
this.transportApiSettings = transportApiSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.edgeSettings = edgeSettings;
this.topicService = topicService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.vcSettings = vcSettings;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes());
this.jsExecutorAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getJsExecutorAttributes());
this.transportApiAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getTransportApiAttributes());
this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes());
this.otaAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getOtaAttributes());
this.vcAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getVcAttributes());
this.edgeAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getEdgeAttributes());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(transportNotificationSettings.getNotificationsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings,
topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName());
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> createToCoreMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreNotificationMsg>> createToCoreNotificationsMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings,
topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> createTransportApiRequestConsumer() {
return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, topicService.buildTopicName(transportApiSettings.getRequestsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, topicService.buildTopicName(transportApiSettings.getResponsesTopic()));
}
@Override
@Bean
public TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate() {
TbQueueProducer<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>> producer = new TbAwsSqsProducerTemplate<>(jsExecutorAdmin, sqsSettings, jsInvokeSettings.getRequestTopic());
TbQueueConsumer<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> consumer = new TbAwsSqsConsumerTemplate<>(jsExecutorAdmin, sqsSettings,
jsInvokeSettings.getResponseTopic() + "_" + serviceInfoProvider.getServiceId(),
msg -> {
JsInvokeProtos.RemoteJsResponse.Builder builder = JsInvokeProtos.RemoteJsResponse.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder);
return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders());
});
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> builder = DefaultTbQueueRequestTemplate.builder();
builder.queueAdmin(jsExecutorAdmin);
builder.requestTemplate(producer);
builder.responseTemplate(consumer);
builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests());
builder.maxRequestTimeout(jsInvokeSettings.getMaxRequestsTimeout());
builder.pollInterval(jsInvokeSettings.getResponsePollInterval());
return builder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(otaAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(otaAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToVersionControlServiceMsg>> createVersionControlMsgProducer() {
return new TbAwsSqsProducerTemplate<>(vcAdmin, sqsSettings, topicService.buildTopicName(vcSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(edgeAdmin, sqsSettings, topicService.buildTopicName(edgeSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgProducer() {
return new TbAwsSqsProducerTemplate<>(edgeAdmin, sqsSettings, topicService.buildTopicName(edgeSettings.getTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createToEdgeNotificationsMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createEdgeNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
if (jsExecutorAdmin != null) {
jsExecutorAdmin.destroy();
}
if (transportApiAdmin != null) {
transportApiAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (otaAdmin != null) {
otaAdmin.destroy();
}
if (vcAdmin != null) {
vcAdmin.destroy();
}
if (edgeAdmin != null) {
edgeAdmin.destroy();
}
}
}

208
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java

@ -1,208 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import com.google.protobuf.util.JsonFormat;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueEdgeSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes;
import org.thingsboard.server.queue.sqs.TbAwsSqsSettings;
import java.nio.charset.StandardCharsets;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && '${service.type:null}'=='tb-rule-engine'")
public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
private final TopicService topicService;
private final TbQueueCoreSettings coreSettings;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbAwsSqsSettings sqsSettings;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueEdgeSettings edgeSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin otaAdmin;
private final TbQueueAdmin edgeAdmin;
public AwsSqsTbRuleEngineQueueFactory(TopicService topicService, TbQueueCoreSettings coreSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TbServiceInfoProvider serviceInfoProvider,
TbAwsSqsSettings sqsSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbQueueEdgeSettings edgeSettings) {
this.topicService = topicService;
this.coreSettings = coreSettings;
this.serviceInfoProvider = serviceInfoProvider;
this.ruleEngineSettings = ruleEngineSettings;
this.sqsSettings = sqsSettings;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.edgeSettings = edgeSettings;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes());
this.jsExecutorAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getJsExecutorAttributes());
this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes());
this.otaAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getOtaAttributes());
this.edgeAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getEdgeAttributes());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(transportNotificationSettings.getNotificationsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgProducer() {
return new TbAwsSqsProducerTemplate<>(edgeAdmin, sqsSettings, topicService.buildTopicName(edgeSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createEdgeNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration) {
return new TbAwsSqsConsumerTemplate<>(ruleEngineAdmin, sqsSettings, topicService.buildTopicName(configuration.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createToRuleEngineNotificationsMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings,
topicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
@Bean
public TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate() {
TbQueueProducer<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>> producer = new TbAwsSqsProducerTemplate<>(jsExecutorAdmin, sqsSettings, jsInvokeSettings.getRequestTopic());
TbQueueConsumer<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> consumer = new TbAwsSqsConsumerTemplate<>(jsExecutorAdmin, sqsSettings,
jsInvokeSettings.getResponseTopic() + "_" + serviceInfoProvider.getServiceId(),
msg -> {
JsInvokeProtos.RemoteJsResponse.Builder builder = JsInvokeProtos.RemoteJsResponse.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder);
return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders());
});
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> builder = DefaultTbQueueRequestTemplate.builder();
builder.queueAdmin(jsExecutorAdmin);
builder.requestTemplate(producer);
builder.responseTemplate(consumer);
builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests());
builder.maxRequestTimeout(jsInvokeSettings.getMaxRequestsTimeout());
builder.pollInterval(jsInvokeSettings.getResponsePollInterval());
return builder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(otaAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
if (jsExecutorAdmin != null) {
jsExecutorAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (otaAdmin != null) {
otaAdmin.destroy();
}
}
}

98
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java

@ -1,98 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes;
import org.thingsboard.server.queue.sqs.TbAwsSqsSettings;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && '${service.type:null}'=='tb-vc-executor'")
public class AwsSqsTbVersionControlQueueFactory implements TbVersionControlQueueFactory {
private final TbAwsSqsSettings sqsSettings;
private final TbQueueCoreSettings coreSettings;
private final TbQueueVersionControlSettings vcSettings;
private final TopicService topicService;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin vcAdmin;
public AwsSqsTbVersionControlQueueFactory(TbAwsSqsSettings sqsSettings,
TbQueueCoreSettings coreSettings,
TbQueueVersionControlSettings vcSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes,
TopicService topicService
) {
this.sqsSettings = sqsSettings;
this.coreSettings = coreSettings;
this.vcSettings = vcSettings;
this.topicService = topicService;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes());
this.vcAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getVcAttributes());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToVersionControlServiceMsg>> createToVersionControlMsgConsumer() {
return new TbAwsSqsConsumerTemplate<>(vcAdmin, sqsSettings, topicService.buildTopicName(vcSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders())
);
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (vcAdmin != null) {
vcAdmin.destroy();
}
}
}

153
common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java

@ -1,153 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin;
import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate;
import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes;
import org.thingsboard.server.queue.sqs.TbAwsSqsSettings;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && (('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport')")
@Slf4j
public class AwsSqsTransportQueueFactory implements TbTransportQueueFactory {
private final TbQueueTransportApiSettings transportApiSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbAwsSqsSettings sqsSettings;
private final TbQueueCoreSettings coreSettings;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TopicService topicService;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin ruleEngineAdmin;
public AwsSqsTransportQueueFactory(TbQueueTransportApiSettings transportApiSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbAwsSqsSettings sqsSettings,
TbServiceInfoProvider serviceInfoProvider,
TbQueueCoreSettings coreSettings,
TbAwsSqsQueueAttributes sqsQueueAttributes,
TbQueueRuleEngineSettings ruleEngineSettings,
TopicService topicService) {
this.transportApiSettings = transportApiSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.sqsSettings = sqsSettings;
this.serviceInfoProvider = serviceInfoProvider;
this.coreSettings = coreSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.topicService = topicService;
this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes());
this.transportApiAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getTransportApiAttributes());
this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes());
this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes());
}
@Override
public TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiRequestTemplate() {
TbQueueProducer<TbProtoQueueMsg<TransportApiRequestMsg>> producerTemplate =
new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, topicService.buildTopicName(transportApiSettings.getRequestsTopic()));
TbQueueConsumer<TbProtoQueueMsg<TransportApiResponseMsg>> consumerTemplate =
new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings,
topicService.buildTopicName(transportApiSettings.getResponsesTopic() + "_" + serviceInfoProvider.getServiceId()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders()));
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> templateBuilder = DefaultTbQueueRequestTemplate.builder();
templateBuilder.queueAdmin(transportApiAdmin);
templateBuilder.requestTemplate(producerTemplate);
templateBuilder.responseTemplate(consumerTemplate);
templateBuilder.maxPendingRequests(transportApiSettings.getMaxPendingRequests());
templateBuilder.maxRequestTimeout(transportApiSettings.getMaxRequestsTimeout());
templateBuilder.pollInterval(transportApiSettings.getResponsePollInterval());
return templateBuilder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsConsumer() {
return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings, topicService.buildTopicName(transportNotificationSettings.getNotificationsTopic() + "_" + serviceInfoProvider.getServiceId()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (transportApiAdmin != null) {
transportApiAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
}
}

315
common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java

@ -1,315 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import com.google.protobuf.util.JsonFormat;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest;
import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.pubsub.TbPubSubAdmin;
import org.thingsboard.server.queue.pubsub.TbPubSubConsumerTemplate;
import org.thingsboard.server.queue.pubsub.TbPubSubProducerTemplate;
import org.thingsboard.server.queue.pubsub.TbPubSubSettings;
import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueEdgeSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings;
import java.nio.charset.StandardCharsets;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='pubsub' && '${service.type:null}'=='monolith'")
public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory {
private final TbPubSubSettings pubSubSettings;
private final TbQueueCoreSettings coreSettings;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueTransportApiSettings transportApiSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TopicService topicService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueVersionControlSettings vcSettings;
private final TbQueueEdgeSettings edgeSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin vcAdmin;
private final TbQueueAdmin edgeAdmin;
public PubSubMonolithQueueFactory(TbPubSubSettings pubSubSettings,
TbQueueCoreSettings coreSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TbQueueTransportApiSettings transportApiSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TopicService topicService,
TbServiceInfoProvider serviceInfoProvider,
TbPubSubSubscriptionSettings pubSubSubscriptionSettings,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueVersionControlSettings vcSettings,
TbQueueEdgeSettings edgeSettings) {
this.pubSubSettings = pubSubSettings;
this.coreSettings = coreSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.transportApiSettings = transportApiSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.topicService = topicService;
this.serviceInfoProvider = serviceInfoProvider;
this.vcSettings = vcSettings;
this.edgeSettings = edgeSettings;
this.coreAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getCoreSettings());
this.ruleEngineAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getRuleEngineSettings());
this.jsExecutorAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getJsExecutorSettings());
this.transportApiAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getTransportApiSettings());
this.notificationAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getNotificationsSettings());
this.vcAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getVcSettings());
this.edgeAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getEdgeSettings());
this.jsInvokeSettings = jsInvokeSettings;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(transportNotificationSettings.getNotificationsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbPubSubProducerTemplate<>(ruleEngineAdmin, pubSubSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToVersionControlServiceMsg>> createToVersionControlMsgConsumer() {
return new TbPubSubConsumerTemplate<>(vcAdmin, pubSubSettings, topicService.buildTopicName(vcSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders())
);
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration) {
return new TbPubSubConsumerTemplate<>(ruleEngineAdmin, pubSubSettings, topicService.buildTopicName(configuration.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createToRuleEngineNotificationsMsgConsumer() {
return new TbPubSubConsumerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> createToCoreMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreNotificationMsg>> createToCoreNotificationsMsgConsumer() {
return new TbPubSubConsumerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> createTransportApiRequestConsumer() {
return new TbPubSubConsumerTemplate<>(transportApiAdmin, pubSubSettings, topicService.buildTopicName(transportApiSettings.getRequestsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbPubSubProducerTemplate<>(transportApiAdmin, pubSubSettings, topicService.buildTopicName(transportApiSettings.getResponsesTopic()));
}
@Override
@Bean
public TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> createRemoteJsRequestTemplate() {
TbQueueProducer<TbProtoJsQueueMsg<RemoteJsRequest>> producer = new TbPubSubProducerTemplate<>(jsExecutorAdmin, pubSubSettings, jsInvokeSettings.getRequestTopic());
TbQueueConsumer<TbProtoQueueMsg<RemoteJsResponse>> consumer = new TbPubSubConsumerTemplate<>(jsExecutorAdmin, pubSubSettings,
jsInvokeSettings.getResponseTopic() + "." + serviceInfoProvider.getServiceId(),
msg -> {
RemoteJsResponse.Builder builder = RemoteJsResponse.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder);
return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders());
});
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> builder = DefaultTbQueueRequestTemplate.builder();
builder.queueAdmin(jsExecutorAdmin);
builder.requestTemplate(producer);
builder.responseTemplate(consumer);
builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests());
builder.maxRequestTimeout(jsInvokeSettings.getMaxRequestsTimeout());
builder.pollInterval(jsInvokeSettings.getResponsePollInterval());
return builder.build();
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToVersionControlServiceMsg>> createVersionControlMsgProducer() {
return new TbPubSubProducerTemplate<>(vcAdmin, pubSubSettings, topicService.buildTopicName(vcSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgProducer() {
return new TbPubSubProducerTemplate<>(edgeAdmin, pubSubSettings, topicService.buildTopicName(edgeSettings.getTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgConsumer() {
return new TbPubSubConsumerTemplate<>(edgeAdmin, pubSubSettings, topicService.buildTopicName(edgeSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createToEdgeNotificationsMsgConsumer() {
return new TbPubSubConsumerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createEdgeNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> createEdgeEventMsgProducer() {
return null;
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
if (jsExecutorAdmin != null) {
jsExecutorAdmin.destroy();
}
if (transportApiAdmin != null) {
transportApiAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (vcAdmin != null) {
vcAdmin.destroy();
}
if (edgeAdmin != null) {
edgeAdmin.destroy();
}
}
}

278
common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java

@ -1,278 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import com.google.protobuf.util.JsonFormat;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.pubsub.TbPubSubAdmin;
import org.thingsboard.server.queue.pubsub.TbPubSubConsumerTemplate;
import org.thingsboard.server.queue.pubsub.TbPubSubProducerTemplate;
import org.thingsboard.server.queue.pubsub.TbPubSubSettings;
import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueEdgeSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import java.nio.charset.StandardCharsets;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='pubsub' && '${service.type:null}'=='tb-core'")
public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
private final TbPubSubSettings pubSubSettings;
private final TbQueueCoreSettings coreSettings;
private final TbQueueTransportApiSettings transportApiSettings;
private final TopicService topicService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TbQueueEdgeSettings edgeSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin transportApiAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin ruleEngineAdmin;
private final TbQueueAdmin edgeAdmin;
public PubSubTbCoreQueueFactory(TbPubSubSettings pubSubSettings,
TbQueueCoreSettings coreSettings,
TbQueueTransportApiSettings transportApiSettings,
TopicService topicService,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TbQueueEdgeSettings edgeSettings,
TbPubSubSubscriptionSettings pubSubSubscriptionSettings) {
this.pubSubSettings = pubSubSettings;
this.coreSettings = coreSettings;
this.transportApiSettings = transportApiSettings;
this.topicService = topicService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.edgeSettings = edgeSettings;
this.coreAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getCoreSettings());
this.jsExecutorAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getJsExecutorSettings());
this.transportApiAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getTransportApiSettings());
this.notificationAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getNotificationsSettings());
this.ruleEngineAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getRuleEngineSettings());
this.edgeAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getEdgeSettings());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(transportNotificationSettings.getNotificationsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName());
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> createToCoreMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreNotificationMsg>> createToCoreNotificationsMsgConsumer() {
return new TbPubSubConsumerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> createTransportApiRequestConsumer() {
return new TbPubSubConsumerTemplate<>(transportApiAdmin, pubSubSettings, topicService.buildTopicName(transportApiSettings.getRequestsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> createTransportApiResponseProducer() {
return new TbPubSubProducerTemplate<>(transportApiAdmin, pubSubSettings, topicService.buildTopicName(transportApiSettings.getResponsesTopic()));
}
@Override
@Bean
public TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate() {
TbQueueProducer<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>> producer = new TbPubSubProducerTemplate<>(jsExecutorAdmin, pubSubSettings, jsInvokeSettings.getRequestTopic());
TbQueueConsumer<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> consumer = new TbPubSubConsumerTemplate<>(jsExecutorAdmin, pubSubSettings,
jsInvokeSettings.getResponseTopic() + "." + serviceInfoProvider.getServiceId(),
msg -> {
JsInvokeProtos.RemoteJsResponse.Builder builder = JsInvokeProtos.RemoteJsResponse.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder);
return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders());
});
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> builder = DefaultTbQueueRequestTemplate.builder();
builder.queueAdmin(jsExecutorAdmin);
builder.requestTemplate(producer);
builder.responseTemplate(consumer);
builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests());
builder.maxRequestTimeout(jsInvokeSettings.getMaxRequestsTimeout());
builder.pollInterval(jsInvokeSettings.getResponsePollInterval());
return builder.build();
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToVersionControlServiceMsg>> createVersionControlMsgProducer() {
//TODO: version-control
return null;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperReprocessingMsgConsumer() {
return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperReprocessingTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToHousekeeperServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgConsumer() {
return new TbPubSubConsumerTemplate<>(edgeAdmin, pubSubSettings, topicService.buildTopicName(edgeSettings.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgProducer() {
return new TbPubSubProducerTemplate<>(edgeAdmin, pubSubSettings, topicService.buildTopicName(edgeSettings.getTopic()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createToEdgeNotificationsMsgConsumer() {
return new TbPubSubConsumerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createEdgeNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (jsExecutorAdmin != null) {
jsExecutorAdmin.destroy();
}
if (transportApiAdmin != null) {
transportApiAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
if (edgeAdmin != null) {
edgeAdmin.destroy();
}
}
}

209
common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java

@ -1,209 +0,0 @@
/**
* Copyright © 2016-2024 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.queue.provider;
import com.google.protobuf.util.JsonFormat;
import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate;
import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.pubsub.TbPubSubAdmin;
import org.thingsboard.server.queue.pubsub.TbPubSubConsumerTemplate;
import org.thingsboard.server.queue.pubsub.TbPubSubProducerTemplate;
import org.thingsboard.server.queue.pubsub.TbPubSubSettings;
import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings;
import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
import org.thingsboard.server.queue.settings.TbQueueEdgeSettings;
import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings;
import java.nio.charset.StandardCharsets;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='pubsub' && '${service.type:null}'=='tb-rule-engine'")
public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
private final TbPubSubSettings pubSubSettings;
private final TbQueueCoreSettings coreSettings;
private final TbQueueRuleEngineSettings ruleEngineSettings;
private final TopicService topicService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueRemoteJsInvokeSettings jsInvokeSettings;
private final TbQueueTransportNotificationSettings transportNotificationSettings;
private final TbQueueEdgeSettings edgeSettings;
private final TbQueueAdmin coreAdmin;
private final TbQueueAdmin ruleEngineAdmin;
private final TbQueueAdmin jsExecutorAdmin;
private final TbQueueAdmin notificationAdmin;
private final TbQueueAdmin edgeAdmin;
public PubSubTbRuleEngineQueueFactory(TbPubSubSettings pubSubSettings,
TbQueueCoreSettings coreSettings,
TbQueueRuleEngineSettings ruleEngineSettings,
TopicService topicService,
TbServiceInfoProvider serviceInfoProvider,
TbQueueRemoteJsInvokeSettings jsInvokeSettings,
TbQueueTransportNotificationSettings transportNotificationSettings,
TbPubSubSubscriptionSettings pubSubSubscriptionSettings,
TbQueueEdgeSettings edgeSettings) {
this.pubSubSettings = pubSubSettings;
this.coreSettings = coreSettings;
this.ruleEngineSettings = ruleEngineSettings;
this.topicService = topicService;
this.serviceInfoProvider = serviceInfoProvider;
this.jsInvokeSettings = jsInvokeSettings;
this.transportNotificationSettings = transportNotificationSettings;
this.edgeSettings = edgeSettings;
this.coreAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getCoreSettings());
this.ruleEngineAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getRuleEngineSettings());
this.jsExecutorAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getJsExecutorSettings());
this.notificationAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getNotificationsSettings());
this.edgeAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getEdgeSettings());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> createTransportNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(transportNotificationSettings.getNotificationsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> createRuleEngineMsgProducer() {
return new TbPubSubProducerTemplate<>(ruleEngineAdmin, pubSubSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createRuleEngineNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(ruleEngineSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> createTbCoreMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> createTbCoreNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> createEdgeMsgProducer() {
return new TbPubSubProducerTemplate<>(edgeAdmin, pubSubSettings, topicService.buildTopicName(edgeSettings.getTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createEdgeNotificationsMsgProducer() {
return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> createEdgeEventMsgProducer() {
return null;
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration) {
return new TbPubSubConsumerTemplate<>(ruleEngineAdmin, pubSubSettings, topicService.buildTopicName(configuration.getTopic()),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> createToRuleEngineNotificationsMsgConsumer() {
return new TbPubSubConsumerTemplate<>(notificationAdmin, pubSubSettings,
topicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(),
msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
}
@Override
@Bean
public TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate() {
TbQueueProducer<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>> producer = new TbPubSubProducerTemplate<>(jsExecutorAdmin, pubSubSettings, jsInvokeSettings.getRequestTopic());
TbQueueConsumer<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> consumer = new TbPubSubConsumerTemplate<>(jsExecutorAdmin, pubSubSettings,
jsInvokeSettings.getResponseTopic() + "." + serviceInfoProvider.getServiceId(),
msg -> {
JsInvokeProtos.RemoteJsResponse.Builder builder = JsInvokeProtos.RemoteJsResponse.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder);
return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders());
});
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> builder = DefaultTbQueueRequestTemplate.builder();
builder.queueAdmin(jsExecutorAdmin);
builder.requestTemplate(producer);
builder.responseTemplate(consumer);
builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests());
builder.maxRequestTimeout(jsInvokeSettings.getMaxRequestsTimeout());
builder.pollInterval(jsInvokeSettings.getResponsePollInterval());
return builder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getUsageStatsTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> createToOtaPackageStateServiceMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getOtaPackageTopic()));
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> createHousekeeperMsgProducer() {
return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, topicService.buildTopicName(coreSettings.getHousekeeperTopic()));
}
@PreDestroy
private void destroy() {
if (coreAdmin != null) {
coreAdmin.destroy();
}
if (ruleEngineAdmin != null) {
ruleEngineAdmin.destroy();
}
if (jsExecutorAdmin != null) {
jsExecutorAdmin.destroy();
}
if (notificationAdmin != null) {
notificationAdmin.destroy();
}
}
}

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

Loading…
Cancel
Save