Browse Source

Merge branch 'master' into task/4114-gateway-version-compatibility

pull/11516/head
Max Petrov 2 years ago
committed by GitHub
parent
commit
f535604ce9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 158
      application/src/main/data/json/system/scada_symbols/leak-sensor.svg
  2. 679
      application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg
  3. 679
      application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg
  4. 16
      application/src/main/data/json/system/scada_symbols/stand-filter.svg
  5. 276
      application/src/main/data/json/system/scada_symbols/waterstop.svg
  6. 4
      application/src/main/data/json/system/widget_bundles/scada_water_system_symbols.json
  7. 37
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  8. 94
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  9. 2
      pom.xml
  10. 11
      ui-ngx/src/assets/locale/locale.constant-en_US.json

158
application/src/main/data/json/system/scada_symbols/leak-sensor.svg

@ -0,0 +1,158 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="100" fill="none" version="1.1" viewBox="0 0 100 100">
<tb:metadata xmlns=""><![CDATA[{
"title": "Leak sensor",
"description": "Leak sensor",
"searchTags": [
"leak"
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"tags": [
{
"tag": "background",
"stateRenderFunction": "var fill = ctx.properties.backgroundColor;\nelement.attr({fill: fill});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "icon",
"stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.resetAnimation(element);\n}\n",
"actions": null
}
],
"behavior": [
{
"id": "leak",
"name": "{i18n:scada.symbol.leak}",
"hint": "{i18n:scada.symbol.water-leak-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": {
"action": "GET_TIME_SERIES",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "leak"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"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": "backgroundColor",
"name": "{i18n:scada.symbol.background-color}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "defaultColor",
"name": "{i18n:scada.symbol.default-color}",
"type": "color",
"default": "#1C943E",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "leakColor",
"name": "{i18n:scada.symbol.leak-color}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="#fff" tb:tag="background"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint0_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint1_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" stroke="#fff"/><path d="m56.668 52.309c0 2.04-0.7902 3.9966-2.1967 5.4391-0.7985 0.819-1.7586 1.4347-2.8035 1.8135m10-6.7401c0-10.962-11.667-19.487-11.667-19.487s-11.667 8.5256-11.667 19.487c0 3.2301 1.2292 6.3283 3.4171 8.6125 2.1879 2.284 5.1556 3.5659 8.2497 3.5659 3.0942 0 6.0615-1.2826 8.2493-3.5666 2.188-2.2842 3.4172-5.3817 3.4172-8.6118z" stroke="#1C943E" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" tb:tag="icon"/><path d="m33.631 0s-33.631 0-33.631 16.75v82.09c0 0.6628 0.89543 1.1601 2 1.1601h96c1.1045 0 2-0.4973 2-1.1601v-82.09c0-16.75-33.035-16.75-33.035-16.75h-16.965zm33.535 20.3c-0.64434 0-1.1666 0.3134-1.1666 0.7v75.1c0 0.3866 0.52234 0.7 1.1666 0.7h7.3336c0.64434 0 1.1666-0.3134 1.1666-0.7v-75.1c0-0.3866-0.52233-0.7-1.1666-0.7z" fill="#000" fill-opacity="0" style="stroke-width:.059761" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217648" x1="100" x2="1.5002" y1="50.119" y2="49.881" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".15"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".51114"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".85"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".95"/>
<stop stop-color="#020202" stop-opacity=".2" offset=".99341"/>
</linearGradient>
<linearGradient id="paint1_linear_2089_217648" x1="50" x2="50" y2="100" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset=".01"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".15343"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".5"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".85"/>
<stop stop-opacity=".12" offset=".95"/>
<stop stop-opacity=".2" offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

679
application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 55 KiB

679
application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 55 KiB

16
application/src/main/data/json/system/scada_symbols/stand-filter.svg

@ -7,7 +7,7 @@
],
"widgetSizeX": 3,
"widgetSizeY": 5,
"stateRenderFunction": "",
"stateRenderFunction": "var running = ctx.values.running;\nif (running) {\n ctx.api.enable(svg.children());\n} else {\n ctx.api.disable(svg.children());\n}",
"tags": [
{
"tag": "background",
@ -25,7 +25,7 @@
},
{
"tag": "filterMode",
"stateRenderFunction": "var defaultBorderColor = ctx.properties.defaultBorderColor;\nvar activeBorderColor = ctx.properties.activeBorderColor;\nvar defaultLabelColor = ctx.properties.defaultLabelColor;\nvar activeLabelColor = ctx.properties.activeLabelColor;\nvar boxBackground = ctx.properties.modeBoxBackground;\n\nvar running = ctx.values.running;\nvar filtrationMode = ctx.values.filtrationMode;\n\nvar filtrationMap = {};\nvar bottomShift = 104;\nvar rightShift = 226;\nvar middleShift = 121;\n\nvar filterModeSet = element.remember('filterModeSet');\n\nif (!filterModeSet) {\n element.remember('filterModeSet', true);\n var clone = element.children()[0];\n setFilterModeColors(clone);\n element.clear();\n \n var i = 0;\n if (ctx.properties.filtrationMode) {\n i++;\n filtrationMap[i] = 'filter';\n }\n if (ctx.properties.wasteMode) {\n i++;\n filtrationMap[i] = 'waste';\n }\n if (ctx.properties.backwashMode) {\n i++;\n filtrationMap[i] = 'backwash';\n }\n if (ctx.properties.recirculateMode) {\n i++;\n filtrationMap[i] = 'recirculate';\n }\n if (ctx.properties.rinseMode) {\n i++;\n filtrationMap[i] = 'rinse';\n }\n if (ctx.properties.closedMode) {\n i++;\n filtrationMap[i] = 'closed';\n }\n \n var filterMode = Object.values(filtrationMap);\n var lastToMiddle = filterMode.length % 2;\n \n filterMode.forEach((mode, index, arr) => {\n var template = clone.clone();\n var x = (index % 2) * rightShift;\n var y = Math.floor((index % filterMode.length) / 2) * bottomShift;\n if (index === filterMode.length-1 && lastToMiddle) {\n x = middleShift;\n }\n template.attr({'class': mode}).css('cursor', 'pointer').translate(x, y);\n ctx.api.text(template.findOne('text'), capitalizeFirstLetter(mode));\n template.click((event) => click(event, getFilterModeKey(mode)));\n element.add(template);\n })\n if (isFinite(filtrationMode)) {\n setFilterModeColorsByMap(filtrationMode, running);\n }\n}\n\nif (running) {\n ctx.api.enable(element.children());\n} else {\n ctx.api.disable(element.children());\n}\n\nfunction click(event, index) {\n var filtrationMode = ctx.values.filtrationMode;\n if (ctx.values.running && isFinite(filtrationMode)) {\n ctx.api.disable(element.children());\n var newValue = +index;\n if (newValue === filtrationMode) {\n newValue = 0;\n } else {\n setFilterModeColorsByMap(filtrationMode);\n }\n ctx.api.setValue('filtrationMode', newValue);\n ctx.api.callAction(event, 'filtrationModeUpdateState', newValue, {\n next: () => {\n setFilterModeColorsByMap(newValue ? newValue: filtrationMode, newValue);\n ctx.api.enable(element.children());\n },\n error: () => {\n setFilterModeColorsByMap(newValue);\n ctx.api.setValue('filtrationMode', filtrationMode);\n }\n });\n }\n}\n\nfunction getFilterModeKey(value) {\n return Object.keys(filtrationMap).find(key => filtrationMap[key] === value);\n}\n\nfunction setFilterModeColorsByMap(mode, active = false) {\n var filterBox = element.findOne('g.'+filtrationMap[mode])\n if (filterBox) {\n return setFilterModeColors(filterBox, active);\n }\n}\n\nfunction setFilterModeColors(filterBox, active = false) {\n if (filterBox) {\n var borderColor = active ? activeBorderColor : defaultBorderColor;\n var labelColor = active ? activeLabelColor : defaultLabelColor;\n filterBox.children()[0].fill(boxBackground);\n filterBox.children()[1].stroke(borderColor);\n filterBox.children()[2].fill(borderColor);\n ctx.api.font(filterBox.findOne('text'), null, labelColor);\n }\n}\n\nfunction capitalizeFirstLetter(string) {\n return string.charAt(0).toUpperCase() + string.slice(1);\n}",
"stateRenderFunction": "var defaultBorderColor = ctx.properties.defaultBorderColor;\nvar activeBorderColor = ctx.properties.activeBorderColor;\nvar defaultLabelColor = ctx.properties.defaultLabelColor;\nvar activeLabelColor = ctx.properties.activeLabelColor;\nvar boxBackground = ctx.properties.modeBoxBackground;\n\nvar running = ctx.values.running;\nvar filtrationMode = ctx.values.filtrationMode;\n\nvar filtrationMap = {};\nvar bottomShift = 104;\nvar rightShift = 226;\nvar middleShift = 121;\n\nvar filterModeSet = element.remember('filterModeSet');\n\nvar i = 0;\nif (ctx.properties.filtrationMode) {\n i++;\n filtrationMap[i] = 'filter';\n}\nif (ctx.properties.wasteMode) {\n i++;\n filtrationMap[i] = 'waste';\n}\nif (ctx.properties.backwashMode) {\n i++;\n filtrationMap[i] = 'backwash';\n}\nif (ctx.properties.recirculateMode) {\n i++;\n filtrationMap[i] = 'recirculate';\n}\nif (ctx.properties.rinseMode) {\n i++;\n filtrationMap[i] = 'rinse';\n}\nif (ctx.properties.closedMode) {\n i++;\n filtrationMap[i] = 'closed';\n}\n\nif (!filterModeSet) {\n element.remember('filterModeSet', true);\n var clone = element.children()[0];\n setFilterModeColors(clone);\n element.clear();\n \n var filterMode = Object.values(filtrationMap);\n var lastToMiddle = filterMode.length % 2;\n \n filterMode.forEach((mode, index, arr) => {\n var template = clone.clone();\n var x = (index % 2) * rightShift;\n var y = Math.floor((index % filterMode.length) / 2) * bottomShift;\n if (index === filterMode.length-1 && lastToMiddle) {\n x = middleShift;\n }\n template.attr({'class': mode}).css('cursor', 'pointer').translate(x, y);\n ctx.api.text(template.findOne('text'), capitalizeFirstLetter(mode));\n template.click((event) => click(event, getFilterModeKey(mode)));\n element.add(template);\n })\n}\n\nif (isFinite(filtrationMode)) {\n if (element.findOne('.active')) {\n setFilterModeColors(element.findOne('.active'));\n }\n setFilterModeColorsByMap(filtrationMode, running);\n}\n\nfunction click(event, index) {\n var filtrationMode = ctx.values.filtrationMode;\n if (ctx.values.running && isFinite(filtrationMode)) {\n ctx.api.disable(element.children());\n var newValue = +index;\n if (newValue === filtrationMode) {\n newValue = 0;\n } else {\n setFilterModeColorsByMap(filtrationMode);\n }\n ctx.api.setValue('filtrationMode', newValue);\n ctx.api.callAction(event, 'filtrationModeUpdateState', newValue, {\n next: () => {\n setFilterModeColorsByMap(newValue ? newValue: filtrationMode, newValue);\n ctx.api.enable(element.children());\n },\n error: () => {\n setFilterModeColorsByMap(newValue);\n ctx.api.setValue('filtrationMode', filtrationMode);\n ctx.api.enable(element.children());\n }\n });\n }\n}\n\nfunction getFilterModeKey(value) {\n return Object.keys(filtrationMap).find(key => filtrationMap[key] === value);\n}\n\nfunction setFilterModeColorsByMap(mode, active = false) {\n var filterBox = element.findOne('g.'+filtrationMap[mode])\n if (filterBox) {\n return setFilterModeColors(filterBox, active);\n }\n}\n\nfunction setFilterModeColors(filterBox, active = false) {\n if (filterBox) {\n if (active) {\n filterBox.addClass('active');\n } else {\n filterBox.removeClass('active');\n }\n var borderColor = active ? activeBorderColor : defaultBorderColor;\n var labelColor = active ? activeLabelColor : defaultLabelColor;\n filterBox.children()[0].fill(boxBackground);\n filterBox.children()[1].stroke(borderColor);\n filterBox.children()[2].fill(borderColor);\n ctx.api.font(filterBox.findOne('text'), null, labelColor);\n }\n}\n\nfunction capitalizeFirstLetter(string) {\n return string.charAt(0).toUpperCase() + string.slice(1);\n}",
"actions": null
}
],
@ -159,7 +159,7 @@
"id": "filtrationMode",
"name": "{i18n:scada.symbol.filter-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -175,7 +175,7 @@
"id": "wasteMode",
"name": "{i18n:scada.symbol.waste-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -191,7 +191,7 @@
"id": "backwashMode",
"name": "{i18n:scada.symbol.backwash-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -207,7 +207,7 @@
"id": "recirculateMode",
"name": "{i18n:scada.symbol.recirculate-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -223,7 +223,7 @@
"id": "rinseMode",
"name": "{i18n:scada.symbol.rinse-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -239,7 +239,7 @@
"id": "closedMode",
"name": "{i18n:scada.symbol.closed-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

276
application/src/main/data/json/system/scada_symbols/waterstop.svg

@ -0,0 +1,276 @@
<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": "Water stop",
"description": "Remotely controlled water shutoff valve with configurable connectors and various states.",
"searchTags": [
"water stop"
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "",
"tags": [
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "var opened = ctx.values.opened;\nvar action = opened ? 'close' : 'open';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('opened', !opened);\n }\n});"
}
}
},
{
"tag": "pipe-color",
"stateRenderFunction": "var color = ctx.properties.pipeColor;\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "walve",
"stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}\n",
"actions": null
}
],
"behavior": [
{
"id": "opened",
"name": "{i18n:scada.symbol.opened}",
"hint": "{i18n:scada.symbol.opened-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.opened}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "open"
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "open",
"name": "{i18n:scada.symbol.open}",
"hint": "{i18n:scada.symbol.open-hint}",
"group": null,
"type": "action",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": {
"action": "SET_ATTRIBUTE",
"executeRpc": {
"method": "setState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SHARED_SCOPE",
"key": "open"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"defaultWidgetActionSettings": null
},
{
"id": "close",
"name": "{i18n:scada.symbol.close}",
"hint": "{i18n:scada.symbol.close-hint}",
"group": null,
"type": "action",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": {
"action": "SET_ATTRIBUTE",
"executeRpc": {
"method": "setState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SHARED_SCOPE",
"key": "open"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"defaultWidgetActionSettings": null
},
{
"id": "closeAnimation",
"name": "{i18n:scada.symbol.close-state-animation}",
"hint": "{i18n:scada.symbol.close-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
}
],
"properties": [
{
"id": "openedColor",
"name": "{i18n:scada.symbol.opened-color}",
"type": "color",
"default": "#1C943E",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "closedColor",
"name": "{i18n:scada.symbol.closed-color}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "pipeColor",
"name": "{i18n:scada.symbol.pipe-color}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata><path d="M14 64H186V136H14V64Z" fill="#fff" tb:tag="pipe-color"/><path d="M14 64H186V136H14V64Z" fill="url(#paint0_linear_2089_217683)"/><path d="m15.5 65.5h169v69h-169v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="187.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="#1C943E" tb:tag="walve"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="url(#paint1_linear_2089_217683)"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" stroke="#fff"/><path d="m110 136v-72h-21v72h21z" fill="#1C943E" tb:tag="walve"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint2_linear_2089_217683)"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint3_linear_2089_217683)"/><path d="m109.5 135.5v-71h-20v71h20z" stroke="#000" stroke-opacity=".12"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="#647484"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="url(#paint4_linear_2089_217683)"/><path d="m126 62.5c1.381 0 2.5-1.1193 2.5-2.5v-6c0-1.3807-1.119-2.5-2.5-2.5h-52c-1.3807 0-2.5 1.1193-2.5 2.5v6c0 1.3807 1.1193 2.5 2.5 2.5h52z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m67.262 0s-67.262 0-67.262 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.93zm67.07 40.6c-1.2887 0-2.3332 0.6268-2.3332 1.4v150.2c0 0.7732 1.0447 1.4 2.3332 1.4h14.667c1.2887 0 2.3332-0.6268 2.3332-1.4v-150.2c0-0.7732-1.0447-1.4-2.3332-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217683" x1="58.72" x2="58.53" y1="64" y2="136" gradientUnits="userSpaceOnUse">
<stop stop-color="#727171" offset="0"/>
<stop stop-color="#727171" stop-opacity=".35" offset=".26388"/>
<stop stop-color="#727171" stop-opacity=".1" offset=".41759"/>
<stop stop-color="#fff" stop-opacity="0" offset=".49829"/>
<stop stop-color="#727171" stop-opacity=".1" offset=".58094"/>
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<linearGradient id="paint1_linear_2089_217683" x1="134.3" x2="65.705" y1="8.0405" y2="9.0306" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".14093"/>
<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=".85946"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<linearGradient id="paint2_linear_2089_217683" x1="99.475" x2="100.06" y1="64" y2="134.92" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".15" offset=".15823"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".28193"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".50291"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".72128"/>
<stop stop-color="#020202" stop-opacity=".15" offset=".8423"/>
<stop stop-color="#020202" stop-opacity=".35" offset=".99341"/>
</linearGradient>
<linearGradient id="paint3_linear_2089_217683" x1="110" x2="89" y1="100" y2="100" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset=".01"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".15343"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".5"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".85"/>
<stop stop-opacity=".12" offset=".95"/>
<stop stop-opacity=".2" offset="1"/>
</linearGradient>
<linearGradient id="paint4_linear_2089_217683" x1="72.287" x2="127.11" y1="55.78" y2="57.142" 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>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

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

@ -34,6 +34,9 @@
"left_flow_meter",
"horizontal_inline_flow_meter",
"vertical_inline_flow_meter",
"left_analog_water_level_meter",
"right_analog_water_level_meter",
"leak_sensor",
"centrifugal_pump",
"small_right_motor_pump",
"small_left_motor_pump",
@ -50,6 +53,7 @@
"vertical_wheel_valve",
"horizontal_ball_valve",
"vertical_ball_valve",
"water_stop",
"vertical_tank",
"stand_vertical_tank",
"cylindrical_tank",

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

@ -273,13 +273,13 @@ public class TbUtils {
Long.class, boolean.class, boolean.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class, boolean.class, boolean.class, int.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToRadixString", new MethodStub(TbUtils.class.getMethod("intLongToRadixString",
Long.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToRadixString", new MethodStub(TbUtils.class.getMethod("intLongToRadixString",
Long.class, int.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToRadixString", new MethodStub(TbUtils.class.getMethod("intLongToRadixString",
Long.class, int.class, boolean.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToRadixString", new MethodStub(TbUtils.class.getMethod("intLongToRadixString",
Long.class, int.class, boolean.class, boolean.class)));
parserConfig.addImport("floatToHex", new MethodStub(TbUtils.class.getMethod("floatToHex",
Float.class)));
@ -315,8 +315,6 @@ public class TbUtils {
String.class)));
parserConfig.addImport("decodeURI", new MethodStub(TbUtils.class.getMethod("decodeURI",
String.class)));
parserConfig.addImport("raiseError", new MethodStub(TbUtils.class.getMethod("raiseError",
String.class, Object.class)));
parserConfig.addImport("raiseError", new MethodStub(TbUtils.class.getMethod("raiseError",
String.class)));
parserConfig.addImport("isBinary", new MethodStub(TbUtils.class.getMethod("isBinary",
@ -327,7 +325,7 @@ public class TbUtils {
String.class)));
parserConfig.addImport("isHexadecimal", new MethodStub(TbUtils.class.getMethod("isHexadecimal",
String.class)));
parserConfig.addImport("byteArrayToExecutionArrayList", new MethodStub(TbUtils.class.getMethod("bytesToExecutionArrayList",
parserConfig.addImport("bytesToExecutionArrayList", new MethodStub(TbUtils.class.getMethod("bytesToExecutionArrayList",
ExecutionContext.class, byte[].class)));
parserConfig.addImport("padStart", new MethodStub(TbUtils.class.getMethod("padStart",
String.class, int.class, char.class)));
@ -725,19 +723,19 @@ public class TbUtils {
return prepareNumberHexString(l, bigEndian, pref, len, HEX_LEN_LONG_MAX);
}
public static String intLongToString(Long number) {
return intLongToString(number, DEC_RADIX);
public static String intLongToRadixString(Long number) {
return intLongToRadixString(number, DEC_RADIX);
}
public static String intLongToString(Long number, int radix) {
return intLongToString(number, radix, true);
public static String intLongToRadixString(Long number, int radix) {
return intLongToRadixString(number, radix, true);
}
public static String intLongToString(Long number, int radix, boolean bigEndian) {
return intLongToString(number, radix, bigEndian, false);
public static String intLongToRadixString(Long number, int radix, boolean bigEndian) {
return intLongToRadixString(number, radix, bigEndian, false);
}
public static String intLongToString(Long number, int radix, boolean bigEndian, boolean pref) {
public static String intLongToRadixString(Long number, int radix, boolean bigEndian, boolean pref) {
if (radix >= 25 && radix <= MAX_RADIX) {
return Long.toString(number, radix);
}
@ -1150,12 +1148,7 @@ public class TbUtils {
}
public static void raiseError(String message) {
raiseError(message, null);
}
public static void raiseError(String message, Object value) {
String msg = value == null ? message : message + " for value " + value;
throw new RuntimeException(msg);
throw new RuntimeException(message);
}
private static void parseRecursive(Object json, Map<String, Object> map, List<String> excludeList, String path, boolean pathInKey) {
@ -1344,8 +1337,8 @@ public class TbUtils {
* Writes the bit value to the appropriate location in the bins array, starting at the end of the array,
* to ensure proper alignment (highest bit to low end).
*/
public static byte[] parseLongToBinaryArray(long longValue, int binsLength) {
int len = Math.min(binsLength, BYTES_LEN_LONG_MAX * BIN_LEN_MAX);
public static byte[] parseLongToBinaryArray(long longValue, int binLength) {
int len = Math.min(binLength, BYTES_LEN_LONG_MAX * BIN_LEN_MAX);
byte[] bins = new byte[len];
for (int i = 0; i < len; i++) {
bins[len - 1 - i] = (byte) ((longValue >> i) & 1);

94
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -19,6 +19,7 @@ import com.google.common.collect.Lists;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@ -330,8 +331,8 @@ public class TbUtilsTest {
@Test
public void parseBytesIntToFloat() {
byte[] intValByte = {0x00, 0x00, 0x00, 0x0A};
Float valueExpected = 10.0f;
Float valueActual = TbUtils.parseBytesIntToFloat(intValByte, 3, 1, true);
float valueExpected = 10.0f;
float valueActual = TbUtils.parseBytesIntToFloat(intValByte, 3, 1, true);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 3, 1, false);
Assertions.assertEquals(valueExpected, valueActual);
@ -345,20 +346,23 @@ public class TbUtilsTest {
valueExpected = 10.0f;
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 0, 4, true);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 1.6777216E8f;
valueActual = TbUtils.parseBytesIntToFloat(intValByte, 0, 4, false);
valueExpected = 167772.16f;
double factor = 1e3;
valueActual = (float) (TbUtils.parseBytesIntToFloat(intValByte, 0, 4, false) / factor);
Assertions.assertEquals(valueExpected, valueActual);
String dataAT101 = "0x01756403671B01048836BF7701F000090722050000";
List<Byte> byteAT101 = TbUtils.hexToBytes(ctx, dataAT101);
float latitudeExpected = 24.62495f;
int offset = 9;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset, 4, false);
Assertions.assertEquals(latitudeExpected, valueActual / 1000000);
factor = 1e6;
valueActual = (float) (TbUtils.parseBytesIntToFloat(byteAT101, offset, 4, false) / factor);
Assertions.assertEquals(latitudeExpected, valueActual);
float longitudeExpected = 118.030576f;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset + 4, 4, false);
Assertions.assertEquals(longitudeExpected, valueActual / 1000000);
valueActual = (float) (TbUtils.parseBytesIntToFloat(byteAT101, offset + 4, 4, false) / factor);
Assertions.assertEquals(longitudeExpected, valueActual);
valueExpected = 9.185175E8f;
valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset);
@ -520,6 +524,18 @@ public class TbUtilsTest {
valueExpected = 7.2057594037927936E17d;
valueActual = TbUtils.parseBytesLongToDouble(longValByte, 0, 8, false);
Assertions.assertEquals(valueExpected, valueActual);
String dataPalacKiyv = "0x32D009423F23B300B0106E08D96B6C00";
List<Byte> byteAT101 = TbUtils.hexToBytes(ctx, dataPalacKiyv);
double latitudeExpected = 50.422775429058610d;
int offset = 0;
double factor = 1e15;
valueActual = TbUtils.parseBytesLongToDouble(byteAT101, offset, 8, false);
Assertions.assertEquals(latitudeExpected, valueActual / factor);
double longitudeExpected = 30.517877378257072d;
valueActual = TbUtils.parseBytesLongToDouble(byteAT101, offset + 8, 8, false);
Assertions.assertEquals(longitudeExpected, valueActual / factor);
}
@Test
@ -720,27 +736,27 @@ public class TbUtilsTest {
@Test
public void numberToString_Test() {
Assertions.assertEquals("00111010", TbUtils.intLongToString(58L, MIN_RADIX));
Assertions.assertEquals("0000000010011110", TbUtils.intLongToString(158L, MIN_RADIX));
Assertions.assertEquals("00000000000000100000001000000001", TbUtils.intLongToString(131585L, MIN_RADIX));
Assertions.assertEquals("0111111111111111111111111111111111111111111111111111111111111111", TbUtils.intLongToString(Long.MAX_VALUE, MIN_RADIX));
Assertions.assertEquals("1000000000000000000000000000000000000000000000000000000000000001", TbUtils.intLongToString(-Long.MAX_VALUE, MIN_RADIX));
Assertions.assertEquals("1111111111111111111111111111111111111111111111111111111110011010", TbUtils.intLongToString(-102L, MIN_RADIX));
Assertions.assertEquals("1111111111111111111111111111111111111111111111111100110010011010", TbUtils.intLongToString(-13158L, MIN_RADIX));
Assertions.assertEquals("777777777777777777777", TbUtils.intLongToString(Long.MAX_VALUE, 8));
Assertions.assertEquals("1000000000000000000000", TbUtils.intLongToString(Long.MIN_VALUE, 8));
Assertions.assertEquals("9223372036854775807", TbUtils.intLongToString(Long.MAX_VALUE));
Assertions.assertEquals("-9223372036854775808", TbUtils.intLongToString(Long.MIN_VALUE));
Assertions.assertEquals("3366", TbUtils.intLongToString(13158L, 16));
Assertions.assertEquals("FFCC9A", TbUtils.intLongToString(-13158L, 16));
Assertions.assertEquals("0xFFCC9A", TbUtils.intLongToString(-13158L, 16, true, true));
Assertions.assertEquals("0x0400", TbUtils.intLongToString(1024L, 16, true, true));
Assertions.assertNotEquals("400", TbUtils.intLongToString(1024L, 16));
Assertions.assertEquals("0xFFFC00", TbUtils.intLongToString(-1024L, 16, true, true));
Assertions.assertNotEquals("0xFC00", TbUtils.intLongToString(-1024L, 16, true, true));
Assertions.assertEquals("hazelnut", TbUtils.intLongToString(1356099454469L, MAX_RADIX));
Assertions.assertEquals("00111010", TbUtils.intLongToRadixString(58L, MIN_RADIX));
Assertions.assertEquals("0000000010011110", TbUtils.intLongToRadixString(158L, MIN_RADIX));
Assertions.assertEquals("00000000000000100000001000000001", TbUtils.intLongToRadixString(131585L, MIN_RADIX));
Assertions.assertEquals("0111111111111111111111111111111111111111111111111111111111111111", TbUtils.intLongToRadixString(Long.MAX_VALUE, MIN_RADIX));
Assertions.assertEquals("1000000000000000000000000000000000000000000000000000000000000001", TbUtils.intLongToRadixString(-Long.MAX_VALUE, MIN_RADIX));
Assertions.assertEquals("1111111111111111111111111111111111111111111111111111111110011010", TbUtils.intLongToRadixString(-102L, MIN_RADIX));
Assertions.assertEquals("1111111111111111111111111111111111111111111111111100110010011010", TbUtils.intLongToRadixString(-13158L, MIN_RADIX));
Assertions.assertEquals("777777777777777777777", TbUtils.intLongToRadixString(Long.MAX_VALUE, 8));
Assertions.assertEquals("1000000000000000000000", TbUtils.intLongToRadixString(Long.MIN_VALUE, 8));
Assertions.assertEquals("9223372036854775807", TbUtils.intLongToRadixString(Long.MAX_VALUE));
Assertions.assertEquals("-9223372036854775808", TbUtils.intLongToRadixString(Long.MIN_VALUE));
Assertions.assertEquals("3366", TbUtils.intLongToRadixString(13158L, 16));
Assertions.assertEquals("FFCC9A", TbUtils.intLongToRadixString(-13158L, 16));
Assertions.assertEquals("0xFFCC9A", TbUtils.intLongToRadixString(-13158L, 16, true, true));
Assertions.assertEquals("0x0400", TbUtils.intLongToRadixString(1024L, 16, true, true));
Assertions.assertNotEquals("400", TbUtils.intLongToRadixString(1024L, 16));
Assertions.assertEquals("0xFFFC00", TbUtils.intLongToRadixString(-1024L, 16, true, true));
Assertions.assertNotEquals("0xFC00", TbUtils.intLongToRadixString(-1024L, 16, true, true));
Assertions.assertEquals("hazelnut", TbUtils.intLongToRadixString(1356099454469L, MAX_RADIX));
}
@Test
@ -870,14 +886,16 @@ public class TbUtilsTest {
@Test
public void raiseError_Test() {
String message = "frequency_weighting_type must be 0, 1 or 2.";
Object value = 4;
String message = "frequency_weighting_type must be 0, 1 or 2. A value of " + value.toString() + " is invalid.";
try {
TbUtils.raiseError(message, value);
TbUtils.raiseError(message);
Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2. for value 4"));
Assertions.assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2. A value of 4 is invalid."));
}
message = "frequency_weighting_type must be 0, 1 or 2.";
try {
TbUtils.raiseError(message);
Assertions.fail("Should throw NumberFormatException");
@ -1067,6 +1085,18 @@ public class TbUtilsTest {
String actual = TbUtils.hexToBase64(hex);
Assertions.assertEquals(expected, actual);
}
@Test
public void bytesToHex_Test() {
byte[] bb = {(byte) 0xBB, (byte) 0xAA};
String expected = "BBAA";
String actual = TbUtils.bytesToHex(bb);
Assertions.assertEquals(expected, actual);
ExecutionArrayList<Integer> expectedList = new ExecutionArrayList<>(ctx);
expectedList.addAll(List.of(-69, 83));
expected = "BB53";
actual = TbUtils.bytesToHex(expectedList);
Assertions.assertEquals(expected, actual);
}
private static List<Byte> toList(byte[] data) {
List<Byte> result = new ArrayList<>(data.length);

2
pom.xml

@ -83,7 +83,7 @@
<zookeeper.version>3.9.2</zookeeper.version>
<protobuf.version>3.25.3</protobuf.version> <!-- A Major v4 does not support by the pubsub yet-->
<grpc.version>1.63.0</grpc.version>
<tbel.version>1.2.2</tbel.version>
<tbel.version>1.2.3</tbel.version>
<lombok.version>1.18.32</lombok.version>
<paho.client.version>1.2.5</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

11
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3835,6 +3835,8 @@
"open-hint": "Action invoked when user clicks to open component.",
"close": "Close",
"close-hint": "Action invoked when user clicks to close component.",
"close-state-animation": "Closed state animation",
"close-state-animation-hint": "Whether to enable blinking animation when component is in closed state.",
"opened-color": "Opened color",
"closed-color": "Closed color",
"opened-rotation-angle": "Opened rotation angle",
@ -3880,7 +3882,14 @@
"stand-filter-color": "Stand filter color",
"mode-box-background": "Mode box background",
"border-color": "Border color",
"label-color": "Label color"
"label-color": "Label color",
"water-leak-hint": "Indicates whether a leak.",
"default-color": "Default color",
"leak-color": "Leak color",
"full-value": "Full value",
"full-value-hint": "Double value indicating full value.",
"label": "Label",
"icon": "Icon"
}
},
"item": {

Loading…
Cancel
Save